// Copyright 2021 99cloud // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import React from 'react'; import { toJS } from 'mobx'; import { inject, observer } from 'mobx-react'; import { ServerStore } from 'stores/nova/instance'; import { InstanceVolumeStore } from 'stores/nova/instance-volume'; import { PortStore } from 'stores/neutron/port'; import { ServerGroupStore } from 'stores/nova/server-group'; import Base from 'containers/BaseDetail'; import ItemActionButtons from 'components/Tables/Base/ItemActionButtons'; import { Link } from 'react-router-dom'; // render topo content import { Col, Row, Button } from 'antd'; import { SyncOutlined } from '@ant-design/icons'; import Status from 'components/Status'; import volumeIcon from 'asset/image/volume.svg'; import instanceIcon from 'asset/image/instance.svg'; import interfaceIcon from 'asset/image/interface.svg'; import classnames from 'classnames'; import ImageType from 'components/ImageType'; import { instanceStatus, isIronicInstance } from 'resources/instance'; import { generateId } from 'utils/index'; import { getSinceTime, getLocalTimeStr } from 'utils/time'; import AttachVolume from 'pages/compute/containers/Instance/actions/AttachVolume'; import globalRootStore from 'stores/root'; import styles from './index.less'; export class BaseDetail extends Base { componentDidMount() { this.fetchVolumes(); this.fetchInterfaces(); if (this.detailData.server_groups[0]) { this.fetchSeverGroup(); } } init() { this.store = new ServerStore(); this.interfaceStore = new PortStore(); this.volumeStore = new InstanceVolumeStore(); this.serverGroupStore = new ServerGroupStore(); } get leftCards() { const cards = [ this.networkCard, this.flavorCard, this.imageCard, this.securityGroupCard, ]; if (!isIronicInstance(this.detailData)) { cards.push(this.serverGroupCard); } return cards; } get rightCards() { const ret = [this.topoCard]; const { detail: { fault }, } = this.props; if (fault && fault.message) { ret.splice(0, 0, this.errorCard); } return ret; } get networkCard() { const addresses = toJS(this.detailData.addresses) || []; const networks = []; Object.keys(addresses).forEach((netName) => { const values = addresses[netName]; const fixedIps = values.filter((it) => it['OS-EXT-IPS:type'] === 'fixed'); const fips = values.filter((it) => it['OS-EXT-IPS:type'] === 'floating'); fixedIps.forEach((fixedIp) => { const fip = fips.find( (it) => it['OS-EXT-IPS-MAC:mac_addr'] === fixedIp['OS-EXT-IPS-MAC:mac_addr'] ); networks.push({ netName, fixedIp, fip, }); }); }); const content = networks.map((item, index) => { const { netName, fixedIp, fip } = item; return (
{netName} | {fixedIp.addr} {fip && | {fip.addr}}
); }); const options = [ { label: t('Network'), content, }, ]; return { title: t('Network Info'), options, }; } get flavorCard() { const flavor = toJS(this.detailData.flavor) || {}; const { extra_specs = {} } = flavor; const options = [ { label: t('Flavor Name'), content: flavor.original_name, }, { label: t('RAM'), content: `${flavor.ram / 1024} GB`, }, { label: t('VCPUs'), content: flavor.vcpus, }, // { // label: t('Disk'), // content: `${flavor.disk} GB`, // }, ]; if ( extra_specs[':architecture'] === 'heterogeneous_computing' && extra_specs[':category'] === 'visualization_compute_optimized_type_with_gpu' ) { options.push({ label: t('VGPU'), content: extra_specs['resources:VGPU'], }); } return { title: t('Flavor Info'), options, }; } get imageCard() { const item = this.detailData.itemInList || {}; const { image, image_name } = item; const url = this.getRoutePath('imageDetail', { id: image }); const options = [ { label: t('Name'), content: image_name || '-', }, { label: t('ID'), content: image ? {image} : '-', }, ]; return { title: t('Image Info'), options, }; } get securityGroupCard() { const { security_groups = [] } = this.detailData; const items = Array.from(new Set(security_groups.map((it) => it.name))); const { match: { url }, } = this.props; const options = [ { label: t('Name'), dataIndex: 'security_groups', render: () => items && items.length ? items.map((it) => (
{it}
)) : '-', }, ]; return { title: t('Security Group Info'), options, }; } get serverGroupCard() { const server_group = this.serverGroupStore.detail || {}; const { name } = server_group; const options = [ { label: t('Name'), content: name || '-', }, ]; return { title: t('Server Group'), options, }; } get interfaces() { const infos = []; const { match: { url }, } = this.props; (this.interfaceStore.list.data || []).forEach((item) => { const { name, id, networkName, fixed_ips = [], network_id } = item; infos.push({ networkName, name: {name || id}, address: fixed_ips.map((it) => it.ip_address), network_id, interface: item, }); }); return infos; } get volumeActions() { return { firstAction: AttachVolume }; } fetchVolumes = async () => { const params = { serverId: this.id, }; if (!this.isMyResource) { params.all_projects = true; } await this.volumeStore.fetchList(params); }; fetchInterfaces = async () => { const params = { device_id: this.id, }; if (!this.isMyResource) { params.all_projects = true; } await this.interfaceStore.fetchList(params); this.store.isLoading = false; }; fetchSeverGroup = async () => { const { server_groups = [] } = this.detailData; await this.serverGroupStore.fetchDetail({ id: server_groups[0] }); }; handleRefreshVolume = () => { this.fetchVolumes(); }; renderInterfaceRow() { const interfaceItem = this.interfaces.map((info, index) => (
{info.networkName} ( {info.name} ){' '}
interface_icon
{info.address.map((it) => (
{it}
))}
)); return {interfaceItem}; } renderImageType(osDistro) { return ( ); } renderVmRow() { const item = toJS(this.detailData.itemInList) || {}; const { status } = this.detailData; const { image_name, image_os_distro } = item; return (
instance_icon
{this.renderImageType(image_os_distro)} {image_name}
); } renderVolumeRow() { if (!this.props.rootStore.checkEndpoint('cinder')) return null; const { match: { url }, } = this.props; const attachedVolumes = (this.volumeStore.list.data || []).map((item) => { const volumeInfos = [ { label: item.disk_tag === 'os_disk' ? t('Root Disk') : t('Data Disk'), value: item.name || '-', }, { label: t('ID'), value: {item.id}, }, { label: t('Size'), value: `${item.size}GB`, }, { label: t('Volume Type'), value: item.volume_type || '-', }, { label: t('Create Time'), value: getSinceTime(item.created_at) || '-', }, ]; const volumeInfoItem = volumeInfos.map((info) => ( {info.label} {info.value} )); return (
volume_icon
{volumeInfoItem}
); }); const { isAdminPage } = this.props; const containerProps = { isAdminPage }; return (
{attachedVolumes} ); } renderTopoContent() { return (
{this.renderVmRow()} {this.renderInterfaceRow()} {this.renderVolumeRow()}
); } get topoCard() { const title = t('Instance Architecture'); const titleHelp = (

{t( 'The instance architecture diagram mainly shows the overall architecture composition of the instance. If you need to view the network topology of the instance, please go to: ' )}

{t('Network topology page')}
); const options = [ { content: this.renderTopoContent(), }, ]; const { refreshDetail } = this.props; const button = (