From 87d104d27b4a4fb761def9ef1ce730312b58dfb1 Mon Sep 17 00:00:00 2001 From: wujun <897415845@qq.com> Date: Tue, 13 Jul 2021 10:42:10 +0800 Subject: [PATCH] HARVESTER: feat: setting & sshKey page --- assets/translations/en-us.yaml | 110 +++++++++ assets/translations/zh-hans.yaml | 67 +++++- components/LabelValue.vue | 42 ++++ components/Tip.vue | 38 ++++ components/nav/TopLevelMenu.vue | 22 ++ config/product/virtual.js | 213 ++++++++++++++++++ config/settings.js | 41 ++++ config/table-headers.js | 37 +++ detail/harvesterhci.io.keypair.vue | 52 +++++ edit/harvesterhci.io.keypair.vue | 112 +++++++++ .../harvesterhci.io.setting/backup-target.vue | 173 ++++++++++++++ edit/harvesterhci.io.setting/index.vue | 191 ++++++++++++++++ .../index.vue | 19 ++ .../vlan.vue | 69 ++++++ list/harvesterhci.io.setting.vue | 196 ++++++++++++++++ models/harvesterhci.io.setting.js | 125 ++++++++++ .../network.harvesterhci.io.clusternetwork.js | 88 ++++++++ package.json | 1 + store/index.js | 18 +- store/type-map.js | 22 +- utils/dynamic-importer.js | 8 + yarn.lock | 13 ++ 22 files changed, 1648 insertions(+), 9 deletions(-) create mode 100644 components/LabelValue.vue create mode 100644 components/Tip.vue create mode 100644 config/product/virtual.js create mode 100644 detail/harvesterhci.io.keypair.vue create mode 100644 edit/harvesterhci.io.keypair.vue create mode 100644 edit/harvesterhci.io.setting/backup-target.vue create mode 100644 edit/harvesterhci.io.setting/index.vue create mode 100644 edit/network.harvesterhci.io.clusternetwork/index.vue create mode 100644 edit/network.harvesterhci.io.clusternetwork/vlan.vue create mode 100644 list/harvesterhci.io.setting.vue create mode 100644 models/harvesterhci.io.setting.js create mode 100644 models/network.harvesterhci.io.clusternetwork.js diff --git a/assets/translations/en-us.yaml b/assets/translations/en-us.yaml index ab5b7e12d5..81bfdabb50 100644 --- a/assets/translations/en-us.yaml +++ b/assets/translations/en-us.yaml @@ -4041,6 +4041,8 @@ tableHeaders: value: Value version: Version weight: Weight + progress: Progress + fingerprint: Fingerprint target: router: @@ -4656,6 +4658,42 @@ workload: tip: The duration the pod needs to terminate successfully. title: Upgrading +harvester: + dashboard: + label: Dashboard + host: + label: Hosts + virtualMachine: + label: Virtual Machines + volume: + label: Volumes + image: + label: Images + vmTemplate: + label: Templates + backup: + label: Backups + network: + label: Networks + sshKey: + label: SSH Keys + keypair: SSH Key + tabs: + basics: Basics + + setting: + label: Settings + validation: + physicalNIC: DefaultPhysicalNIC + placeholder: + accessKeyId: specify your access key id + secretAccessKey: specify your secret access key + cert: upload a self-signed SSL certificate + vlanChangeTip: The newly modified default physical NIC only applies to newly added nodes, not existing ones + defaultPhysicalNIC: Default Physical NIC + + cloudTemplate: + label: Cloud Config Templates ############################## # Model Properties @@ -4951,6 +4989,62 @@ typeLabel: one { Workload } other { Workloads } } + kubevirt.io.virtualmachine: |- + {count, plural, + one { Virtual Machine } + other { Virtual Machines } + } + harvesterhci.io.virtualmachineimage: |- + {count, plural, + one { Image } + other { Images } + } + harvesterhci.io.keypair: |- + {count, plural, + one { SSH Key } + other { SSH Keys } + } + host: |- + {count, plural, + one { Host } + other { Hosts } + } + configmap: |- + {count, plural, + one { Cloud Config Template } + other { Cloud Config Templates } + } + k8s.cni.cncf.io.networkattachmentdefinition: |- + {count, plural, + one { Network } + other { Networks } + } + cdi.kubevirt.io.datavolume: |- + {count, plural, + one { Volume } + other { Volumes } + } + harvesterhci.io.user: |- + {count, plural, + one { User } + other { Users } + } + harvesterhci.io.setting: |- + {count, plural, + one { Setting } + other { Settings } + } + harvesterhci.io.virtualmachinetemplateversion: |- + {count, plural, + one { Template } + other { Templates } + } + harvesterhci.io.virtualmachinebackup: |- + {count, plural, + one { Backup } + other { Backups } + } + action: clone: Clone @@ -5043,6 +5137,14 @@ advancedSettings: 'ui-banners': 'Classification banner is used to display a custom fixed banner in the header, footer, or both.' 'ui-default-landing': 'The default page users land on after login.' 'brand': Folder name for an alternative theme defined in '/assets/brand' + 'vlan': Default physical NIC name of the VLAN network. + 'api-ui-source': Config how to load the API and UI source. + 'backup-target': Custom back target to store VM backups. + 'log-level': Configure Harvester server log level. Default to info. + 'rancher-enabled': Specify whether to display the Rancher UI navigation button or not. + 'server-version': Harvester server version. + 'upgrade-checker-enabled': Specify whether to enable Harvester upgrade check or not. Default is true. + 'upgrade-checker-url': Default Harvester upgrade check url. Only used when the upgrade-checker-enabled is equal to true. editHelp: 'ui-banners': This setting takes a JSON object containing 3 root parameters; banner, showHeader, showFooter. banner is an object containing; textColor, background, and text, where textColor and background are any valid CSS color value. enum: @@ -5057,6 +5159,14 @@ advancedSettings: dynamic: Dynamic true: Local false: Remote + 'api-ui-source': + auto: 'Auto' + bundled: 'Bundled' + external: 'External' + 'log-level': + info: Info + debug: Debug + trace: Trace featureFlags: label: Feature Flags diff --git a/assets/translations/zh-hans.yaml b/assets/translations/zh-hans.yaml index 246c5abff9..925b67f49d 100644 --- a/assets/translations/zh-hans.yaml +++ b/assets/translations/zh-hans.yaml @@ -3142,6 +3142,8 @@ tableHeaders: value: 值 version: 版本号 weight: 权重 + progress: 进度 + fingerprint: 唯一标识 target: router: @@ -3743,6 +3745,42 @@ workload: tip: 杀死 Pod 前所需的等待时间 title: 升级中 +harvester: + dashboard: + label: 概览 + host: + label: 主机 + virtualMachine: + label: 虚拟机 + volume: + label: 卷 + image: + label: 镜像 + vmTemplate: + label: 虚拟机模板 + backup: + label: 备份 + network: + label: 网络 + sshKey: + label: SSH 秘钥 + keypair: SSH 公钥 + tabs: + basics: 基本信息 + + setting: + label: 设置 + validation: + physicalNIC: DefaultPhysicalNIC + placeholder: + accessKeyId: 指定您的访问密钥ID + secretAccessKey: 指定您的私密访问密钥 + cert: 上传自签名SSL证书 + vlanChangeTip: 新修改的默认物理网卡仅适用于新增节点,不适用于现有节点 + defaultPhysicalNIC: 默认物理网卡 + + cloudTemplate: + label: Cloud 配置模板 ############################## # Model Properties @@ -4001,6 +4039,17 @@ typeLabel: one { API密钥 } other { API密钥 } } + kubevirt.io.virtualmachine: 虚拟机 + harvesterhci.io.virtualmachineimage: 镜像 + harvesterhci.io.keypair: SSH 密钥 + cloudTemplate: Cloud 配置模板 + harvesterhci.io.virtualmachinetemplateversion: 虚拟机模板 + k8s.cni.cncf.io.networkattachmentdefinition: 网络 + cdi.kubevirt.io.datavolume: 卷 + harvesterhci.io.user: 用户 + harvesterhci.io.setting: 设置 + host: 主机 + harvesterhci.io.virtualmachinebackup: 备份 action: clone: 克隆 @@ -4087,9 +4136,25 @@ advancedSettings: 'rke-metadata-config': '配置RKE元数据刷新参数。' 'ui-banners': '分类横幅是用来在页眉、页脚或两者中显示一个自定义的固定横幅。' 'ui-default-landing': '用户在登录后登陆的默认页面。' + 'vlan': VLAN网络的默认物理NIC名称. + 'api-ui-source': 配置如何加载API和UI资源. + 'backup-target': 自定义备份目标用于存储VM备份. + 'log-level': 配置Harvester服务器日志级别. 默认为信息 + 'rancher-enabled': 指定是否显示Rancher UI导航按钮. + 'server-version': Harvester服务器版本. + 'upgrade-checker-enabled': 指定是否启用Harvester升级检查. 默认为true. + 'upgrade-checker-url': 默认的Harvester升级检查地址。 仅在 upgrade-checker-enabled 等于true时使用。 editHelp: 'ui-banners': 这个设置需要一个JSON对象,包含3个根参数;banner, showHeader, showFooterbanner是一个包含;textColor, background, 和text的对象,其中textColorbackground是任何有效的CSS颜色值。 - #enum: + enum: + 'api-ui-source': + auto: 'Auto' + bundled: 'Bundled' + external: 'External' + 'log-level': + info: Info + debug: Debug + trace: Trace #'ui-default-landing': # ember: Cluster Manager #vue: Cluster Explorer diff --git a/components/LabelValue.vue b/components/LabelValue.vue new file mode 100644 index 0000000000..ea06c09fbe --- /dev/null +++ b/components/LabelValue.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/components/Tip.vue b/components/Tip.vue new file mode 100644 index 0000000000..959e608ce5 --- /dev/null +++ b/components/Tip.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/components/nav/TopLevelMenu.vue b/components/nav/TopLevelMenu.vue index 11f8d15554..2eb4bcd0c3 100644 --- a/components/nav/TopLevelMenu.vue +++ b/components/nav/TopLevelMenu.vue @@ -79,6 +79,28 @@ export default { return sorted; }, + harvesters() { + const all = this.$store.getters['management/all'](MANAGEMENT.CLUSTER); + let out = all.map((x) => { + return { + id: x.id, + label: x.nameDisplay, + ready: x.isReady, + osLogo: x.providerOsLogo, + logo: x.providerLogo, + isLocal: x.isLocal + }; + }); + + if (this.clusterFilter.length > 0) { + out = out.filter(item => item.label.indexOf(this.clusterFilter) === 0); + } + + const sorted = sortBy(out, ['ready:desc', 'label']); + + return sorted; + }, + dev: mapPref(DEV), maxClustersToShow: mapPref(MENU_MAX_CLUSTERS), diff --git a/config/product/virtual.js b/config/product/virtual.js new file mode 100644 index 0000000000..ec53265730 --- /dev/null +++ b/config/product/virtual.js @@ -0,0 +1,213 @@ +import { HCI, NODE, CONFIG_MAP } from '@/config/types'; +import { + STATE, NAME as NAME_COL, AGE, NAMESPACE, IMAGE_DOWNLOAD_SIZE, + FINGERPRINT +} from '@/config/table-headers'; + +import { DSL } from '@/store/type-map'; + +export const NAME = 'virtual'; + +const TEMPLATE = HCI.VM_VERSION; +const CLOUD_TEMPLATE = 'cloudTemplate'; +const HOST = 'host'; + +export function init(store) { + const { + product, + basicType, + headers, + configureType, + virtualType, + } = DSL(store, NAME); + + product({ + inStore: 'virtual', + removable: false, + showNamespaceFilter: true, + showClusterSwitcher: false, + icon: 'compass' + }); + + basicType(['virtual-dashboard']); + virtualType({ + ifHaveType: NODE, + label: store.getters['i18n/t']('harvester.dashboard.label'), + group: 'Root', + labelDisplay: 'harvester.nav.dashboard', + namespaced: true, + name: 'virtual-dashboard', + weight: 500, + route: { name: 'c-cluster-virtual' }, + exact: true, + }); + + configureType(HOST, { + location: { + name: 'c-cluster-product-resource', + params: { resource: HOST }, + }, + resource: NODE, + useCustomInImport: true + }); + + configureType(HOST, { isCreatable: false, isEditable: true }); + basicType([HOST]); + virtualType({ + ifHaveType: NODE, + label: store.getters['i18n/t']('harvester.host.label'), + group: 'Root', + labelDisplay: 'harvester.typeLabel.host', + name: HOST, + namespaced: true, + weight: 399, + route: { + name: 'c-cluster-product-resource', + params: { resource: HOST } + }, + exact: false, + }); + + basicType([HCI.VM]); + virtualType({ + label: store.getters['i18n/t']('harvester.virtualMachine.label'), + group: 'root', + name: HCI.VM, + namespaced: true, + weight: 299, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.VM } + }, + exact: false, + }); + + basicType([HCI.DATA_VOLUME]); + virtualType({ + label: store.getters['i18n/t']('harvester.volume.label'), + group: 'root', + name: HCI.DATA_VOLUME, + namespaced: true, + weight: 199, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.DATA_VOLUME } + }, + exact: false, + }); + + basicType([HCI.IMAGE]); + headers(HCI.IMAGE, [STATE, NAME_COL, NAMESPACE, /* IMAGE_PROGRESS, IMAGE_MESSAGE, */IMAGE_DOWNLOAD_SIZE, AGE]); + virtualType({ + label: store.getters['i18n/t']('harvester.image.label'), + group: 'root', + name: HCI.IMAGE, + namespaced: true, + weight: 99, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.IMAGE } + }, + exact: false, + }); + + basicType([ + TEMPLATE, + HCI.NETWORK_ATTACHMENT, + HCI.BACKUP, + HCI.SSH, + CLOUD_TEMPLATE, + HCI.SETTING + ], 'advanced'); + + configureType(HCI.CLUSTER_NETWORK, { realResource: HCI.SETTING, showState: false }); + virtualType({ + label: store.getters['i18n/t']('harvester.vmTemplate.label'), + group: 'root', + name: TEMPLATE, + namespaced: true, + weight: 289, + route: { + name: 'c-cluster-product-resource', + params: { resource: TEMPLATE } + }, + exact: false, + }); + + configureType(HCI.BACKUP, { + DisableEditInDetail: true, + showListMasthead: false + }); + virtualType({ + label: store.getters['i18n/t']('harvester.backup.label'), + name: HCI.BACKUP, + namespaced: true, + weight: 200, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.BACKUP } + }, + exact: false, + }); + + configureType(HCI.NETWORK_ATTACHMENT, { isEditable: false, showState: false }); + virtualType({ + label: store.getters['i18n/t']('harvester.network.label'), + name: HCI.NETWORK_ATTACHMENT, + namespaced: true, + weight: 189, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.NETWORK_ATTACHMENT } + }, + exact: false, + }); + + headers(HCI.SSH, [STATE, NAME_COL, NAMESPACE, FINGERPRINT, AGE]); + virtualType({ + label: store.getters['i18n/t']('harvester.sshKey.label'), + name: HCI.SSH, + namespaced: true, + weight: 170, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.SSH } + }, + exact: false, + }); + + configureType(CLOUD_TEMPLATE, { + location: { + name: 'c-cluster-product-resource', + params: { resource: CLOUD_TEMPLATE }, + }, + resource: CONFIG_MAP, + useCustomInImport: true + }); + virtualType({ + label: store.getters['i18n/t']('harvester.cloudTemplate.label'), + name: CLOUD_TEMPLATE, + namespaced: true, + weight: 87, + route: { + name: 'c-cluster-product-resource', + params: { resource: CLOUD_TEMPLATE } + }, + exact: false, + }); + + // settings + configureType(HCI.SETTING, { isCreatable: false }); + virtualType({ + ifHaveType: HCI.SETTING, + label: store.getters['i18n/t']('harvester.setting.label'), + name: HCI.SETTING, + namespaced: true, + weight: -1, + route: { + name: 'c-cluster-product-resource', + params: { resource: HCI.SETTING } + }, + exact: false + }); +} diff --git a/config/settings.js b/config/settings.js index ab4d1cb175..9f9915d493 100644 --- a/config/settings.js +++ b/config/settings.js @@ -86,6 +86,47 @@ export const ALLOWED_SETTINGS = { }, }; +// harvester Settings ID +const HCI_SETTING = { + API_UI_SOURCE: 'api-ui-source', + AUTH_TOKEN_MAX_TTL_MINUTES: 'auth-token-max-ttl-minutes', + BACKUP_TARGET: 'backup-target', + LOG_LEVEL: 'log-level', + RANCHER_ENABLED: 'rancher-enabled', + SERVER_URL: 'server-url', + SERVER_VERSION: 'server-version', + UI_INDEX: 'ui-index', + UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled', + UPGRADE_CHECKER_URL: 'upgrade-checker-url', + VLAN: 'harvester-system/vlan', + // DEFAULT_STORAGE_CLASS: 'default-storage-class' +}; + +export const HCI_ALLOWED_SETTINGS = { + [HCI_SETTING.API_UI_SOURCE]: { + kind: 'enum', + options: ['auto', 'external', 'bundled'] + }, + [HCI_SETTING.AUTH_TOKEN_MAX_TTL_MINUTES]: {}, + [HCI_SETTING.BACKUP_TARGET]: { + kind: 'json', from: 'import', disableReset: true + }, + [HCI_SETTING.LOG_LEVEL]: { + kind: 'enum', + options: ['info', 'debug', 'trace'] + }, + [HCI_SETTING.RANCHER_ENABLED]: { kind: 'boolean' }, + [HCI_SETTING.SERVER_VERSION]: {}, + [HCI_SETTING.SERVER_URL]: {}, + [HCI_SETTING.UI_INDEX]: { kind: 'url' }, + [HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' }, + [HCI_SETTING.UPGRADE_CHECKER_URL]: { kind: 'url' }, + [HCI_SETTING.VLAN]: { + kind: 'custom', from: 'import', alias: 'vlan' + }, + // [HCI_SETTING.DEFAULT_STORAGE_CLASS]: {} +}; + export const fetchOrCreateSetting = async(store, id, val, save = true) => { let setting; diff --git a/config/table-headers.js b/config/table-headers.js index 2c647e6825..dad87d27db 100644 --- a/config/table-headers.js +++ b/config/table-headers.js @@ -831,3 +831,40 @@ export const STATE_NORMAN = { default: 'unknown', formatter: 'BadgeStateFormatter', }; + +/** + * Harvester + */ + +// image +export const IMAGE_DOWNLOAD_SIZE = { + name: 'downloadedBytes', + labelKey: 'tableHeaders.size', + value: 'status.size', + sort: 'status.size', + formatter: 'ByteFormat', + width: 120 +}; + +export const IMAGE_PROGRESS = { + name: 'Uploaded', + labelKey: 'tableHeaders.progress', + value: 'status.progress', + sort: 'status.progress', + formatter: 'ImagePercentageBar', +}; + +export const IMAGE_MESSAGE = { + name: 'Message', + labelKey: 'tableHeaders.message', + value: 'status.conditions', + sort: 'status.conditions', + formatter: 'ImageMessage', +}; + +// SSH keys +export const FINGERPRINT = { + name: 'Fingerprint', + labelKey: 'tableHeaders.fingerprint', + value: 'status.fingerPrint', +}; diff --git a/detail/harvesterhci.io.keypair.vue b/detail/harvesterhci.io.keypair.vue new file mode 100644 index 0000000000..5dd80a8bb7 --- /dev/null +++ b/detail/harvesterhci.io.keypair.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/edit/harvesterhci.io.keypair.vue b/edit/harvesterhci.io.keypair.vue new file mode 100644 index 0000000000..a82f78e653 --- /dev/null +++ b/edit/harvesterhci.io.keypair.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/edit/harvesterhci.io.setting/backup-target.vue b/edit/harvesterhci.io.setting/backup-target.vue new file mode 100644 index 0000000000..ba0c12c090 --- /dev/null +++ b/edit/harvesterhci.io.setting/backup-target.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/edit/harvesterhci.io.setting/index.vue b/edit/harvesterhci.io.setting/index.vue new file mode 100644 index 0000000000..2b95e82228 --- /dev/null +++ b/edit/harvesterhci.io.setting/index.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/edit/network.harvesterhci.io.clusternetwork/index.vue b/edit/network.harvesterhci.io.clusternetwork/index.vue new file mode 100644 index 0000000000..662469588b --- /dev/null +++ b/edit/network.harvesterhci.io.clusternetwork/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/edit/network.harvesterhci.io.clusternetwork/vlan.vue b/edit/network.harvesterhci.io.clusternetwork/vlan.vue new file mode 100644 index 0000000000..1055e39ea8 --- /dev/null +++ b/edit/network.harvesterhci.io.clusternetwork/vlan.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/list/harvesterhci.io.setting.vue b/list/harvesterhci.io.setting.vue new file mode 100644 index 0000000000..e8af70aebb --- /dev/null +++ b/list/harvesterhci.io.setting.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/models/harvesterhci.io.setting.js b/models/harvesterhci.io.setting.js new file mode 100644 index 0000000000..4619facb9f --- /dev/null +++ b/models/harvesterhci.io.setting.js @@ -0,0 +1,125 @@ +import { findBy } from '@/utils/array'; +import { HCI_ALLOWED_SETTINGS } from '@/config/settings'; + +export default { + _availableActions() { + const out = this._standardActions; + + const toFilter = ['cloneYaml', 'download', 'goToEditYaml', 'goToViewYaml', 'goToViewConfig', 'promptRemove']; + + const actions = out.map((O) => { + const enabled = toFilter.includes(O.action) ? false : O.enabled; + const bulkable = toFilter.includes(O.action) ? false : O?.bulkable; + + return { + ...O, + enabled, + bulkable + }; + }); + + const editAction = actions.find(action => action.action === 'goToEdit'); + + if (editAction) { + editAction.label = this.t('advancedSettings.edit.label'); + } + + return actions; + }, + + hasCustomized() { + const setting = HCI_ALLOWED_SETTINGS[this.id]; + const readonly = !!setting.readOnly; + + return !readonly && this.value && this.value !== this.default; + }, + + backupTagetetIsEmpty() { + return !this.value; + }, + + errMessage() { + if (this.metadata?.state?.error === true) { + return this.metadata.state.message; + } else { + return false; + } + }, + + configuredCondition() { + return findBy((this?.status?.conditions || []), 'type', 'configured') || {}; + }, + + valueOrDefaultValue() { + return this.value || this.default; + }, + + upgradeableVersion() { + const value = this.value || ''; + + if (!value) { + return []; + } + + return value.split(',').sort((a, b) => { + return a > b ? -1 : 1; + }).map( (V) => { + return { + label: V, + value: V + }; + }); + }, + + currentVersion() { + return this.value || ''; + }, + + displayValue() { // Select the field you want to display + if (this.id === 'backup-target') { + return this.parseValue?.endpoint || ' '; + } + + return null; + }, + + parseValue() { + let parseDefaultValue = {}; + + try { + parseDefaultValue = JSON.parse(this.value); + } catch (err) { + parseDefaultValue = JSON.parse(this.default); + } + + return parseDefaultValue; + }, + + isS3() { + return this.parseValue.type === 's3'; + }, + + isNFS() { + return this.parseValue.type === 'nfs'; + }, + + customValidationRules() { + const id = this.id; + + const out = []; + + switch (id) { + case 'backup-target': + out.push( { + nullable: false, + path: 'value', + required: true, + type: 'string', + validators: ['backupTarget'], + }); + break; + } + + return out; + }, +}; diff --git a/models/network.harvesterhci.io.clusternetwork.js b/models/network.harvesterhci.io.clusternetwork.js new file mode 100644 index 0000000000..52584db31c --- /dev/null +++ b/models/network.harvesterhci.io.clusternetwork.js @@ -0,0 +1,88 @@ +import { HCI } from '@/config/types'; +import { clone } from '@/utils/object'; + +export default { + availableActions() { + let out = this._standardActions; + const toFilter = ['goToClone', 'cloneYaml', 'goToViewYaml', 'goToViewConfig', 'promptRemove', 'goToEditYaml', 'download']; + + out = out.filter((action) => { + if (!toFilter.includes(action.action)) { + return action; + } + }); + + const editAction = out.find(action => action.action === 'goToEdit'); + + if (editAction) { + editAction.label = this.t('advancedSettings.edit.label'); + } + + return out; + }, + + doneOverride() { + const detailLocation = clone(this.listLocation); + + detailLocation.params.resource = HCI.SETTING; + + return detailLocation; + }, + + // vlan + canUseVlan() { + return this.isVlanOpen && this.defaultPhysicalNic.length > 0; + }, + + canReset() { + return true; + }, + + defaultValue() { + this.enable = false; + if (this.config) { // initializing: the config value is empty + this.config.defaultPhysicalNIC = ''; + } + + return this; + }, + + isVlanOpen() { + return !!this.enable; + }, + + defaultPhysicalNic() { + return this?.config?.defaultPhysicalNIC; + }, + + displayValue() { // Select the field you want to display + if (this.enable) { + return this?.config?.defaultPhysicalNIC || ''; + } + }, + + customValue() { + return this.enable ? this?.config?.defaultPhysicalNIC : null; + }, + + hasCustomized() { + return this.enable; + }, + + customValidationRules() { + const out = []; + + if (this.enable) { + out.push({ + nullable: false, + path: 'config.defaultPhysicalNIC', + required: true, + translationKey: 'harvester.setting.validation.physicalNIC', + type: 'string', + }); + } + + return out; + }, + +}; diff --git a/package.json b/package.json index b107a66ad0..4b6e2cc27a 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "nuxt": "2.14.6", "papaparse": "^5.3.0", "portal-vue": "^2.1.5", + "randomstring": "^1.2.1", "require-extension-hooks": "^0.3.3", "require-extension-hooks-babel": "^1.0.0-beta.1", "require-extension-hooks-vue": "^3.0.0", diff --git a/store/index.js b/store/index.js index 2f5df0646f..92692efe55 100644 --- a/store/index.js +++ b/store/index.js @@ -28,9 +28,8 @@ export const plugins = [ namespace: 'management', baseUrl: '/v1', modelBaseClass: BY_TYPE }), Steve({ namespace: 'cluster', baseUrl: '' }), // URL dynamically set for the selected cluster - Steve({ - namespace: 'rancher', baseUrl: '/v3', modelBaseClass: NORMAN_CLASS - }), + Steve({ namespace: 'rancher', baseUrl: '/v3' }), + Steve({ namespace: 'virtual', baseUrl: '' }), ]; export const state = () => { @@ -587,12 +586,15 @@ export const actions = { } const clusterBase = `/k8s/clusters/${ escape(id) }/v1`; + const virtualBase = `/k8s/clusters/${ escape(id) }/v1/harvester`; // Update the Steve client URLs commit('cluster/applyConfig', { baseUrl: clusterBase }); + commit('virtual/applyConfig', { baseUrl: virtualBase }); await Promise.all([ dispatch('cluster/loadSchemas', true), + dispatch('virtual/loadSchemas', true), ]); dispatch('cluster/subscribe'); @@ -606,10 +608,12 @@ export const actions = { }; const res = await allHash({ - projects: isRancher && dispatch('management/findAll', projectArgs), - counts: dispatch('cluster/findAll', { type: COUNT }), - namespaces: dispatch('cluster/findAll', { type: NAMESPACE }), - navLinks: !!getters['cluster/schemaFor'](UI.NAV_LINK) && dispatch('cluster/findAll', { type: UI.NAV_LINK }), + projects: isRancher && dispatch('management/findAll', projectArgs), + counts: dispatch('cluster/findAll', { type: COUNT }), + virtualCount: dispatch('virtual/findAll', { type: COUNT }), + namespaces: dispatch('cluster/findAll', { type: NAMESPACE }), + virtualNamespaces: dispatch('virtual/findAll', { type: NAMESPACE }), + navLinks: !!getters['cluster/schemaFor'](UI.NAV_LINK) && dispatch('cluster/findAll', { type: UI.NAV_LINK }), }); await dispatch('cleanNamespaces'); diff --git a/store/type-map.js b/store/type-map.js index 5db4332544..ce5ba69b4a 100644 --- a/store/type-map.js +++ b/store/type-map.js @@ -118,7 +118,9 @@ import { clone, get } from '@/utils/object'; import { ensureRegex, escapeHtml, escapeRegex, ucFirst, pluralize } from '@/utils/string'; -import { importList, importDetail, importEdit, loadProduct } from '@/utils/dynamic-importer'; +import { + importList, importDetail, importEdit, loadProduct, importComponent +} from '@/utils/dynamic-importer'; import { NAME as EXPLORER } from '@/config/product/explorer'; import isObject from 'lodash/isObject'; @@ -995,6 +997,24 @@ export const getters = { }; }, + haveComponent(state, getters) { + return (path) => { + try { + require.resolve(`@/edit/${ path }`); + + return true; + } catch (e) { + return false; + } + }; + }, + + importComponent(state, getters) { + return (path) => { + return importComponent(path); + }; + }, + importList(state, getters) { return (rawType) => { const type = getters.componentFor(rawType); diff --git a/utils/dynamic-importer.js b/utils/dynamic-importer.js index 1e6daf4166..99af400a95 100644 --- a/utils/dynamic-importer.js +++ b/utils/dynamic-importer.js @@ -83,3 +83,11 @@ export function loadTranslation(name) { // Note: directly returns the import, not a function return import(/* webpackChunkName: "[request]" */ `@/assets/translations/${name}.yaml`); } + +export function importComponent(path) { + if ( !path ) { + throw new Error('Path required'); + } + + return () => import(/* webpackChunkName: "import-component" */ `@/edit/${ path }`); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4c1fdee570..491e0d6f13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4311,6 +4311,11 @@ array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" +array-uniq@1.0.2: + version "1.0.2" + resolved "https://registry.nlark.com/array-uniq/download/array-uniq-1.0.2.tgz?cache=0&sync_timestamp=1620042102127&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farray-uniq%2Fdownload%2Farray-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d" + integrity sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0= + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -12359,6 +12364,14 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +randomstring@^1.2.1: + version "1.2.1" + resolved "https://registry.nlark.com/randomstring/download/randomstring-1.2.1.tgz#71cd3cda24ad1b7e0b65286b3aa5c10853019349" + integrity sha1-cc082iStG34LZShrOqXBCFMBk0k= + dependencies: + array-uniq "1.0.2" + randombytes "2.0.3" + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"