/*
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 '../../../ui/Checkbox/Checkbox'
import ReloadButton from '../../../ui/ReloadButton/ReloadButton'
import StatusImage from '../../../ui/StatusComponents/StatusImage/StatusImage'
import Button from '../../../ui/Button/Button'
import SearchInput from '../../../ui/SearchInput/SearchInput'
import InfoIcon from '../../../ui/InfoIcon/InfoIcon'
import Pagination from '../../../ui/Pagination/Pagination'
import { ThemePalette, ThemeProps } from '../../../Theme'
import type { Instance as InstanceType } from '../../../../@types/Instance'
import instanceImage from './images/instance.svg'
import bigInstanceImage from './images/instance-big.svg'
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;
`
export const Image = styled.div`
${ThemeProps.exactSize('48px')}
background: url('${instanceImage}') 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: boolean = false
componentWillUnmount() {
this.props.onSearchInputChange('')
}
handleSeachInputChange(searchText: string) {
clearTimeout(this.timeout)
this.setState({ searchText })
this.timeout = 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}
data-test-id={`wInstances-item-${instance.id}`}
>
{ this.props.onInstanceClick(instance) }}
onMouseDown={() => { this.isCheckboxMouseDown = true }}
onMouseUp={() => { this.isCheckboxMouseDown = false }}
/>
{`${instance.num_cpu} vCPU | ${instance.memory_mb} 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"
data-test-id="wInstances-searchInput"
/>
{this.props.hasSourceOptions ? (
) : null}
{count} instance{plural} selected
|
{ this.props.onReloadClick() }}
data-test-id="wInstances-reloadButton"
/>
)
}
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