| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- /*
- 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 <http://www.gnu.org/licenses/>.
- */
- 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<any>`
- width: 100%;
- display: flex;
- flex-direction: column;
- `
- const LoadingWrapper = styled.div<any>`
- margin-top: 32px;
- display: flex;
- flex-direction: column;
- align-items: center;
- `
- const InstancesWrapper = styled.div<any>`
- margin-left: -32px;
- flex-grow: 1;
- overflow: auto;
- `
- const InstanceContent = styled.div<any>`
- 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<any>`
- 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<any>`
- margin-top: 38px;
- font-size: 18px;
- `
- export const Image = styled.div<any>`
- ${ThemeProps.exactSize('48px')}
- background: url('${instanceImage}') center no-repeat;
- `
- const Label = styled.div<any>`
- 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<any>`
- color: ${ThemePalette.grayscale[4]};
- min-width: 160px;
- text-align: right;
- `
- const FiltersWrapper = styled.div<any>`
- padding: 8px 0 0 8px;
- min-height: 24px;
- margin-bottom: 16px;
- display: flex;
- justify-content: space-between;
- `
- const SearchInputInfo = styled.div<any>`
- display: flex;
- align-items: center;
- `
- const FilterInfo = styled.div<any>`
- display: flex;
- color: ${ThemePalette.grayscale[4]};
- `
- const SelectionInfo = styled.div<any>``
- const FilterSeparator = styled.div<any>`
- margin: 0 14px 0 16px;
- `
- const Reloading = styled.div<any>`
- margin: 32px auto 0 auto;
- flex-grow: 1;
- `
- const SearchNotFound = styled.div<any>`
- display: flex;
- flex-direction: column;
- align-items: center;
- ${props => (props.marginTop ? 'margin-top: 64px;' : '')}
- > * {
- margin-bottom: 42px;
- }
- `
- const SearchNotFoundText = styled.div<any>`
- font-size: 18px;
- `
- const SearchNotFoundSubtitle = styled.div<any>`
- color: ${ThemePalette.grayscale[4]};
- margin-top: -32px;
- text-align: center;
- `
- const BigInstanceImage = styled.div<any>`
- ${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<Props, State> {
- 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 = (
- <SearchNotFoundSubtitle>
- Some platforms require pre-inputting parameters
- like location or resource containers for listing instances.
- <br />Please check that all of the options from the previous screen are correct.
- </SearchNotFoundSubtitle>
- )
- } else {
- subtitle = (
- <SearchNotFoundSubtitle>
- You can retry the search or choose another Endpoint
- </SearchNotFoundSubtitle>
- )
- }
- return (
- <SearchNotFound marginTop>
- <BigInstanceImage />
- <SearchNotFoundText>
- It seems like you don’t have any Instances in this Endpoint
- </SearchNotFoundText>
- {subtitle}
- <Button hollow onClick={() => { this.props.onReloadClick() }}>Retry Search</Button>
- </SearchNotFound>
- )
- }
- renderSearchNotFound() {
- if (!this.props.searchNotFound) {
- return null
- }
- let subtitle = null
- if (this.props.hasSourceOptions) {
- subtitle = (
- <SearchNotFoundSubtitle>
- Some platforms require pre-inputting parameters like location or resource containers for
- <br />listing instances.
- Please check that all of the options from the previous screen are correct.
- </SearchNotFoundSubtitle>
- )
- }
- return (
- <SearchNotFound>
- <StatusImage status="ERROR" />
- <SearchNotFoundText data-test-id="wInstances-notFoundText">Your search returned no results</SearchNotFoundText>
- {subtitle}
- <Button hollow onClick={() => { this.props.onReloadClick() }}>Retry</Button>
- </SearchNotFound>
- )
- }
- renderReloading() {
- if (!this.props.reloading) {
- return null
- }
- return (
- <Reloading>
- <StatusImage loading />
- </Reloading>
- )
- }
- renderLoading() {
- if (!this.props.loading) {
- return null
- }
- return (
- <LoadingWrapper>
- <StatusImage loading data-test-id="wInstances-loadingStatus" />
- <LoadingText>Loading instances...</LoadingText>
- </LoadingWrapper>
- )
- }
- 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 (
- <InstancesWrapper>
- {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 (
- <Instance
- key={instance.id}
- onMouseDown={() => {
- if (!this.isCheckboxMouseDown) this.props.onInstanceClick(instance)
- }}
- selected={selected}
- data-test-id={`wInstances-item-${instance.id}`}
- >
- <CheckboxStyled
- checked={selected}
- onChange={() => { this.props.onInstanceClick(instance) }}
- onMouseDown={() => { this.isCheckboxMouseDown = true }}
- onMouseUp={() => { this.isCheckboxMouseDown = false }}
- />
- <InstanceContent data-test-id="wInstances-instanceItem">
- <Image />
- <Label>
- <LabelTitle>{instance.name}</LabelTitle>
- {instanceId !== instance.name ? (
- <LabelSubtitle>{instanceId}</LabelSubtitle>
- ) : null}
- </Label>
- <Details>{`${instance.num_cpu} vCPU | ${instance.memory_mb} MB RAM${flavorName}`}</Details>
- </InstanceContent>
- </Instance>
- )
- })}
- </InstancesWrapper>
- )
- }
- 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 (
- <FiltersWrapper>
- <SearchInputInfo>
- <SearchInput
- alwaysOpen
- onChange={searchText => { this.handleSeachInputChange(searchText) }}
- value={this.state.searchText}
- loading={this.props.searching}
- placeholder="Search VMs"
- data-test-id="wInstances-searchInput"
- />
- {this.props.hasSourceOptions ? (
- <InfoIcon
- text="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."
- marginBottom={0}
- marginLeft={8}
- filled
- />
- ) : null}
- </SearchInputInfo>
- <FilterInfo>
- <SelectionInfo data-test-id="wInstances-selInfo">{count} instance{plural} selected</SelectionInfo>
- <FilterSeparator>|</FilterSeparator>
- <ReloadButton
- onClick={() => { this.props.onReloadClick() }}
- data-test-id="wInstances-reloadButton"
- />
- </FilterInfo>
- </FiltersWrapper>
- )
- }
- 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 (
- <Pagination
- style={{ margin: '32px 0 16px 0' }}
- previousDisabled={isPreviousDisabled}
- onPreviousClick={() => { 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 (
- <Wrapper>
- {this.renderFilters()}
- {this.renderLoading()}
- {this.renderReloading()}
- {this.renderSearchNotFound()}
- {this.renderInstances()}
- {this.renderPagination()}
- {this.renderNoInstances()}
- </Wrapper>
- )
- }
- }
- export default WizardInstances
|