| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- /*
- Copyright (C) 2022 Cloudbase Solutions SRL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- import React from "react";
- import { observer } from "mobx-react";
- import styled from "styled-components";
- import moment from "moment";
- import CopyValue from "@src/components/ui/CopyValue";
- import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
- import Button from "@src/components/ui/Button";
- import { ThemePalette, ThemeProps } from "@src/components/Theme";
- import {
- MetalHubDisk,
- MetalHubNic,
- MetalHubServer,
- } from "@src/@types/MetalHub";
- import {
- ArrowStyled,
- GlobalStyle,
- HeaderIcon,
- HeaderName,
- Row,
- RowBody,
- RowBodyColumn,
- RowBodyColumnValue,
- RowHeader,
- RowHeaderColumn,
- } from "@src/components/modules/TransferModule/TransferDetailsTable";
- import { Collapse } from "react-collapse";
- import LoadingButton from "@src/components/ui/LoadingButton";
- import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
- const Wrapper = styled.div`
- ${ThemeProps.exactWidth(ThemeProps.contentWidth)}
- margin: 0 auto;
- padding-left: 126px;
- `;
- const Info = styled.div`
- display: flex;
- flex-wrap: wrap;
- margin-top: 32px;
- margin-left: -32px;
- `;
- const Field = styled.div`
- ${ThemeProps.exactWidth("calc(50% - 32px)")}
- margin-bottom: 32px;
- margin-left: 32px;
- `;
- const Value = styled.div``;
- const Label = styled.div`
- font-size: 10px;
- font-weight: ${ThemeProps.fontWeights.medium};
- color: ${ThemePalette.grayscale[3]};
- text-transform: uppercase;
- margin-bottom: 3px;
- `;
- const LoadingWrapper = styled.div`
- display: flex;
- justify-content: center;
- width: 100%;
- margin: 32px 0 64px 0;
- `;
- const Buttons = styled.div<any>`
- margin-top: 64px;
- display: flex;
- justify-content: space-between;
- `;
- const ButtonsColumn = styled.div<any>`
- display: flex;
- flex-direction: column;
- button {
- margin-bottom: 16px;
- &:last-child {
- margin-bottom: 0;
- }
- }
- `;
- const Table = styled.div``;
- const TableBody = styled.div``;
- const TableHeader = styled.div`
- background: ${ThemePalette.grayscale[1]};
- border-radius: ${ThemeProps.borderRadius};
- margin-bottom: 32px;
- &:last-child {
- margin-bottom: 0;
- }
- `;
- const TableHeaderInfo = styled.div`
- padding: 16px;
- border-bottom: 1px solid ${ThemePalette.grayscale[5]};
- font-size: 16px;
- `;
- const TableBodyContent = styled.div`
- font-size: 14px;
- `;
- const HeaderSubtitle = styled.span`
- color: ${ThemePalette.grayscale[5]};
- margin-left: 4px;
- `;
- type Props = {
- server: MetalHubServer | null;
- loading: boolean;
- creatingReplica: boolean;
- creatingMigration: boolean;
- onCreateReplicaClick: () => void;
- onCreateMigrationClick: () => void;
- onDeleteClick: () => void;
- };
- type State = {
- openedRows: string[];
- };
- @observer
- class MetalHubServerDetailsContent extends React.Component<Props, State> {
- state: State = {
- openedRows: [],
- };
- handleRowClick(id: string) {
- if (this.state.openedRows.find(i => i === id)) {
- this.setState(prevState => ({
- openedRows: prevState.openedRows.filter(i => id !== i),
- }));
- } else {
- this.setState(prevState => ({
- openedRows: [...prevState.openedRows, id],
- }));
- }
- }
- renderLoading() {
- return (
- <LoadingWrapper>
- <StatusImage />
- </LoadingWrapper>
- );
- }
- renderButtons() {
- if (this.props.loading) {
- return null;
- }
- const creating = this.props.creatingReplica || this.props.creatingMigration;
- return (
- <Buttons>
- <ButtonsColumn>
- {this.props.creatingReplica ? (
- <LoadingButton>Loading Wizard ...</LoadingButton>
- ) : (
- <Button
- onClick={this.props.onCreateReplicaClick}
- disabled={creating || !this.props.server?.active}
- hollow
- >
- Create Replica
- </Button>
- )}
- {this.props.creatingMigration ? (
- <LoadingButton>Loading Wizard ...</LoadingButton>
- ) : (
- <Button
- hollow
- disabled={creating || !this.props.server?.active}
- onClick={this.props.onCreateMigrationClick}
- >
- Create Migration
- </Button>
- )}
- </ButtonsColumn>
- <ButtonsColumn>
- <Button
- alert
- hollow
- onClick={() => {
- this.props.onDeleteClick();
- }}
- >
- Remove Server
- </Button>
- </ButtonsColumn>
- </Buttons>
- );
- }
- renderInfo() {
- if (this.props.loading || !this.props.server) {
- return null;
- }
- const server = this.props.server;
- return (
- <Info>
- <Field>
- <Label>Hostname</Label>
- {this.renderValue(server.hostname || "-")}
- </Field>
- <Field>
- <Label>ID</Label>
- {this.renderValue(String(server.id))}
- </Field>
- <Field>
- <Label>Status</Label>
- <Value>
- {server.active ? (
- <StatusPill status="COMPLETED" label="Active" />
- ) : (
- <StatusPill status="ERROR" label="Inactive" />
- )}
- </Value>
- </Field>
- <Field>
- <Label>API Endpoint</Label>
- {this.renderValue(String(server.api_endpoint))}
- </Field>
- <Field>
- <Label>Created At</Label>
- <Value>
- {moment(server.created_at).format("YYYY-MM-DD HH:mm:ss")}
- </Value>
- </Field>
- <Field>
- <Label>Updated At</Label>
- <Value>
- {moment(server.updated_at).format("YYYY-MM-DD HH:mm:ss")}
- </Value>
- </Field>
- <Field>
- <Label>Firmware Type</Label>
- {this.renderValue(server.firmware_type || "-")}
- </Field>
- <Field>
- <Label>CPU Cores</Label>
- {server.physical_cores || "-"} physical, {server.logical_cores || "-"}{" "}
- logical
- </Field>
- <Field>
- <Label>Memory Size</Label>
- {server.memory
- ? this.renderValue(
- `${(server.memory / 1024 / 1024 / 1024).toFixed(2)} GB`
- )
- : "-"}
- </Field>
- <Field>
- <Label>Operating System</Label>
- {server.os_info.os_name
- ? this.renderValue(
- `${server.os_info.os_name} ${server.os_info.os_version}`
- )
- : "-"}
- </Field>
- </Info>
- );
- }
- renderValue(value: string) {
- return value !== "-" ? (
- <CopyValue value={value} maxWidth="90%" />
- ) : (
- <Value>{value}</Value>
- );
- }
- renderNics(nics: MetalHubNic[]) {
- return nics.map(nic => {
- const isOpened = Boolean(
- this.state.openedRows.find(i => i === nic.nic_name)
- );
- return (
- <Row
- key={nic.nic_name}
- onClick={() => {
- this.handleRowClick(nic.nic_name);
- }}
- >
- <ArrowStyled
- primary
- orientation={isOpened ? "up" : "down"}
- opacity={isOpened ? 1 : 0}
- thick
- />
- <RowHeader>
- <RowHeaderColumn>
- <HeaderIcon icon="network" />
- <HeaderName source>{nic.nic_name}</HeaderName>
- </RowHeaderColumn>
- <RowHeaderColumn />
- </RowHeader>
- <Collapse isOpened={isOpened}>
- <RowBody>
- <RowBodyColumn>
- {[
- `MAC Address: ${nic.mac_address}`,
- `IP Addresses: ${nic.ip_addresses.join(", ")}`,
- `Interface Type: ${nic.interface_type}`,
- ].map(l => (
- <RowBodyColumnValue key={l}>{l}</RowBodyColumnValue>
- ))}
- </RowBodyColumn>
- </RowBody>
- </Collapse>
- </Row>
- );
- });
- }
- renderPartitions(
- partitions: MetalHubDisk["partitions"] = [],
- sectorSize = 0
- ) {
- return partitions.map(partition => {
- if (!partition.partition_uuid) {
- return null;
- }
- const isOpened = Boolean(
- this.state.openedRows.find(i => i === partition.partition_uuid)
- );
- const size = partition.sectors * sectorSize;
- const sizeString = sectorSize
- ? size < 1024 * 1024 * 1024
- ? `${(size / 1024 / 1024).toFixed(2)} MB`
- : `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`
- : "Size unavailable";
- return (
- <Row
- key={partition.partition_uuid}
- onClick={() => {
- this.handleRowClick(partition.partition_uuid!);
- }}
- >
- <ArrowStyled
- primary
- orientation={isOpened ? "up" : "down"}
- opacity={isOpened ? 1 : 0}
- thick
- />
- <RowHeader>
- <RowHeaderColumn>
- <HeaderIcon icon="storage" />
- <HeaderName source>
- {partition.name} <HeaderSubtitle>{sizeString}</HeaderSubtitle>
- </HeaderName>
- </RowHeaderColumn>
- <RowHeaderColumn />
- </RowHeader>
- <Collapse isOpened={isOpened}>
- <RowBody>
- <RowBodyColumn>
- {[
- `ID: ${partition.partition_uuid}`,
- `Path: ${partition.path}`,
- `Sectors: ${partition.sectors}`,
- `Start Sector: ${partition.start_sector}`,
- `End Sector: ${partition.end_sector}`,
- ].map(l => (
- <RowBodyColumnValue key={l}>{l}</RowBodyColumnValue>
- ))}
- </RowBodyColumn>
- </RowBody>
- </Collapse>
- </Row>
- );
- });
- }
- renderNicsTable() {
- if (this.props.loading || !this.props.server?.nics) {
- return null;
- }
- return (
- <Table style={{ marginTop: "24px" }}>
- <Label>Network Interface Controllers</Label>
- <TableBody>
- <TableHeader>
- <TableBodyContent>
- {this.renderNics(this.props.server.nics)}
- </TableBodyContent>
- </TableHeader>
- </TableBody>
- </Table>
- );
- }
- renderPartitionsTable() {
- if (this.props.loading || !this.props.server?.disks) {
- return null;
- }
- return (
- <Table>
- <GlobalStyle />
- <Label>Disk Partitions</Label>
- <TableBody>
- {this.props.server.disks.map(disk => (
- <TableHeader key={disk.id}>
- <TableHeaderInfo>
- {disk.name}{" "}
- {disk.size ? (
- <HeaderSubtitle>
- {(disk.size / 1024 / 1024 / 1024).toFixed(2)} GB
- </HeaderSubtitle>
- ) : null}
- </TableHeaderInfo>
- <TableBodyContent>
- {this.renderPartitions(
- disk.partitions,
- disk.physical_sector_size
- )}
- </TableBodyContent>
- </TableHeader>
- ))}
- </TableBody>
- </Table>
- );
- }
- render() {
- return (
- <Wrapper>
- {this.renderInfo()}
- {this.renderPartitionsTable()}
- {this.renderNicsTable()}
- {this.props.loading ? this.renderLoading() : null}
- {this.renderButtons()}
- <GlobalStyle />
- </Wrapper>
- );
- }
- }
- export default MetalHubServerDetailsContent;
|