| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- /*
- 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 styled, { css } from 'styled-components'
- import PropTypes from 'prop-types'
- import {
- Checkbox,
- SearchInput,
- ReloadButton,
- Arrow,
- StatusIcon,
- StatusImage,
- Button,
- } from 'components'
- import Palette from '../../styleUtils/Palette'
- import StyleProps from '../../styleUtils/StyleProps'
- 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 ${Palette.grayscale[1]};
- transition: background ${StyleProps.animations.swift};
- &:hover {
- background: ${Palette.grayscale[1]};
- }
- `
- const CheckboxStyled = styled(Checkbox) `
- opacity: 0;
- transition: all ${StyleProps.animations.swift};
- `
- 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 ${Palette.grayscale[1]};
- }
- `
- const LoadingText = styled.div`
- margin-top: 38px;
- font-size: 18px;
- `
- const Image = styled.div`
- width: 48px;
- height: 48px;
- background: url('${instanceImage}') center no-repeat;
- `
- const Label = styled.div`
- flex-grow: 1;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- margin: 0 16px;
- `
- const Details = styled.div`
- font-size: 14px;
- color: ${Palette.grayscale[4]};
- `
- const FiltersWrapper = styled.div`
- padding: 8px 0 0 8px;
- min-height: 24px;
- margin-bottom: 16px;
- display: flex;
- justify-content: space-between;
- `
- const FilterInfo = styled.div`
- display: flex;
- color: ${Palette.grayscale[4]};
- `
- const SelectionInfo = styled.div``
- const FilterSeparator = styled.div`
- margin: 0 14px 0 16px;
- `
- const Pagination = styled.div`
- display: flex;
- justify-content: center;
- margin: 32px 0 16px 0;
- flex-shrink: 0;
- `
- const Page = styled.div`
- width: 30px;
- height: 30px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid ${Palette.grayscale[3]};
- cursor: ${props => props.disabled ? 'default' : 'pointer'};
- ${props => props.previous ? css`
- border-top-left-radius: ${StyleProps.borderRadius};
- border-bottom-left-radius: ${StyleProps.borderRadius};
- padding-top: 2px;
- height: 28px;
- ` : ''}
- ${props => props.number ? css`
- border-top: 1px solid ${Palette.grayscale[3]};
- border-bottom: 1px solid ${Palette.grayscale[3]};
- border-left: 1px solid white;
- border-right: 1px solid white;
- cursor: default;
- ` : ''}
- ${props => props.next ? css`
- border-top-right-radius: ${StyleProps.borderRadius};
- border-bottom-right-radius: ${StyleProps.borderRadius};
- ` : ''}
- `
- 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;
- &:last-child {
- margin-bottom: 0;
- }
- }
- `
- const SearchNotFoundText = styled.div`
- font-size: 18px;
- `
- const SearchNotFoundSubtitle = styled.div`
- color: ${Palette.grayscale[4]};
- margin-top: -32px;
- `
- const BigInstanceImage = styled.div`
- ${StyleProps.exactSize('96px')}
- background: url('${bigInstanceImage}') center no-repeat;
- `
- class WizardInstances extends React.Component {
- static propTypes = {
- instances: PropTypes.array,
- selectedInstances: PropTypes.array,
- currentPage: PropTypes.number,
- loading: PropTypes.bool,
- searching: PropTypes.bool,
- searchNotFound: PropTypes.bool,
- loadingPage: PropTypes.bool,
- hasNextPage: PropTypes.bool,
- reloading: PropTypes.bool,
- onSearchInputChange: PropTypes.func,
- onNextPageClick: PropTypes.func,
- onPreviousPageClick: PropTypes.func,
- onReloadClick: PropTypes.func,
- onInstanceClick: PropTypes.func,
- }
- constructor() {
- super()
- this.state = {
- searchText: '',
- }
- }
- handleSeachInputChange(searchText) {
- clearTimeout(this.timeout)
- this.timeout = setTimeout(() => {
- this.setState({ searchText })
- this.props.onSearchInputChange(searchText)
- }, 500)
- }
- areNoInstances() {
- return !this.props.loading && !this.props.searchNotFound && !this.props.reloading && this.props.instances.length === 0
- }
- renderNoInstances() {
- if (!this.areNoInstances()) {
- return null
- }
- return (
- <SearchNotFound marginTop>
- <BigInstanceImage />
- <SearchNotFoundText>It seems like you don’t have any Instances in this Endpoint</SearchNotFoundText>
- <SearchNotFoundSubtitle>You can retry the search or choose another Endpoint</SearchNotFoundSubtitle>
- <Button hollow onClick={() => { this.props.onReloadClick(this.state.searchText) }}>Retry Search</Button>
- </SearchNotFound>
- )
- }
- renderSearchNotFound() {
- if (!this.props.searchNotFound) {
- return null
- }
- return (
- <SearchNotFound>
- <StatusImage status="ERROR" />
- <SearchNotFoundText>Your search returned no results</SearchNotFoundText>
- <Button hollow onClick={() => { this.props.onReloadClick(this.state.searchText) }}>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 />
- <LoadingText>Loading instances...</LoadingText>
- </LoadingWrapper>
- )
- }
- renderInstances() {
- if (this.props.loading || this.props.searchNotFound || this.props.reloading || this.areNoInstances()) {
- return null
- }
- return (
- <InstancesWrapper>
- {this.props.instances.map(instance => {
- let selected = Boolean(this.props.selectedInstances && this.props.selectedInstances.find(i => i.id === instance.id))
- let flavorName = instance.flavor_name ? ` | ${instance.flavor_name}` : ''
- return (
- <Instance
- key={instance.id}
- onClick={() => { this.props.onInstanceClick(instance) }}
- selected={selected}
- >
- <CheckboxStyled checked={selected} onChange={() => {}} />
- <InstanceContent>
- <Image />
- <Label>{instance.instance_name}</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
- }
- let count = this.props.selectedInstances ? this.props.selectedInstances.length : 0
- let plural = count === 1 ? '' : 's'
- return (
- <FiltersWrapper>
- <SearchInput
- alwaysOpen
- onChange={searchText => { this.handleSeachInputChange(searchText) }}
- loading={this.props.searching}
- placeholder="Search VMs"
- />
- <FilterInfo>
- <SelectionInfo>{count} instance{plural} selected</SelectionInfo>
- <FilterSeparator>|</FilterSeparator>
- <ReloadButton onClick={() => { this.props.onReloadClick(this.state.searchText) }} />
- </FilterInfo>
- </FiltersWrapper>
- )
- }
- renderPagination() {
- if (this.props.loading || this.props.searchNotFound || this.props.reloading || this.areNoInstances()) {
- return null
- }
- let areAllDisabled = this.props.searching || this.props.loadingPage
- let isPreviousDisabled = this.props.currentPage === 1 || areAllDisabled
- let isNextDisabled = !this.props.hasNextPage || areAllDisabled
- return (
- <Pagination>
- <Page
- previous
- disabled={isPreviousDisabled}
- onClick={() => { if (!isPreviousDisabled) { this.props.onPreviousPageClick() } }}
- >
- <Arrow orientation="left" disabled={isPreviousDisabled} />
- </Page>
- <Page number>
- {this.props.loadingPage ? <StatusIcon status="RUNNING" secondary /> : this.props.currentPage}
- </Page>
- <Page
- next
- onClick={() => { if (!isNextDisabled) { this.props.onNextPageClick(this.state.searchText) } }}
- disabled={isNextDisabled}
- >
- <Arrow disabled={isNextDisabled} />
- </Page>
- </Pagination>
- )
- }
- render() {
- return (
- <Wrapper>
- {this.renderFilters()}
- {this.renderLoading()}
- {this.renderReloading()}
- {this.renderSearchNotFound()}
- {this.renderInstances()}
- {this.renderPagination()}
- {this.renderNoInstances()}
- </Wrapper>
- )
- }
- }
- export default WizardInstances
|