/*
Copyright (C) 2017 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 .
*/
import React from "react";
import { observer } from "mobx-react";
import styled from "styled-components";
import Checkbox from "@src/components/ui/Checkbox";
import ReloadButton from "@src/components/ui/ReloadButton";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
import Button from "@src/components/ui/Button";
import SearchInput from "@src/components/ui/SearchInput";
import InfoIcon from "@src/components/ui/InfoIcon";
import Pagination from "@src/components/ui/Pagination";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
import type { Instance as InstanceType } from "@src/@types/Instance";
import instanceImage from "./images/instance.svg";
import instanceLinuxImage from "./images/instance-linux.svg";
import instanceWindowsImage from "./images/instance-windows.svg";
import bigInstanceImage from "./images/instance-big.svg";
const mbToGbString = (mb: number) =>
mb >= 1024 ? `${(mb / 1024).toFixed(2)} GB` : `${mb} MB`;
const Wrapper = styled.div`
width: 100%;
display: flex;
flex-direction: column;
`;
const LoadingWrapper = styled.div`
margin-top: 32px;
display: flex;
flex-direction: column;
align-items: center;
`;
const InstancesWrapper = styled.div`
margin-left: -32px;
flex-grow: 1;
overflow: auto;
`;
const InstanceContent = styled.div`
display: flex;
align-items: center;
width: 100%;
padding: 8px 16px;
margin-left: 16px;
border-top: 1px solid ${ThemePalette.grayscale[1]};
transition: background ${ThemeProps.animations.swift};
&:hover {
background: ${ThemePalette.grayscale[1]};
}
`;
const CheckboxStyled = styled(Checkbox)`
opacity: 0;
transition: all ${ThemeProps.animations.swift};
:focus {
opacity: 1;
}
`;
const Instance = styled.div`
display: flex;
align-items: center;
position: relative;
cursor: pointer;
${CheckboxStyled} {
${props => (props.selected ? "opacity: 1;" : "")}
}
&:hover ${CheckboxStyled} {
opacity: 1;
}
&:last-child ${InstanceContent} {
border-bottom: 1px solid ${ThemePalette.grayscale[1]};
}
`;
const LoadingText = styled.div`
margin-top: 38px;
font-size: 18px;
`;
const getOsImage = (osType?: string) => {
if (osType === "linux") {
return instanceLinuxImage;
}
if (osType === "windows") {
return instanceWindowsImage;
}
return instanceImage;
};
export const InstanceImage = styled.div<{ os?: string }>`
${ThemeProps.exactSize("48px")}
background: url('${props => getOsImage(props.os)}') center no-repeat;
`;
const Label = styled.div`
flex-grow: 1;
margin: 0 16px;
display: flex;
flex-direction: column;
`;
const LabelTitle = styled.div``;
const LabelSubtitle = styled.div`
color: ${ThemePalette.grayscale[4]};
overflow-wrap: anywhere;
`;
const Details = styled.div`
color: ${ThemePalette.grayscale[4]};
min-width: 160px;
text-align: right;
`;
const FiltersWrapper = styled.div`
padding: 8px 0 0 8px;
min-height: 24px;
margin-bottom: 16px;
display: flex;
justify-content: space-between;
`;
const SearchInputInfo = styled.div`
display: flex;
align-items: center;
`;
const FilterInfo = styled.div`
display: flex;
color: ${ThemePalette.grayscale[4]};
`;
const SelectionInfo = styled.div``;
const FilterSeparator = styled.div`
margin: 0 14px 0 16px;
`;
const Reloading = styled.div`
margin: 32px auto 0 auto;
flex-grow: 1;
`;
const SearchNotFound = styled.div`
display: flex;
flex-direction: column;
align-items: center;
${props => (props.marginTop ? "margin-top: 64px;" : "")}
> * {
margin-bottom: 42px;
}
`;
const SearchNotFoundText = styled.div`
font-size: 18px;
`;
const SearchNotFoundSubtitle = styled.div`
color: ${ThemePalette.grayscale[4]};
margin-top: -32px;
text-align: center;
`;
const BigInstanceImage = styled.div`
${ThemeProps.exactSize("96px")}
background: url('${bigInstanceImage}') center no-repeat;
`;
type Props = {
instances: InstanceType[];
selectedInstances?: InstanceType[] | null;
currentPage: number;
instancesPerPage: number;
loading: boolean;
chunksLoading: boolean;
searching: boolean;
searchNotFound: boolean;
reloading: boolean;
hasSourceOptions: boolean;
searchText?: string;
onSearchInputChange: (value: string) => void;
onReloadClick: () => void;
onInstanceClick: (instance: InstanceType) => void;
onPageClick: (page: number) => void;
};
type State = {
searchText: string;
};
@observer
class WizardInstances extends React.Component {
state = {
searchText: "",
};
timeout!: number;
isCheckboxMouseDown = false;
componentWillUnmount() {
this.props.onSearchInputChange("");
}
handleSeachInputChange(searchText: string) {
clearTimeout(this.timeout);
this.setState({ searchText });
this.timeout = window.setTimeout(() => {
this.props.onSearchInputChange(searchText);
}, 500);
}
handlePreviousPageClick() {
this.props.onPageClick(this.props.currentPage - 1);
}
handleNextPageClick() {
this.props.onPageClick(this.props.currentPage + 1);
}
areNoInstances() {
return (
!this.props.loading &&
!this.props.searchNotFound &&
!this.props.reloading &&
this.props.instances.length === 0 &&
!this.props.searching
);
}
renderNoInstances() {
if (!this.areNoInstances()) {
return null;
}
let subtitle;
if (this.props.hasSourceOptions) {
subtitle = (
Some platforms require pre-inputting parameters like location or
resource containers for listing instances.
Please check that all of the options from the previous screen are
correct.
);
} else {
subtitle = (
You can retry the search or choose another Endpoint
);
}
return (
It seems like you don’t have any Instances in this Endpoint
{subtitle}
);
}
renderSearchNotFound() {
if (!this.props.searchNotFound) {
return null;
}
let subtitle = null;
if (this.props.hasSourceOptions) {
subtitle = (
Some platforms require pre-inputting parameters like location or
resource containers for
listing instances. Please check that all of the options from the
previous screen are correct.
);
}
return (
Your search returned no results
{subtitle}
);
}
renderReloading() {
if (!this.props.reloading) {
return null;
}
return (
);
}
renderLoading() {
if (!this.props.loading) {
return null;
}
return (
Loading instances...
);
}
renderInstances() {
if (
this.props.loading ||
this.props.searchNotFound ||
this.props.reloading ||
this.areNoInstances()
) {
return null;
}
const startIdx = (this.props.currentPage - 1) * this.props.instancesPerPage;
const endIdx = startIdx + (this.props.instancesPerPage - 1);
const filteredInstances = this.props.instances.filter(
(_, idx) => idx >= startIdx && idx <= endIdx
);
return (
{filteredInstances.map(instance => {
const selected = Boolean(
this.props.selectedInstances &&
this.props.selectedInstances.find(i => i.id === instance.id)
);
const flavorName = instance.flavor_name
? ` | ${instance.flavor_name}`
: "";
const instanceId = instance.instance_name || instance.id;
return (
{
if (!this.isCheckboxMouseDown)
this.props.onInstanceClick(instance);
}}
selected={selected}
>
{
this.props.onInstanceClick(instance);
}}
onMouseDown={() => {
this.isCheckboxMouseDown = true;
}}
onMouseUp={() => {
this.isCheckboxMouseDown = false;
}}
/>
{`${instance.num_cpu} vCPU | ${mbToGbString(
instance.memory_mb
)} RAM${flavorName}`}
);
})}
);
}
renderFilters() {
if (this.props.loading || this.areNoInstances()) {
return null;
}
const count = this.props.selectedInstances
? this.props.selectedInstances.length
: 0;
const plural = count === 1 ? "" : "s";
return (
{
this.handleSeachInputChange(searchText);
}}
value={this.state.searchText}
loading={this.props.searching}
placeholder="Search VMs"
/>
{this.props.hasSourceOptions ? (
) : null}
{count} instance{plural} selected
|
{
this.props.onReloadClick();
}}
/>
);
}
renderPagination() {
if (
this.props.loading ||
this.props.searchNotFound ||
this.props.reloading ||
this.areNoInstances()
) {
return null;
}
const hasNextPage =
this.props.currentPage * this.props.instancesPerPage <
this.props.instances.length;
const areAllDisabled = this.props.searching;
const isPreviousDisabled = this.props.currentPage === 1 || areAllDisabled;
const isNextDisabled = !hasNextPage || areAllDisabled;
return (
{
this.handlePreviousPageClick();
}}
currentPage={this.props.currentPage}
totalPages={Math.ceil(
this.props.instances.length / this.props.instancesPerPage
)}
loading={this.props.chunksLoading}
nextDisabled={isNextDisabled}
onNextClick={() => {
this.handleNextPageClick();
}}
/>
);
}
render() {
return (
{this.renderFilters()}
{this.renderLoading()}
{this.renderReloading()}
{this.renderSearchNotFound()}
{this.renderInstances()}
{this.renderPagination()}
{this.renderNoInstances()}
);
}
}
export default WizardInstances;