From f96e233d24851e04b15f3702889011059e8e8ce7 Mon Sep 17 00:00:00 2001
From: GONGONGONG <506419689@qq.com>
Date: Wed, 21 Feb 2024 14:48:26 +0800
Subject: [PATCH 01/17] =?UTF-8?q?feat:Agent=E5=8C=85=E7=AE=A1=E7=90=86?=
=?UTF-8?q?=E5=BC=80=E5=8F=91=E5=8F=8A=E6=8E=A5=E5=85=A5=E7=82=B9=E6=94=B9?=
=?UTF-8?q?=E9=80=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 6 +-
frontend/.eslintrc.js | 1 +
frontend/src/api/modules/pkg_manage.js | 33 +
frontend/src/common/demand-import.ts | 4 +-
frontend/src/common/form-check.ts | 29 +-
frontend/src/common/form-label-hook.ts | 34 +
frontend/src/common/regexp.ts | 2 +-
frontend/src/common/util.ts | 22 +
.../src/components/RussianDolls/DollForm.vue | 20 +-
.../RussianDolls/item/DollArray.vue | 20 +-
.../components/RussianDolls/item/DollBase.vue | 30 +-
.../RussianDolls/item/DollIndex.vue | 25 +-
.../RussianDolls/item/DollObject.vue | 25 +-
.../src/components/common/flexible-tag.vue | 214 +++--
frontend/src/components/common/navigation.vue | 49 +-
.../src/components/common/strategy-table.vue | 4 +-
.../components/common/strategy-template.vue | 2 +-
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/config/test-anchor-key.ts | 1 +
frontend/src/css/app.css | 20 +-
frontend/src/css/install-table.css | 5 +
frontend/src/css/variable.css | 2 +
frontend/src/i18n/en.js | 92 +-
frontend/src/i18n/zh.js | 96 +-
frontend/src/router/modules/agent-manager.ts | 15 +
frontend/src/router/navigation-config.ts | 8 +-
frontend/src/setup/i18n-setup.ts | 2 +
frontend/src/setup/index.ts | 8 +-
frontend/src/store/modules/agent.ts | 98 +-
frontend/src/store/modules/cloud.ts | 2 +-
frontend/src/store/modules/config.ts | 34 +-
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/config/config.ts | 43 +-
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/cloud-channel/channel-edit.vue | 6 +-
.../cloud/cloud-channel/channel-table.vue | 8 +-
.../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 +
.../gse-config/access-point-table.vue | 243 +++--
.../views/global-config/gse-config/index.vue | 7 +-
.../set-access-point/apFormConfig.ts | 88 +-
.../set-access-point/host-td-input.vue | 70 ++
.../set-access-point/host-td-operate.vue | 61 ++
.../set-access-point/step-form-table.vue | 2 +-
.../gse-config/set-access-point/step-host.vue | 905 ++++++++++--------
.../gse-config/set-access-point/step-info.vue | 19 +-
.../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 -
79 files changed, 6080 insertions(+), 836 deletions(-)
create mode 100644 frontend/src/api/modules/pkg_manage.js
create mode 100644 frontend/src/common/form-label-hook.ts
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
create mode 100644 frontend/src/views/global-config/gse-config/set-access-point/host-td-input.vue
create mode 100644 frontend/src/views/global-config/gse-config/set-access-point/host-td-operate.vue
diff --git a/.gitignore b/.gitignore
index cd5b58fe3..936953f8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -188,4 +188,8 @@ build.yml
## Helm
support-files/**/private_values.yaml
support-files/**/*.tgz
-.codecc
\ No newline at end of file
+.codecc
+.idea
+.vscode
+
+pre-*-bkcodeai
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
index b7f44e364..c9d924368 100644
--- a/frontend/.eslintrc.js
+++ b/frontend/.eslintrc.js
@@ -20,5 +20,6 @@ module.exports = {
rules: {
'@typescript-eslint/member-ordering': 'off',
'no-param-reassign': 'off',
+ 'vue/camelcase': 'off',
},
};
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/form-check.ts b/frontend/src/common/form-check.ts
index 7ba6d7c9b..7bb4640bd 100644
--- a/frontend/src/common/form-check.ts
+++ b/frontend/src/common/form-check.ts
@@ -1,3 +1,4 @@
+import i18n from '@/setup';
import { isEmpty } from './util';
import {
splitCodeArr,
@@ -34,7 +35,7 @@ export function createIpRegu(type: 'IPv4' | 'IPv6' | 'mixins' = 'IPv4', isBatch
: (val: string) => !val || regex.test(val);
return {
trigger: 'blur',
- message: window.i18n.t('IP格式不正确'),
+ message: i18n.t('IP格式不正确'),
// regex,
validator,
};
@@ -45,7 +46,7 @@ export function createIpRegu(type: 'IPv4' | 'IPv6' | 'mixins' = 'IPv4', isBatch
*/
export const reguRequired = {
required: true,
- message: window.i18n.t('必填项'),
+ message: i18n.t('必填项'),
trigger: 'blur',
};
export const reguIp = createIpRegu();
@@ -57,39 +58,41 @@ export const reguIpMixinsBatch = createIpRegu('mixins', true);
export const reguUrl = {
regex: regUrl,
validator: (val: string) => regUrl.test(val),
- message: window.i18n.t('URL格式不正确'),
+ message: i18n.t('URL格式不正确'),
trigger: 'blur',
};
export const reguUrlMixinIp = {
regex: regUrlMixinIp,
- message: window.i18n.t('URL格式不正确'),
+ message: i18n.t('URL格式不正确'),
trigger: 'blur',
validator: (val: string) => regUrlMixinIp.test(val),
};
export const reguPort = {
// validator: (val: string) => regNaturalNumber.test(val) && parseInt(val, 10) <= 65535, // 端口范围不应该包括
- validator: (val: string) => regNaturalNumber.test(val) && val && parseInt(val, 10) && parseInt(val, 10) <= 65535,
- message: window.i18n.t('端口范围', { range: '1-65535' }),
+ validator: (val: string): boolean => regNaturalNumber.test(val)
+ && val && parseInt(val, 10)
+ && parseInt(val, 10) <= 65535,
+ message: i18n.t('端口范围', { range: '1-65535' }),
trigger: 'blur',
};
export const reguNaturalNumber = {
regex: regNaturalNumber,
validator: (val: string) => regNaturalNumber.test(val),
- message: window.i18n.t('不小于零的整数'),
+ message: i18n.t('不小于零的整数'),
trigger: 'blur',
};
export function reguFnName(params?: { max: number } = {}) {
const { max = 32 } = params;
return {
validator: (val: string) => regNormalText.test(val) && regrLengthCheck(val, max),
- message: window.i18n.t('正常输入内容校验', [max]),
+ message: i18n.t('正常输入内容校验', [max]),
trigger: 'blur',
};
}
export function reguFnStrLength(max = 40) {
return {
validator: (val: string) => regrLengthCheck(val, max),
- message: window.i18n.t('字符串长度校验', [Math.floor(max / 2), max]),
+ message: i18n.t('字符串长度校验', [Math.floor(max / 2), max]),
trigger: 'blur',
};
}
@@ -98,14 +101,14 @@ export function reguFnSysPath(params?: { [key: string]: number | string } = {})
const reg = regFnSysPath({ minText, maxText, minLevel, type });
return {
type,
- message: window.i18n.t(i18nMap[type], { minLevel, maxText }),
+ message: i18n.t(i18nMap[type], { minLevel, maxText }),
trigger: 'blur',
validator: (val: string) => reg.test(val),
};
}
export function reguFnMinInteger(min = 0) {
return {
- message: window.i18n.t('整数最小值校验提示', { min }),
+ message: i18n.t('整数最小值校验提示', { min }),
validator: (val: string) => isEmpty(val) || (regInteger.test(val) && parseInt(val, 10) >= min),
trigger: 'blur',
};
@@ -113,14 +116,14 @@ export function reguFnMinInteger(min = 0) {
export function reguFnRangeInteger(min: number, max: number) {
return {
validator: (val: string) => regInteger.test(val) && Number(val) <= max && Number(val) >= min,
- message: window.i18n.t('整数范围校验提示', { max, min }),
+ message: i18n.t('整数范围校验提示', { max, min }),
trigger: 'blur',
};
}
// 一行内不能重复
export const reguIpInLineRepeat = {
trigger: 'blur',
- message: window.i18n.t('冲突校验', { prop: 'IP' }),
+ message: i18n.t('冲突校验', { prop: 'IP' }),
validator(val: string) {
if (!val) return true;
const splitCode = splitCodeArr.find(split => val.indexOf(split) > 0);
diff --git a/frontend/src/common/form-label-hook.ts b/frontend/src/common/form-label-hook.ts
new file mode 100644
index 000000000..31eb51d59
--- /dev/null
+++ b/frontend/src/common/form-label-hook.ts
@@ -0,0 +1,34 @@
+import { ref } from 'vue';
+
+export default function useFormLabelWidth() {
+ const minWidth = ref(0);
+
+ const initLabelWidth = (form: Vue) => {
+ const el = form ? form.$el : null;
+ if (!el) return;
+ let max = 0;
+ const leftPadding = 28;
+ const safePadding = 8;
+ const $labelEleList = el.querySelectorAll('.bk-label');
+ $labelEleList.forEach((item) => {
+ const spanEle = item.querySelector('span');
+ if (spanEle) {
+ const { width } = spanEle.getBoundingClientRect();
+ max = Math.max(minWidth.value, max, width);
+ }
+ });
+ const width = Math.ceil(max + leftPadding + safePadding);
+ $labelEleList.forEach((item) => {
+ (item as HTMLElement).style.width = `${width}px`;
+ });
+ el.querySelectorAll('.bk-form-content').forEach((item) => {
+ (item as HTMLElement).style.marginLeft = `${width}px`;
+ });
+ return width;
+ };
+
+ return {
+ minWidth,
+ initLabelWidth,
+ };
+}
diff --git a/frontend/src/common/regexp.ts b/frontend/src/common/regexp.ts
index e6af81536..1ff6b9e6f 100644
--- a/frontend/src/common/regexp.ts
+++ b/frontend/src/common/regexp.ts
@@ -18,7 +18,7 @@ export const regIp = new RegExp(`^${IpStr}$`);
export const regFilterIp = new RegExp(`^(?:\\d+:)?(${IpStr})$`);
export const regExclusiveFilterIp = new RegExp(`^\\d+:${IpStr}$`);
export const regIPv6 = new RegExp(`^${IPv6Str}$`);
-export const regIpMixin = window.$DHCP ? new RegExp(`^${IpStr}|${IPv6Str}$`) : regIp; // 区分环境可用的IP类型
+export const regIpMixin = window.$DHCP ? new RegExp(`^${IpStr}$|^${IPv6Str}$`) : regIp; // 区分环境可用的IP类型
export const regFilterIpMixin = window.$DHCP ? new RegExp(`^(?:\\d+:)?(${IpStr}|${IPv6Str})$`) : regFilterIp;
export const regExclusiveFilterIpMixin = window.$DHCP ? new RegExp(`^\\d+:(${IpStr}|${IPv6Str})$`) : regExclusiveFilterIp; // 用于区分IP还是按管控区域筛选ip
export const regUrl = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'*+,;=.]+$/;
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/RussianDolls/DollForm.vue b/frontend/src/components/RussianDolls/DollForm.vue
index 4b1ee7b61..18c41d56f 100644
--- a/frontend/src/components/RussianDolls/DollForm.vue
+++ b/frontend/src/components/RussianDolls/DollForm.vue
@@ -16,10 +16,22 @@ import { deepClone } from '@/common/util';
export default defineComponent({
name: 'RussianDollsForm',
props: {
- data: () => [],
- layout: () => [],
- rules: () => ({}),
- value: () => ({}),
+ data: {
+ type: Array,
+ default: () => [],
+ },
+ layout: {
+ type: Array,
+ default: () => [],
+ },
+ rules: {
+ type: Object,
+ default: () => ({}),
+ },
+ value: {
+ type: Object,
+ default: () => ({}),
+ },
labelWidth: {
type: Number,
default: 150,
diff --git a/frontend/src/components/RussianDolls/item/DollArray.vue b/frontend/src/components/RussianDolls/item/DollArray.vue
index 8af324e14..14a94b518 100644
--- a/frontend/src/components/RussianDolls/item/DollArray.vue
+++ b/frontend/src/components/RussianDolls/item/DollArray.vue
@@ -40,10 +40,22 @@ import bus from '@/common/bus';
export default defineComponent({
props: {
- item: () => ({}),
- schema: () => ({}),
- value: () => [],
- valueProp: '',
+ item: {
+ type: Object,
+ default: () => ({}),
+ },
+ schema: {
+ type: Object,
+ default: () => ({}),
+ },
+ value: {
+ type: Array,
+ default: () => [],
+ },
+ valueProp: {
+ type: String,
+ default: '',
+ },
labelWidth: {
type: Number,
default: 110,
diff --git a/frontend/src/components/RussianDolls/item/DollBase.vue b/frontend/src/components/RussianDolls/item/DollBase.vue
index 78b824b1c..3b3a7d2e3 100644
--- a/frontend/src/components/RussianDolls/item/DollBase.vue
+++ b/frontend/src/components/RussianDolls/item/DollBase.vue
@@ -22,12 +22,30 @@ import { defineComponent, inject, toRefs } from 'vue';
export default defineComponent({
props: {
- item: () => ({}),
- schema: () => ({}),
- itemIndex: -1,
- value: '',
- valueProp: '',
- labelWidth: 110,
+ item: {
+ type: Object,
+ default: () => ({}),
+ },
+ schema: {
+ type: Object,
+ default: () => ({}),
+ },
+ itemIndex: {
+ type: Number,
+ default: -1,
+ },
+ value: {
+ type: String,
+ default: '',
+ },
+ valueProp: {
+ type: String,
+ default: '',
+ },
+ labelWidth: {
+ type: Number,
+ default: 110,
+ },
},
setup(props) {
const updateFormData = inject('updateFormData');
diff --git a/frontend/src/components/RussianDolls/item/DollIndex.vue b/frontend/src/components/RussianDolls/item/DollIndex.vue
index 9543462a0..7d31bf906 100644
--- a/frontend/src/components/RussianDolls/item/DollIndex.vue
+++ b/frontend/src/components/RussianDolls/item/DollIndex.vue
@@ -38,11 +38,26 @@ import { defineComponent, toRefs } from 'vue';
export default defineComponent({
props: {
- item: () => ({}),
- itemIndex: -1,
- value: () => ({}),
- valueProp: '',
- labelWidth: 110,
+ item: {
+ type: Object,
+ default: () => ({}),
+ },
+ itemIndex: {
+ type: Number,
+ default: -1,
+ },
+ value: {
+ type: Object,
+ default: () => ({}),
+ },
+ valueProp: {
+ type: String,
+ default: '',
+ },
+ labelWidth: {
+ type: Number,
+ default: 110,
+ },
},
setup(props) {
return {
diff --git a/frontend/src/components/RussianDolls/item/DollObject.vue b/frontend/src/components/RussianDolls/item/DollObject.vue
index 55f3319a1..fa4f3ee8f 100644
--- a/frontend/src/components/RussianDolls/item/DollObject.vue
+++ b/frontend/src/components/RussianDolls/item/DollObject.vue
@@ -17,11 +17,26 @@ import { defineComponent, inject, toRefs } from 'vue';
export default defineComponent({
props: {
- item: () => ({}),
- schema: () => ({}),
- itemIndex: -1,
- value: () => ({}),
- valueProp: '',
+ item: {
+ type: Object,
+ default: () => ({}),
+ },
+ schema: {
+ type: Object,
+ default: () => ({}),
+ },
+ itemIndex: {
+ type: Number,
+ default: -1,
+ },
+ value: {
+ type: Object,
+ default: () => ({}),
+ },
+ valueProp: {
+ type: String,
+ default: '',
+ },
labelWidth: {
type: Number,
default: 110,
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 @@
-
+
{
+ if (item.name === 'nav_Agent') {
+ item.children = item.children?.filter((child: { name: string; }) => child.name !== 'agentPackage');
+ }
+ });
+ return navList;
+ }
+ private async created() {
+ // 获取开关状态
+ await this.getAgentPackageUI();
+ }
// 左侧导航list
private get sideMenuList() {
if (this.activeIndex === -1) return [];
- return this.navList[this.activeIndex].children || [];
+ // 根据开关状态过滤agent包管理菜单
+ const agentSwitch = MainStore.ENABLE_AGENT_PACKAGE_UI;
+ const menuList = !agentSwitch ? this.navListHideAgentPkg : this.navList[this.activeIndex].children;
+ return menuList || [];
}
// 子菜单默认激活项
private get currentActive() {
@@ -410,15 +429,20 @@ export default class NodemanNavigation extends Mixins(routerBackMixin) {
public handleBizChange(newBizIds: number[]) {
MainStore.setSelectedBiz(newBizIds);
}
+ public async getAgentPackageUI() {
+ await MainStore.getAgentPackageUI();
+ }
}
-
diff --git a/frontend/src/components/common/strategy-table.vue b/frontend/src/components/common/strategy-table.vue
index efcfbafb9..652b65546 100644
--- a/frontend/src/components/common/strategy-table.vue
+++ b/frontend/src/components/common/strategy-table.vue
@@ -168,7 +168,7 @@ export default class StrategyTable extends Vue {
},
{
source: 'Agent',
- targetAdress: 'GSE_task',
+ targetAdress: 'GSE_cluster',
protocol: 'TCP',
portKey: 'io_port',
use: this.$t('任务服务端口'),
@@ -397,7 +397,7 @@ export default class StrategyTable extends Vue {
Proxy: [
{
source: 'Proxy(gse_agent)',
- targetAdress: 'GSE_task',
+ targetAdress: 'GSE_cluster',
protocol: 'TCP',
portKey: 'io_port',
use: this.$t('任务服务端口'),
diff --git a/frontend/src/components/common/strategy-template.vue b/frontend/src/components/common/strategy-template.vue
index 6e78d9b21..f2522c4a6 100644
--- a/frontend/src/components/common/strategy-template.vue
+++ b/frontend/src/components/common/strategy-template.vue
@@ -115,7 +115,7 @@ export default class StrategyTemplate extends Vue {
let idKey = cloud.ap_id;
let ap = AgentStore.apList.find(apItem => apItem.id === idKey) as IApExpand;
- const serverKey = cloud.type === 'Agent' ? 'inner_ip' : 'outer_ip'; // Pagent 非必要
+ const serverKey = cloud.type === 'Agent' ? 'inner_ip_infos' : 'outer_ip_infos'; // Pagent 非必要
const proxyKey = cloud.type === 'Proxy' ? 'outer_ip' : 'inner_ip'; // Agent 非必要
// 先排除掉找不到接入点的主机
if (ap) {
diff --git a/frontend/src/components/common/tag.vue b/frontend/src/components/common/tag.vue
new file mode 100644
index 000000000..2163046c7
--- /dev/null
+++ b/frontend/src/components/common/tag.vue
@@ -0,0 +1,458 @@
+
+
+
+
+
+
+
+
+ --
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/common/upload.vue b/frontend/src/components/common/upload.vue
index c6c0b5770..cf8e34ddc 100644
--- a/frontend/src/components/common/upload.vue
+++ b/frontend/src/components/common/upload.vue
@@ -43,13 +43,17 @@
-
-
- {{ $t('上传成功') }}
-
-
- {{ file.errorMsg }}
-
+
+
+
+ {{ $t('上传成功') }}
+
+
+
+
+ {{ file.errorMsg }}
+
+
@@ -60,7 +64,7 @@
- {{ $t('版本包文件类型') }}
+ {{ acceptDesc }}
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/config/test-anchor-key.ts b/frontend/src/config/test-anchor-key.ts
index 7da3f67f8..d03266282 100644
--- a/frontend/src/config/test-anchor-key.ts
+++ b/frontend/src/config/test-anchor-key.ts
@@ -196,6 +196,7 @@ export default {
accessPoint: {
apBaseForm: 'form_apBase',
apBaseTable: 'table_apBase',
+ apOmitBtn: 'btn_apOmit',
apTestBtn: 'btn_apTest',
apBaseInput: 'input_apBase',
addRowBtn: 'btn_addRow',
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 24d6201c3..460910966 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',
@@ -472,7 +494,7 @@ export default {
Server信息: 'Server information',
Server序号: 'Server serial number ',
Agent信息: 'Agent information',
- 安装包: 'Installation package',
+ Proxy安装包: 'Proxy installation package',
内网: 'Intranet:',
外网: 'Extranet:',
hostid路径: 'Hostid path',
@@ -482,10 +504,12 @@ export default {
数据文件路径: 'Data file path',
临时文件路径: 'Temporary file path',
接入点名称: 'Access point',
+ 接入点版本: 'Access point version',
+ 接入点描述: 'Select the associated GSE channel, V1 is associated with GSE1.0, V2 is associated with GSE2.0',
用户创建的接入点: 'User-created access point',
请输入Server的内网IP: 'Please enter the intranet IP of {type} Server',
请输入Server的外网IP: 'Please enter the extranet IP of {type} Server',
- Agent安装包: 'Agent package',
+ Agent安装包信息: 'Agent package',
Agent包URL: 'Package URL',
Agent包服务器目录: 'Package path',
请输入服务器目录: 'Please enter the the server path',
@@ -529,6 +553,12 @@ export default {
加载插件基础信息成功: 'Load plugin basic information successfully',
该接入点被使用中无法删除: 'This access point is in use and cannot be deleted',
序号: 'No.',
+ GSEFile服务地址: 'GSE File Endpoints',
+ GSEData服务地址: 'GSE Data Endpoints',
+ GSECluster服务地址: 'GSE Cluster Endpoints',
+ 跳过检测: 'Skip Detection',
+ 回调地址: 'Callback Address',
+ IPv4和IPv6不能混合使用: 'IPv4 and IPv6 cannot be mixed',
// 自监控
后台服务器: 'Servers',
@@ -567,6 +597,8 @@ export default {
内网回调: 'LAN callback',
请输入内网回调地址: 'Please enter the LAN callback address',
请输入以backend结尾的URL地址: 'Please enter the URL address ending with /backend',
+ 内网URL: 'LAN url',
+ 外网URL: 'WAN url',
// 任务历史
机器数量: '(total {0})',
@@ -629,6 +661,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',
@@ -938,6 +971,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}',
@@ -1115,6 +1150,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 170121900..f6c811e7c 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 磁盘空间',
@@ -374,9 +396,9 @@ export default {
主机属性: '主机属性',
登录信息: '登录信息',
批量应用: '批量应用',
- 内网IP: '内网 IP',
- 内网IPv4: '内网 IPv4',
- 内网IPv6: '内网 IPv6',
+ 内网IP: '内网IP',
+ 内网IPv4: '内网IPv4',
+ 内网IPv6: '内网IPv6',
'「登录IP」': '「登录 IP」',
'「内网IPv4」': '「内网IPv4」',
'「内网IPv6」': '「内网IPv6」',
@@ -416,6 +438,7 @@ export default {
动态: '动态',
静态: '静态',
接入点互斥: '主机处于 GSE{0}接入点,{1} 的接入点禁用',
+ pkg的详细信息: '{0} 的详细信息',
// 安装通道
安装通道: '安装通道',
@@ -462,6 +485,7 @@ export default {
复制成功: '复制成功',
没有需要复制的内容: '没有需要复制的内容',
查看任务详情: '查看任务详情',
+ 新建标签: '新建 ”{0}“ 标签',
// 全局配置 - GSE-环境管理
默认: '默认',
@@ -472,7 +496,7 @@ export default {
Server信息: 'Server信息',
Server序号: 'Server序号',
Agent信息: 'Agent信息',
- 安装包: '安装包',
+ Proxy安装包: 'Proxy安装包',
内网: '内网: ',
外网: '外网: ',
hostid路径: 'hostid路径',
@@ -482,10 +506,12 @@ export default {
数据文件路径: '数据文件路径',
临时文件路径: '临时文件路径',
接入点名称: '接入点名称',
+ 接入点版本: '接入点版本',
+ 接入点描述: '选择关联的GSE通道,V1关联GSE1.0,V2关联GSE2.0',
用户创建的接入点: '用户创建的接入点',
请输入Server的内网IP: '请输入{type} Server的内网IP',
请输入Server的外网IP: '请输入{type} Server的外网IP',
- Agent安装包: 'Agent安装包',
+ Agent安装包信息: 'Agent安装包信息',
Agent包URL: 'Agent包URL',
Agent包服务器目录: 'Agent包服务器目录',
请输入服务器目录: '请输入服务器目录',
@@ -529,6 +555,12 @@ export default {
加载插件基础信息成功: '加载插件基础信息成功',
该接入点被使用中无法删除: '该接入点被使用中,无法删除',
序号: '序号',
+ GSEFile服务地址: 'GSE File地址',
+ GSEData服务地址: 'GSE Data地址',
+ GSECluster服务地址: 'GSE Cluster地址',
+ 跳过检测: '跳过检测',
+ 回调地址: '回调地址',
+ IPv4和IPv6不能混合使用: 'IPv4和IPv6不能混合使用',
// 自监控
后台服务器: '后台服务器',
@@ -567,6 +599,8 @@ export default {
内网回调: '内网回调',
请输入内网回调地址: '请输入内网回调地址',
请输入以backend结尾的URL地址: '请输入以/backend结尾的URL地址',
+ 内网URL: '内网URL',
+ 外网URL: '外网URL',
// 任务历史
机器数量: '(共 {0} 个)',
@@ -629,6 +663,7 @@ export default {
重装PROXY: '重装 Proxy',
重装AGENT: '重装 Agent',
升级PROXY: '升级 Proxy',
+ 确认Proxy目标版本: '确认Proxy目标版本',
升级AGENT: '升级 Agent',
已忽略IP无日志详情: '已忽略IP无日志详情',
已忽略信息提示: '{ip} 等 {num} 个 IP 已被系统自动忽略,可在执行状态筛选查看',
@@ -938,6 +973,8 @@ export default {
操作策略成功: '{0}策略成功!',
不能低于当前版本: '不能低于当前版本',
版本未处于正式状态: '版本未处于正式状态',
+ 已是目标版本: '已是目标版本,无需升级',
+ 当前版本: '当前版本,无需升级',
部分目标机器已被部署策略管控无法进行操作如需操作请调整对应的部署策略: '部分目标机器已被部署略管控,无法进行操作;如需操作请调整对应的部署策略',
停用策略dialogTitle: '停用策略:{0}',
停用策略dialogContentOnly: '仅停用策略,保留插件 {0}',
@@ -1115,6 +1152,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/i18n-setup.ts b/frontend/src/setup/i18n-setup.ts
index 8f31b9800..27bd78c67 100644
--- a/frontend/src/setup/i18n-setup.ts
+++ b/frontend/src/setup/i18n-setup.ts
@@ -71,3 +71,5 @@ export function loadLanguageAsync(lang: string) {
}
window.i18n = i18n;
+
+export default i18n;
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/config.ts b/frontend/src/store/modules/config.ts
index 8b0346edb..afe07ceb0 100644
--- a/frontend/src/store/modules/config.ts
+++ b/frontend/src/store/modules/config.ts
@@ -7,7 +7,7 @@ import { apIsUsing, listAp, retrieveAp, createAp, updateAp, testAp, deleteAp } f
import { retrieveGlobalSettings, jobSettings } from '@/api/modules/meta';
import { listApPermission } from '@/api/modules/permission';
import { healthz } from '@/api/modules/healthz';
-import { initIpProp } from '@/setup/ipv6';
+// import { initIpProp } from '@/setup/ipv6';
// eslint-disable-next-line new-cap
@Module({ name: 'config', namespaced: true })
@@ -39,15 +39,18 @@ export default class ConfigStore extends VuexModule {
zk_hosts: [
{ zk_ip: '', zk_port: '' },
],
- btfileserver: [
- { inner_ip: '', outer_ip: '' },
- ],
- dataserver: [
- { inner_ip: '', outer_ip: '' },
- ],
- taskserver: [
- { inner_ip: '', outer_ip: '' },
- ],
+ btfileserver: {
+ inner_ip_infos: [{ ip: '' }],
+ outer_ip_infos: [{ ip: '' }],
+ },
+ dataserver: {
+ inner_ip_infos: [{ ip: '' }],
+ outer_ip_infos: [{ ip: '' }],
+ },
+ taskserver: {
+ inner_ip_infos: [{ ip: '' }],
+ outer_ip_infos: [{ ip: '' }],
+ },
callback_url: '',
outer_callback_url: '',
package_inner_url: '',
@@ -68,11 +71,12 @@ export default class ConfigStore extends VuexModule {
// this.apDetail[`${key}`] = detail[key]
// } else {
if (['btfileserver', 'dataserver', 'taskserver'].includes(key)) {
- Vue.set(this.apDetail, key, detail[key].map((server) => {
- const copyServer = { ...server };
- initIpProp(copyServer, ['inner_ip', 'outer_ip']);
- return copyServer;
- }));
+ Vue.set(this.apDetail, key, { ...detail[key] });
+ // Vue.set(this.apDetail, key, detail[key].map((server) => {
+ // const copyServer = { ...server };
+ // initIpProp(copyServer, ['inner_ip', 'outer_ip']);
+ // return copyServer;
+ // }));
} else {
Vue.set(this.apDetail, key, detail[key]);
}
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/config/config.ts b/frontend/src/types/config/config.ts
index 204127bd5..77d6c29ed 100644
--- a/frontend/src/types/config/config.ts
+++ b/frontend/src/types/config/config.ts
@@ -1,10 +1,16 @@
/* eslint-disable camelcase */
+export type IServerProp = 'inner_ip_infos' | 'outer_ip_infos';
export interface IIpGroup {
- inner_ip: string
- outer_ip: string
- inner_ipv6?: string
- outer_ipv6?: string
+ inner_ip_infos: { ip: string }[]
+ outer_ip_infos: { ip: string }[]
+
+// inner_ip: string
+// outer_ip: string
+// inner_ipv6?: string
+// outer_ipv6?: string
}
+
+
export interface IZk {
zk_ip: string
zk_port: string
@@ -36,16 +42,16 @@ export interface IApBase {
zk_hosts: IZk[]
city_id: string
region_id: string
- btfileserver: IIpGroup[]
- dataserver: IIpGroup[]
- taskserver: IIpGroup[]
+ btfileserver: IIpGroup
+ dataserver: IIpGroup
+ taskserver: IIpGroup
description: string
callback_url: string
outer_callback_url: string
package_inner_url: string
package_outer_url: string
nginx_path: null | ''
- gse_version: 'V1' | 'V2' // ENABLE_AP_VERSION_MUTEX 开启时使用
+ gse_version?: 'V1' | 'V2' // ENABLE_AP_VERSION_MUTEX 开启时使用
}
export interface IApParams extends IApBase {
@@ -66,12 +72,15 @@ export interface IAp extends IApParams {
}
export interface IApExpand extends IAp {
- BtfileServer: IIpGroup[]
- DataServer: IIpGroup[]
- TaskServer: IIpGroup[]
- is_used?: boolean
- // is_default?: boolean
- collapse: boolean
+ BtfileServer: IIpGroup;
+ DataServer: IIpGroup;
+ TaskServer: IIpGroup;
+ is_used?: boolean;
+ // is_default?: boolean;
+ collapse: boolean;
+ linux: { name: TranslateResult; value: string; }[];
+ windows: { name: TranslateResult; value: string; }[];
+ view?: boolean;
}
export interface ITaskConfig {
@@ -86,9 +95,9 @@ export interface ITaskConfig {
// server 可用性
export interface IAvailable {
- btfileserver: IIpGroup[]
- dataserver: IIpGroup[]
- taskserver: IIpGroup[]
+ btfileserver: IIpGroup
+ dataserver: IIpGroup
+ taskserver: IIpGroup
package_inner_url: string
package_outer_url: string
callback_url?: 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">
+
+ {{ row.version | filterEmpty }}
+
+
+
@@ -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 f6044ae16..950a871dc 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 @@
+
+
+
+
+ {{ $t('nav_Agent包管理') }}
+
+
+
+ {{ $t('包上传') }}
+
+
+
+
+
+
+
+
+
{{ $t('快捷筛选') }}
+
+
+ {{ $t('维度') }}
+
+
+
+
+
+
+
+
+ -
+ All
+
+
+ {{ optItem.name }}
+ {{ optItem.count }}
+
+
+
+
+
+
+
+
+ updateTabActive(active)" />
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ {{ `${row.os}_${row.cpu_arch}` }}
+
+
+
+
+ handleEditTags(row, val)"
+ :options="options"
+ :value="row.formatTags" />
+
+
+
+
+
+
+ {{ row.hostNumber }}
+ 0
+
+
+
+
+
+ {{ row.is_ready ? $t('启用状态') : $t('停用状态')}}
+
+
+
+
+
+
+ {{ $t('启用操作')}}
+
+
+
+
{{ $t('确认停用该Agent包') }}
+
{{ $t('确认停用该Agent包Tip1', [row.pkg_name]) }}
+
{{ $t('确认停用该Agent包Tip2') }}
+
+
+
+ {{ $t('停用操作') }}
+
+
+
+
+
+
+
{{ $t('确认删除该Agent包') }}
+
{{ $t('确认删除该Agent包Tip1', [row.pkg_name]) }}
+
{{ $t('确认删除该Agent包Tip2') }}
+
+
+
+ {{ $t('删除') }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('存在同名 Agent 包') }}
+
+
+
{{ $t('存在同名Agent包,是否覆盖上传?') }}
+
{{ $t('包覆盖包名:', [pkgFileName]) }}
+
{{ $t('包覆盖MD5:', [pkgFileMd5]) }}
+
{{ $t('继续上传,将会覆盖当前平台同名的 Agent 包') }}
+
+
+
+
+
+
+ {{ $t('上传成功(存在同名 Agent 包,确认覆盖上传)') }}
+
+
+
+
+
+
+
+
{{ $t('结果预览') }}
+
+
+
+ {{ row.pkg_name }}
+
+
+
+
+ {{ `${row.os}_${row.cpu_arch}` }}
+
+
+
+
+ {{ pkgType[row.project] }}
+
+
+
+
+
+
+
+
+
{{ $t('描述') }}
+
+
{{ pkgDesc }}
+
+
+
+
{{ $t('解析错误') }}
+
{{ pkgDesc }}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/cloud/cloud-channel/channel-edit.vue b/frontend/src/views/cloud/cloud-channel/channel-edit.vue
index 3d77adf84..c8bfacc36 100644
--- a/frontend/src/views/cloud/cloud-channel/channel-edit.vue
+++ b/frontend/src/views/cloud/cloud-channel/channel-edit.vue
@@ -112,9 +112,9 @@ export default class ChannelEdit extends Vue {
private btnLoading = false;
private channelForm: Dictionary = {};
private labelMap: Dictionary = {
- btfileserver: 'Btfileserver',
- dataserver: 'Dataserver',
- taskserver: 'Taskserver',
+ btfileserver: this.$t('GSE File服务地址'),
+ dataserver: this.$t('GSE Data服务地址'),
+ taskserver: this.$t('GSE Cluster服务地址'),
};
private showAdvancedConfig = false;
private edited = false;
diff --git a/frontend/src/views/cloud/cloud-channel/channel-table.vue b/frontend/src/views/cloud/cloud-channel/channel-table.vue
index 851851562..46057d779 100644
--- a/frontend/src/views/cloud/cloud-channel/channel-table.vue
+++ b/frontend/src/views/cloud/cloud-channel/channel-table.vue
@@ -42,13 +42,19 @@ import { CloudStore } from '@/store';
export default class ChannelTable extends Vue {
@Prop({ default: () => ({}), type: Object }) private readonly channel!: Dictionary;
+ private label: Dictionary = {
+ btfileserver: this.$t('GSE File服务地址'),
+ dataserver: this.$t('GSE Data服务地址'),
+ taskserver: this.$t('GSE Cluster服务地址'),
+ };
+
private get channelServerKeys() {
return CloudStore.channelServerKeys;
}
private get serverList() {
const { upstream_servers: servers = {} } = this.channel;
return this.channelServerKeys.map(key => ({
- key,
+ key: this.label[key],
value: servers[key] ? servers[key].join(';') : '',
}));
}
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/global-config/gse-config/access-point-table.vue b/frontend/src/views/global-config/gse-config/access-point-table.vue
index fb86bbff6..86d5e2dc0 100644
--- a/frontend/src/views/global-config/gse-config/access-point-table.vue
+++ b/frontend/src/views/global-config/gse-config/access-point-table.vue
@@ -1,10 +1,9 @@
-
+
- |
- |
+ |
|
|
@@ -12,60 +11,66 @@
- {{ $t('Server信息') }} |
- {{ $t('地域信息') }} |
- {{ $t('区域') }} |
+ {{ $t('Server信息') }} |
+
+
+ {{ $t('区域') }} |
{{ formData.region_id }} |
- {{ $t('城市') }} |
+ {{ $t('城市') }} |
{{ formData.city_id }} |
- Zookeeper |
- {{ $t('集群地址') }} |
+ {{ $t('Zookeeper集群地址') }} |
{{ zookeeper }} |
-
-
- {{ `${str} ${ index + 1 }` }} |
- IP |
+
+
+ {{ server.name }} |
+ {{ $t('内网IP') }} |
+
+ {{ formData[server.id]?.inner_ip_infos?.map(item => item.ip).join('; ') || '--' }}
+ |
+
+
+ {{ $t('外网IP') }} |
- {{ `${ $t('内网') + (server.inner_ip || server.inner_ipv6) }; ${
- $t('外网') + (server.outer_ip || server.outer_ipv6) }`
- }}
+ {{ formData[server.id]?.outer_ip_infos?.map(item => item.ip).join('; ') || '--' }}
|
- {{ $t('外网回调') }} |
- URL |
-
- {{ formData.outer_callback_url | filterEmpty }}
- |
+ {{ $t('回调地址') }} |
+ {{ $t('内网URL') }} |
+ {{ formData.callback_url || '--' }} |
+
+
+ {{ $t('外网URL') }} |
+ {{ formData.outer_callback_url || '--' }} |
- {{ $t('内网回调') }} |
- URL |
-
- {{ formData.callback_url | filterEmpty }}
- |
+ {{ $t('Agent安装包信息') }} |
+ {{ $t('内网URL') }} |
+ {{ formData.package_inner_url || '--' }} |
- {{ $t('Agent安装包') }} |
- URL |
-
- {{ `${ $t('内网') + formData.package_inner_url }; ${ $t('外网') + formData.package_outer_url }` }}
- |
+ {{ $t('外网URL') }} |
+ {{ formData.package_outer_url || '--' }} |
{{ $t('服务器目录') }} |
- {{ formData.nginx_path | filterEmpty }} |
+ {{ formData.nginx_path || '--' }} |
+
+
+
+
+
+ {{ $t('Agent信息') }} |
-
- {{ $t('Agent信息') }} |
+
Linux |
{{ path.name }} |
{{ path.value }} |
@@ -79,11 +84,16 @@
{{ path.value }} |
-
+
+
+
+
+
+ {{ $t('Proxy信息') }} |
+
- {{ $t('Proxy信息') }} |
- {{ $t('安装包') }} |
+ {{ $t('Proxy安装包') }} |
{{ formData.proxy_package.join('; ') }} |
@@ -92,80 +102,113 @@
-
diff --git a/frontend/src/views/global-config/gse-config/index.vue b/frontend/src/views/global-config/gse-config/index.vue
index c1d8461b3..14a679d58 100644
--- a/frontend/src/views/global-config/gse-config/index.vue
+++ b/frontend/src/views/global-config/gse-config/index.vue
@@ -84,17 +84,17 @@
-
>> .access-point-table tr:last-child td {
- border-bottom: 0;
- }
}
diff --git a/frontend/src/views/global-config/gse-config/set-access-point/apFormConfig.ts b/frontend/src/views/global-config/gse-config/set-access-point/apFormConfig.ts
index 4382a25db..712ecd799 100644
--- a/frontend/src/views/global-config/gse-config/set-access-point/apFormConfig.ts
+++ b/frontend/src/views/global-config/gse-config/set-access-point/apFormConfig.ts
@@ -8,6 +8,24 @@ export const stepHost = [
ruleName: 'name',
placeholder: window.i18n.t('用户创建的接入点'),
},
+ {
+ label: window.i18n.t('接入点版本'),
+ key: 'gse_version',
+ required: true,
+ ruleName: 'gse_version',
+ desc: window.i18n.t('接入点描述'),
+ type: 'select',
+ options: [
+ {
+ id: 'V1',
+ name: 'V1'
+ },
+ {
+ id: 'V2',
+ name: 'V2'
+ },
+ ],
+ },
{
label: window.i18n.t('接入点说明'),
key: 'description',
@@ -45,42 +63,56 @@ export const stepHost = [
type: 'zk',
},
{
- label: window.i18n.t('外网回调地址'),
- key: 'outer_callback_url',
- ruleName: 'callback',
- placeholder: window.i18n.t('请输入外网回调地址'),
- extCls: 'mt20',
+ type: 'url',
+ items: [
+ {
+ label: window.i18n.t('回调地址'),
+ prepend: window.i18n.t('内网URL'),
+ key: 'callback_url',
+ ruleName: 'callback',
+ placeholder: window.i18n.t('请输入内网回调地址'),
+ extCls: 'mt20',
+ },
+ {
+ label: '',
+ prepend: window.i18n.t('外网URL'),
+ key: 'outer_callback_url',
+ ruleName: 'callback',
+ placeholder: window.i18n.t('请输入外网回调地址'),
+ extCls: 'mt10',
+ },
+ ],
},
{
- label: window.i18n.t('内网回调地址'),
- key: 'callback_url',
- ruleName: 'callback',
- placeholder: window.i18n.t('请输入内网回调地址'),
- extCls: 'mt20',
+ type: 'url',
+ items: [
+ {
+ label: window.i18n.t('Agent安装包地址'),
+ prepend: window.i18n.t('内网URL'),
+ required: true,
+ key: 'package_inner_url',
+ ruleName: 'url',
+ placeholder: window.i18n.t('请输入内网下载URL'),
+ extCls: 'mt20',
+ },
+ {
+ label: '',
+ prepend: window.i18n.t('外网URL'),
+ required: true,
+ key: 'package_outer_url',
+ ruleName: 'url',
+ placeholder: window.i18n.t('请输入外网下载URL'),
+ extCls: 'mt10 hide-require',
+ },
+ ],
},
{
label: window.i18n.t('Agent包服务器目录'),
key: 'nginx_path',
ruleName: 'nginxPath',
placeholder: window.i18n.t('请输入服务器目录'),
- extCls: 'mt40',
- },
- {
- label: window.i18n.t('Agent包URL'),
- required: true,
- key: 'package_inner_url',
- ruleName: 'url',
- placeholder: window.i18n.t('请输入内网下载URL'),
extCls: 'mt20',
},
- {
- label: '',
- required: true,
- key: 'package_outer_url',
- ruleName: 'url',
- placeholder: window.i18n.t('请输入外网下载URL'),
- extCls: 'mt10',
- },
// 可用性测试
{
type: 'usability',
@@ -117,12 +149,12 @@ export const apAgentInfo = [
];
// 目录名可以包含但不相等,所以末尾加了 /, 校验的时候给值也需要加上 /
-const linuxNotInclude = [
+export const linuxNotInclude = [
'/etc/', '/root/', '/boot/', '/dev/', '/sys/', '/tmp/', '/var/', '/usr/lib/',
'/usr/lib64/', '/usr/include/', '/usr/local/etc/', '/usr/local/sa/', '/usr/local/lib/',
'/usr/local/lib64/', '/usr/local/bin/', '/usr/local/libexec/', '/usr/local/sbin/',
];
-const linuxNotIncludeError = [
+export const linuxNotIncludeError = [
'/etc', '/root', '/boot', '/dev', '/sys', '/tmp', '/var', '/usr/lib',
'/usr/lib64', '/usr/include', '/usr/local/etc', '/usr/local/sa', '/usr/local/lib',
'/usr/local/lib64', '/usr/local/bin', '/usr/local/libexec', '/usr/local/sbin',
diff --git a/frontend/src/views/global-config/gse-config/set-access-point/host-td-input.vue b/frontend/src/views/global-config/gse-config/set-access-point/host-td-input.vue
new file mode 100644
index 000000000..640416977
--- /dev/null
+++ b/frontend/src/views/global-config/gse-config/set-access-point/host-td-input.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+ |
+
+
+
diff --git a/frontend/src/views/global-config/gse-config/set-access-point/host-td-operate.vue b/frontend/src/views/global-config/gse-config/set-access-point/host-td-operate.vue
new file mode 100644
index 000000000..f1eb402cd
--- /dev/null
+++ b/frontend/src/views/global-config/gse-config/set-access-point/host-td-operate.vue
@@ -0,0 +1,61 @@
+
+
+
+ cellIconClick('add')">
+
+ cellIconClick('delete')">
+
+
+ |
+
+
+
+
diff --git a/frontend/src/views/global-config/gse-config/set-access-point/step-form-table.vue b/frontend/src/views/global-config/gse-config/set-access-point/step-form-table.vue
index b8320dd3e..b9d28b9f5 100644
--- a/frontend/src/views/global-config/gse-config/set-access-point/step-form-table.vue
+++ b/frontend/src/views/global-config/gse-config/set-access-point/step-form-table.vue
@@ -76,7 +76,7 @@ export default class SetupFormTable extends Vue {
text-align: left;
font-weight: 400;
&:first-child {
- padding-left: 16px;
+ text-align: center;
}
}
}
diff --git a/frontend/src/views/global-config/gse-config/set-access-point/step-host.vue b/frontend/src/views/global-config/gse-config/set-access-point/step-host.vue
index 44db476ca..bdea2b559 100644
--- a/frontend/src/views/global-config/gse-config/set-access-point/step-host.vue
+++ b/frontend/src/views/global-config/gse-config/set-access-point/step-host.vue
@@ -1,56 +1,96 @@
-
-
+
+
+
+
+ class="bk-form-item ip-related-item clearfix mb24">