diff --git a/web/console/src/modules/cluster/actions/resourcePodActions.ts b/web/console/src/modules/cluster/actions/resourcePodActions.ts index 0cfd4d909..d7036eeba 100644 --- a/web/console/src/modules/cluster/actions/resourcePodActions.ts +++ b/web/console/src/modules/cluster/actions/resourcePodActions.ts @@ -162,18 +162,19 @@ const restActions = { }, /**Tapp灰度升级编辑项 */ - initTappGrayUpdate: (items: TappGrayUpdateEditItem[]): ReduxAction => { + initTappGrayUpdate: (setting: TappGrayUpdateEditItem): ReduxAction => { return { type: ActionType.W_TappGrayUpdate, - payload: items + payload: setting }; }, - updateTappGrayUpdate: (index_out, index_in, imageName, imageTag) => { + + updateTappGrayUpdate: (index_in, imageName, imageTag) => { return async (dispatch, getState: GetState) => { const { editTappGrayUpdate } = getState().subRoot.resourceDetailState; - const target: TappGrayUpdateEditItem[] = cloneDeep(editTappGrayUpdate); - target[index_out].containers[index_in].imageName = imageName; - target[index_out].containers[index_in].imageTag = imageTag; + const target: TappGrayUpdateEditItem = cloneDeep(editTappGrayUpdate); + target.containers[index_in].imageName = imageName; + target.containers[index_in].imageTag = imageTag; dispatch({ type: ActionType.W_TappGrayUpdate, @@ -181,6 +182,7 @@ const restActions = { }); }; }, + /** 是否展示 登录弹框 */ toggleLoginDialog: () => { return async (dispatch, getState: GetState) => { diff --git a/web/console/src/modules/cluster/actions/validateWorkloadActions.ts b/web/console/src/modules/cluster/actions/validateWorkloadActions.ts index a70e9578e..630c8a6ec 100644 --- a/web/console/src/modules/cluster/actions/validateWorkloadActions.ts +++ b/web/console/src/modules/cluster/actions/validateWorkloadActions.ts @@ -792,11 +792,11 @@ export const validateWorkloadActions = { }; }, - validateGrayUpdateRegistrySelection(index_out: number, index_in: number, imageName: string) { + validateGrayUpdateRegistrySelection(index_in: number, imageName: string) { return async (dispatch, getState: GetState) => { const { editTappGrayUpdate } = getState().subRoot.resourceDetailState; - const target: TappGrayUpdateEditItem[] = cloneDeep(editTappGrayUpdate); - target[index_out].containers[index_in].v_imageName = validateWorkloadActions._validateRegistrySelection( + const target: TappGrayUpdateEditItem = cloneDeep(editTappGrayUpdate); + target.containers[index_in].v_imageName = validateWorkloadActions._validateRegistrySelection( imageName ); @@ -810,12 +810,10 @@ export const validateWorkloadActions = { validateGrayUpdate() { return async (dispatch, getState: GetState) => { const { editTappGrayUpdate } = getState().subRoot.resourceDetailState; - editTappGrayUpdate.forEach((item, index_out) => { - item.containers.forEach((container, index_in) => { - dispatch( - validateWorkloadActions.validateGrayUpdateRegistrySelection(index_out, index_in, container.imageName) - ); - }); + editTappGrayUpdate.containers.forEach((container, index_in) => { + dispatch( + validateWorkloadActions.validateGrayUpdateRegistrySelection(index_in, container.imageName) + ); }); }; }, diff --git a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourceGrayUpgradeDialog.tsx b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourceGrayUpgradeDialog.tsx index cd7e96222..0e47005c6 100644 --- a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourceGrayUpgradeDialog.tsx +++ b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourceGrayUpgradeDialog.tsx @@ -2,55 +2,126 @@ import * as classnames from 'classnames'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Alert, Bubble, Button, Input, Modal, Text } from '@tea/component'; +import { Alert, Bubble, Button, Input, Modal, Text, Form, Radio, InputNumber } from '@tea/component'; import { FormPanel } from '@tencent/ff-component'; import { bindActionCreators, uuid } from '@tencent/ff-redux'; import { t, Trans } from '@tencent/tea-app/lib/i18n'; import { resourceConfig } from '../../../../../../config'; -import { cloneDeep, CreateResource, InputField, WorkflowDialog, TipInfo } from '../../../../common'; +import { cloneDeep, CreateResource, InputField, WorkflowDialog, TipInfo, initValidator } from '../../../../common'; import { allActions } from '../../../actions/'; import { RootProps } from '../../ClusterApp'; +import { Pod } from '../../../models'; const mapDispatchToProps = dispatch => Object.assign({}, bindActionCreators({ actions: allActions }, dispatch), { dispatch }); +enum UpdateType { + UserSelect = 'UserSelect', // 升级用户选中的pod + ByPercentage = 'ByPercentage' // 按比例升级pod +} + @connect(state => state, mapDispatchToProps) export class ResourceGrayUpgradeDialog extends React.Component { + state = { + updateType: UpdateType.UserSelect, + updatePercentage: 20 + }; + + // 升级比例的自动步进 + get step(): number { + return 10; + } + + // 升级类型 + get updateType(): UpdateType { + return this.state.updateType; + } + + setUpdateType = (updateType: string): void => { + this.setState({ updateType }); + }; + + // 升级比例 + get updatePercentage(): number { + return this.state.updatePercentage; + } + + setUpdatePercentage = (updatePercentage: number): void => { + this.setState({ updatePercentage }); + }; + + // 用户选中的pods记录 + get podSelection(): Pod[] { + return this.props.subRoot.resourceDetailState.podSelection; + } + + // 全部pods记录 + get podRecords(): Pod[] { + return this.props.subRoot.resourceDetailState.podList.data.records; + } + + // 要升级的pods记录 + get targetPods(): Pod[] { + let result = []; + switch (this.updateType) { + case UpdateType.UserSelect: + result = this.podSelection; + break; + case UpdateType.ByPercentage: + if (this.updatePercentage > 0) { + // 取前x% + result = this.podRecords.slice(0, Math.floor((this.podRecords.length * this.updatePercentage) / 100)); + } else { + // 取后x% + result = this.podRecords.slice(Math.floor((this.podRecords.length * (this.updatePercentage + 100)) / 100)); + } + break; + default: + break; + } + return result; + } + render() { const { actions, region, clusterVersion, route } = this.props; - const { updateGrayTappFlow, editTappGrayUpdate } = this.props.subRoot.resourceDetailState; + const { updateGrayTappFlow, editTappGrayUpdate, podList, podSelection } = this.props.subRoot.resourceDetailState; const targetResource = this.props.subRoot.resourceOption.ffResourceList.selection; let resourceInfo = resourceConfig(clusterVersion)['tapp']; - let templates = {}; - let templatePool = {}; - editTappGrayUpdate.forEach(item => { - let indexName = item.name.split(item.generateName)[1]; //获取第几个实例 - if (indexName) { - templates[indexName] = item.name; - } - //将spec.template复制过来,然后修改caontainer里的name和imageName字段 - let newTemplate = cloneDeep(targetResource.spec.template); - - //editTapp只存储了必要的修改信息如containerName和containerImg, - //所以更新的时候需要把container的之前的内容也带上,不然之前的内容会被清空 - newTemplate.spec.containers = item.containers.map(container => { - let targetContainer = targetResource.spec.template.spec.containers.find(c => c.name === container.name); - return { - ...targetContainer, - name: container.name, - image: `${container.imageName}:${container.imageTag}` - }; + + function getJsonData(pods, updateSettings) { + let templates = {}; + let templatePool = {}; + pods.forEach(pod => { + let indexName = pod.metadata.name.split(pod.metadata.generateName)[1]; //获取第几个实例 + if (indexName) { + templates[indexName] = pod.metadata.name; + } + //将spec.template复制过来,然后修改caontainer里的name和imageName字段 + let newTemplate = cloneDeep(targetResource.spec.template); + + //editTapp只存储了必要的修改信息如containerName和containerImg, + //所以更新的时候需要把container的之前的内容也带上,不然之前的内容会被清空 + newTemplate.spec.containers = pod.spec.containers.map(container => { + let targetContainer = targetResource.spec.template.spec.containers.find(c => c.name === container.name); + let containerSetting = updateSettings.containers.find(c => c.name === container.name); + return { + ...targetContainer, + image: `${containerSetting.imageName}:${containerSetting.imageTag}` + }; + }); + templatePool[pod.metadata.name] = newTemplate; }); - templatePool[item.name] = newTemplate; - }); - let jsonYaml = { - spec: { - templatePool, - templates - } - }; + let jsonYaml = { + spec: { + templatePool, + templates + } + }; + return jsonYaml; + } + // 需要提交的数据 let resource: CreateResource = { id: uuid(), @@ -58,7 +129,6 @@ export class ResourceGrayUpgradeDialog extends React.Component { namespace: route.queries['np'], clusterId: route.queries['clusterId'], resourceIns: route.queries['resourceIns'], - jsonData: JSON.stringify(jsonYaml), isStrategic: false }; @@ -72,38 +142,63 @@ export class ResourceGrayUpgradeDialog extends React.Component { targets={[resource]} validateAction={this._validation.bind(this)} preAction={() => { + const setResourceJSONData = resource => { + let jsonYaml = {}; + jsonYaml = getJsonData(this.targetPods, editTappGrayUpdate); + resource.jsonData = JSON.stringify(jsonYaml); + }; + setResourceJSONData(resource); actions.resourceDetail.pod.podSelect([]); }} >
{t('灰度升级过程中,下列所有容器实例会同时重启')} - - - {editTappGrayUpdate.map((item, index_out) => ( - - {item.containers.map((container, index_in) => { + + + this.setUpdateType(value)}> + 升级选中POD + 按比例升级POD + + + {this.updateType === UpdateType.ByPercentage && ( + + + + )} + + + {this.targetPods.length > 0 ? `共${this.targetPods.length}个` : '-'} +
    + {this.targetPods.slice(0, 3).map(item => ( +
  • {item.metadata.name}
  • + ))} + {this.targetPods.length > 3 && ( +
  • + item.metadata.name).join(', ')}>... +
  • + )} +
+
+
+ {editTappGrayUpdate && ( + + {editTappGrayUpdate.containers.map((container, index_in) => { return ( - - - {container.name} - +
{ actions.resourceDetail.pod.updateTappGrayUpdate( - index_out, index_in, value, container.imageTag @@ -111,7 +206,6 @@ export class ResourceGrayUpgradeDialog extends React.Component { }} onBlur={e => { actions.validate.workload.validateGrayUpdateRegistrySelection( - index_out, index_in, e.target.value ); @@ -122,10 +216,10 @@ export class ResourceGrayUpgradeDialog extends React.Component { { actions.resourceDetail.pod.updateTappGrayUpdate( - index_out, index_in, container.imageName, value @@ -136,8 +230,8 @@ export class ResourceGrayUpgradeDialog extends React.Component { ); })} - - ))} + + )}
@@ -146,13 +240,10 @@ export class ResourceGrayUpgradeDialog extends React.Component { /** 校验Tapp 灰度升级 是否正确 */ private async _validation() { - await this.props.actions.validator.workload.validateGrayUpdate(); let result = true; let { editTappGrayUpdate } = this.props.subRoot.resourceDetailState; - editTappGrayUpdate.forEach(item => { - item.containers.forEach(container => { - result = result && container.v_imageName.status === 1; - }); + editTappGrayUpdate.containers.forEach(container => { + result = result && container.v_imageName.status === 1; }); return result; } diff --git a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodActionPanel.tsx b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodActionPanel.tsx index cee250991..0cac0f22b 100644 --- a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodActionPanel.tsx +++ b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodActionPanel.tsx @@ -1,24 +1,20 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import { IsShowLoginDialog } from 'src/modules/cluster/constants/ActionType'; -import { TappGrayUpdateEditItem } from 'src/modules/cluster/models/ResourceDetailState'; - +import { isEmpty, cloneDeep, uniq } from 'lodash'; import { Bubble, Button, Justify, Modal, Table, TagSearchBox } from '@tea/component'; import { bindActionCreators, insertCSS, uuid } from '@tencent/ff-redux'; import { ChartInstancesPanel } from '@tencent/tchart'; import { t, Trans } from '@tencent/tea-app/lib/i18n'; +import { TappGrayUpdateEditItem } from 'src/modules/cluster/models/ResourceDetailState'; import { resourceConfig } from '../../../../../../config'; import { initValidator } from '../../../../../../src/modules/common'; -import { cloneDeep, isEmpty } from '../../../../common/utils'; import { allActions } from '../../../actions'; -import { CreateResource, PodFilterInNode } from '../../../models'; +import { CreateResource, PodFilterInNode, Pod } from '../../../models'; import { containerMonitorFields, MonitorPanelProps, podMonitorFields } from '../../../models/MonitorPanel'; import { router } from '../../../router'; import { RootProps } from '../../ClusterApp'; import { IsInNodeManageDetail } from './ResourceDetail'; -import { ResourceGrayUpgradeDialog } from './ResourceGrayUpgradeDialog'; -import { ResourceTappPodDeleteDialog } from './ResourceTappPodDeleteDialog'; import { reduceNs } from '@helper'; /** k8s pod的状态值 */ @@ -60,10 +56,8 @@ export class ResourcePodActionPanel extends React.Component { return { attr: { - // type: 'single', key: item, name: TagSearchKeyMap[item] - // values: [] }, values: [{ name: podFilterInNode[item] }] }; @@ -74,6 +68,14 @@ export class ResourcePodActionPanel extends React.Component uniq(podRecords.map(item => item.spec.containers.length)).length === 1; + const podConsistent = getPodConsistency(this.podRecords); + return ( - + - - - + ); } @@ -153,8 +156,7 @@ export class ResourcePodActionPanel extends React.Component { + this.podRecords.forEach(item => { // 获取podName的集合 podNameValues.push({ key: 'podName', @@ -214,7 +216,6 @@ export class ResourcePodActionPanel extends React.Component { this._handleClickForTagSearch(tags); @@ -273,34 +274,26 @@ export class ResourcePodActionPanel extends React.Component { - return { - name: pod.metadata.name, - generateName: pod.metadata.generateName, - containers: pod.spec.containers.map(container => { - let index = container.image.lastIndexOf(':'), - imageName, - imageTag; - if (index !== -1) { - imageName = container.image.slice(0, index); - imageTag = container.image.slice(index + 1); - } else { - imageName = container.image; - imageTag = ''; - } + let setting: TappGrayUpdateEditItem = { containers: [] }; + if (!isEmpty(this.podRecords)) { + // 取第一个pod的容器镜像列表作为初始配置(有选中的pod取选中的pod中的第一个,否则取全部列表中的第一个) + const pod = this.podSelections[0] || this.podRecords[0]; + setting = { + containers: pod.spec.containers.map(({ name, image }) => { + const [imageName, imageTag = ''] = image.split(':'); return { - name: container.name, + name, imageName, imageTag, v_imageName: initValidator }; }) }; - }); - this.props.actions.resourceDetail.pod.initTappGrayUpdate(items); + } + this.props.actions.resourceDetail.pod.initTappGrayUpdate(setting); this.props.actions.workflow.updateGrayTapp.start(); } + /** render监控按钮 */ private _renderMonitorButton() { return ( @@ -355,7 +348,7 @@ export class ResourcePodActionPanel extends React.Component ({ + list: this.podRecords.map(item => ({ pod_name: item.metadata.name, isChecked: !resourceDetailState.podSelection.length || @@ -373,8 +366,8 @@ export class ResourcePodActionPanel extends React.Component - {resourceDetailState.podList.data.records.map(pod => ( + {this.podRecords.map(pod => ( diff --git a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodPanel.tsx b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodPanel.tsx index 586238250..789e0b37a 100644 --- a/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodPanel.tsx +++ b/web/console/src/modules/cluster/components/resource/resourceDetail/ResourcePodPanel.tsx @@ -1,7 +1,6 @@ import * as classnames from 'classnames'; import * as React from 'react'; import { connect } from 'react-redux'; - import { Bubble, Icon, TableColumn, Text } from '@tea/component'; import { autotip } from '@tea/component/table/addons/autotip'; import { expandable } from '@tea/component/table/addons/expandable'; @@ -22,6 +21,8 @@ import { router } from '../../../router'; import { RootProps } from '../../ClusterApp'; import { IsInNodeManageDetail } from './ResourceDetail'; import { PodTabel } from './ResourcePodTable'; +const moment = require('moment'); +moment.locale('zh-CN'); /** 获取containerId,去掉前缀 docker:// */ export function reduceContainerId(containerStatus: any[], containerName: string) { @@ -84,7 +85,6 @@ export class ResourcePodPanel extends React.Component this._renderPodStatus(x) }, + { + key: 'image', + header: t('镜像'), + render: x => ( +
    + {x.spec.containers.map(c => ( +
  • {c.image}
  • + ))} +
+ ) + }, { key: 'hostIP', header: t('实例所在节点IP'), @@ -215,13 +226,12 @@ export class ResourcePodPanel extends React.Component , - render: x => this._getDays(x.status.startTime) - }, - { - key: 'createTime', - header: t('创建时间'), - width: '10%', - render: x => this._getCreateTime(x.metadata.creationTimestamp) + // render: x => this._getDays(x.status.startTime) + render: x => ( + + {this.runningTime(x.status.startTime)} + + ) }, { key: 'operation', @@ -410,7 +420,12 @@ export class ResourcePodPanel extends React.ComponentTerminating; } if (pod.status.reason) { - result = <>{pod.status.phase}({pod.status.reason}) + result = ( + <> + {pod.status.phase} + ({pod.status.reason}) + + ); } return result; } @@ -507,6 +522,8 @@ export class ResourcePodPanel extends React.Component{`${first} ${second}`}; } + private runningTime = (startTime: string) => moment.duration(moment().diff(moment(startTime))).humanize(true); + /** 运行时间 */ private _getDays(startTime: string) { let content = ''; diff --git a/web/console/src/modules/cluster/models/ResourceDetailState.ts b/web/console/src/modules/cluster/models/ResourceDetailState.ts index a68a7c581..1294322bc 100644 --- a/web/console/src/modules/cluster/models/ResourceDetailState.ts +++ b/web/console/src/modules/cluster/models/ResourceDetailState.ts @@ -55,7 +55,7 @@ export interface ResourceDetailState { updateGrayTappFlow?: ResourceModifyWorkflow; /**tapp 灰度升级编辑项 */ - editTappGrayUpdate?: TappGrayUpdateEditItem[]; + editTappGrayUpdate?: TappGrayUpdateEditItem; /** 是否展示 登录弹框 */ isShowLoginDialog?: boolean; @@ -177,13 +177,13 @@ export interface RsEditJSONYaml { } export interface TappGrayUpdateEditItem { /** 实例名称 */ - name: string; + // name: string; - generateName: string; + // generateName: string; /** 容器 */ containers: { /**容器名称 */ - name: string; + // name: string; /**容器镜像名称 */ imageName: string; /**容器镜像版本 */ diff --git a/web/console/src/modules/cluster/reducers/ResourceDetailReducer.ts b/web/console/src/modules/cluster/reducers/ResourceDetailReducer.ts index 2bbfe775b..b758de18a 100644 --- a/web/console/src/modules/cluster/reducers/ResourceDetailReducer.ts +++ b/web/console/src/modules/cluster/reducers/ResourceDetailReducer.ts @@ -83,7 +83,7 @@ const TempReducer = combineReducers({ actionType: ActionType.UpdateGrayTapp }), - editTappGrayUpdate: reduceToPayload(ActionType.W_TappGrayUpdate, []), + editTappGrayUpdate: reduceToPayload(ActionType.W_TappGrayUpdate, { containers: [] }), isShowLoginDialog: reduceToPayload(ActionType.IsShowLoginDialog, false),