WizardSummary.jsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import React from 'react'
  15. import styled from 'styled-components'
  16. import PropTypes from 'prop-types'
  17. import moment from 'moment'
  18. import { StatusPill } from 'components'
  19. import StyleProps from '../../styleUtils/StyleProps'
  20. import Palette from '../../styleUtils/Palette'
  21. import LabelDictionary from '../../../utils/LabelDictionary'
  22. import DateUtils from '../../../utils/DateUtils'
  23. import networkArrowImage from './images/network-arrow.svg'
  24. const Wrapper = styled.div`
  25. width: 100%;
  26. display: flex;
  27. `
  28. const Column = styled.div`
  29. width: 50%;
  30. &:first-child {
  31. margin-right: 160px;
  32. }
  33. `
  34. const Section = styled.div`
  35. margin-bottom: 42px;
  36. &:last-child {
  37. margin-bottom: 0;
  38. }
  39. `
  40. const SectionTitle = styled.div`
  41. font-size: 24px;
  42. font-weight: ${StyleProps.fontWeights.light};
  43. margin-bottom: 16px;
  44. `
  45. const Overview = styled.div``
  46. const OverviewLabel = styled.div`
  47. font-size: 10px;
  48. font-weight: ${StyleProps.fontWeights.medium};
  49. text-transform: uppercase;
  50. color: ${Palette.grayscale[5]};
  51. margin-bottom: 4px;
  52. `
  53. const OverviewRow = styled.div`
  54. margin-bottom: 32px;
  55. &:last-child {
  56. margin-bottom: 0;
  57. }
  58. `
  59. const OverviewRowData = styled.div`
  60. display: flex;
  61. `
  62. const OverviewRowLabel = styled.div`
  63. margin-left: 16px;
  64. white-space: nowrap;
  65. overflow: hidden;
  66. text-overflow: ellipsis;
  67. `
  68. const Table = styled.div``
  69. const Row = styled.div`
  70. display: flex;
  71. flex-direction: ${props => props.direction || 'column'};
  72. padding: 8px 0;
  73. border-top: 1px solid ${Palette.grayscale[1]};
  74. color: ${Palette.grayscale[4]};
  75. &:last-child {
  76. border-bottom: 1px solid ${Palette.grayscale[1]};
  77. }
  78. `
  79. const InstanceRowTitle = styled.div`
  80. margin-bottom: 4px;
  81. `
  82. const InstanceRowSubtitle = styled.div`
  83. font-size: 10px;
  84. color: ${Palette.grayscale[5]};
  85. `
  86. const SourceNetwork = styled.div`
  87. width: 50%;
  88. margin-right: 16px;
  89. `
  90. const NetworkArrow = styled.div`
  91. width: 32px;
  92. height: 16px;
  93. background: url('${networkArrowImage}') center no-repeat;
  94. `
  95. const TargetNetwork = styled.div`
  96. width: 50%;
  97. text-align: right;
  98. margin-left: 20px;
  99. `
  100. const OptionsList = styled.div``
  101. const Option = styled.div`
  102. display: flex;
  103. margin-bottom: 8px;
  104. `
  105. const OptionLabel = styled.div`
  106. color: ${Palette.grayscale[4]};
  107. flex-grow: 1;
  108. `
  109. const OptionValue = styled.div``
  110. class WizardSummary extends React.Component {
  111. static propTypes = {
  112. data: PropTypes.object,
  113. wizardType: PropTypes.string,
  114. }
  115. getDefaultOption(fieldName) {
  116. if (this.props.data.options && this.props.data.options[fieldName] === false) {
  117. return false
  118. }
  119. return true
  120. }
  121. renderScheduleLabel(schedule) {
  122. let monthLabel
  123. if (schedule.month === null || schedule.month === undefined) {
  124. monthLabel = 'Every month'
  125. } else {
  126. monthLabel = `Every ${moment.months()[schedule.month - 1]}`
  127. }
  128. let dayOfMonthLabel
  129. if (schedule.dom === null || schedule.dom === undefined) {
  130. dayOfMonthLabel = 'every day'
  131. } else {
  132. dayOfMonthLabel = `every ${DateUtils.getOrdinalDay(schedule.dom)}`
  133. }
  134. let dayOfWeekLabel
  135. if (schedule.dow === null || schedule.dow === undefined) {
  136. dayOfWeekLabel = 'every weekday'
  137. } else {
  138. dayOfWeekLabel = `every ${moment.weekdays(true)[schedule.dow]}`
  139. }
  140. let padNumber = number => number < 10 ? `0${number}` : number
  141. let timeLabel
  142. if (schedule.minute === null || schedule.minute === undefined) {
  143. if (schedule.hour === null || schedule.hour === undefined) {
  144. timeLabel = 'every hour, every minute'
  145. } else {
  146. timeLabel = `at ${padNumber(schedule.hour)} o'clock, every minute`
  147. }
  148. } else if (schedule.hour === null || schedule.hour === undefined) {
  149. timeLabel = `every hour, at minute ${padNumber(schedule.minute)}`
  150. } else {
  151. timeLabel = `at ${padNumber(schedule.hour)}:${padNumber(schedule.minute)}`
  152. }
  153. return `${monthLabel}, ${dayOfMonthLabel}, ${dayOfWeekLabel}, ${timeLabel}`
  154. }
  155. renderScheduleSection() {
  156. let schedules = this.props.data.schedules
  157. if (this.props.wizardType !== 'replica' || !schedules || schedules.length === 0) {
  158. return null
  159. }
  160. return (
  161. <Section>
  162. <SectionTitle>Schedule</SectionTitle>
  163. <Table>
  164. {schedules.map(schedule => {
  165. return (
  166. <Row key={schedule.id} schedule>
  167. {this.renderScheduleLabel(schedule.schedule || {})}
  168. </Row>
  169. )
  170. })}
  171. </Table>
  172. </Section>
  173. )
  174. }
  175. renderOptionValue(value) {
  176. if (value === true) {
  177. return 'Yes'
  178. }
  179. if (value === false) {
  180. return 'No'
  181. }
  182. return value
  183. }
  184. renderOptionsSection() {
  185. let data = this.props.data
  186. let type = this.props.wizardType.charAt(0).toUpperCase() + this.props.wizardType.substr(1)
  187. let executeNowOption = (
  188. <Option>
  189. <OptionLabel>Execute now?</OptionLabel>
  190. <OptionValue>{this.renderOptionValue(this.getDefaultOption('execute_now'))}</OptionValue>
  191. </Option>
  192. )
  193. let separateVmOption = (
  194. <Option>
  195. <OptionLabel>Separate {type}/VM?</OptionLabel>
  196. <OptionValue>{this.renderOptionValue(this.getDefaultOption('separate_vm'))}</OptionValue>
  197. </Option>
  198. )
  199. return (
  200. <Section>
  201. <SectionTitle>{type} Options</SectionTitle>
  202. <OptionsList>
  203. {this.props.wizardType === 'replica' ? executeNowOption : null}
  204. {this.props.data.selectedInstances.length > 1 ? separateVmOption : null}
  205. {data.options ? Object.keys(data.options).map(optionName => {
  206. if (optionName === 'execute_now' || optionName === 'separate_vm'
  207. || data.options[optionName] === null || data.options[optionName] === undefined) {
  208. return null
  209. }
  210. return (
  211. <Option key={optionName}>
  212. <OptionLabel>{LabelDictionary.get(optionName)}</OptionLabel>
  213. <OptionValue>{this.renderOptionValue(data.options[optionName])}</OptionValue>
  214. </Option>
  215. )
  216. }) : null}
  217. </OptionsList>
  218. </Section>
  219. )
  220. }
  221. renderNetworksSection() {
  222. let data = this.props.data
  223. if (data.networks === null || data.networks === undefined) {
  224. return null
  225. }
  226. return (
  227. <Section>
  228. <SectionTitle>Networks</SectionTitle>
  229. <Table>
  230. {data.networks.map(mapping => {
  231. return (
  232. <Row key={mapping.sourceNic.network_name} direction="row">
  233. <SourceNetwork>{mapping.sourceNic.network_name}</SourceNetwork>
  234. <NetworkArrow />
  235. <TargetNetwork>{mapping.targetNetwork.name}</TargetNetwork>
  236. </Row>
  237. )
  238. })}
  239. </Table>
  240. </Section>
  241. )
  242. }
  243. renderInstancesSection() {
  244. let data = this.props.data
  245. return (
  246. <Section>
  247. <SectionTitle>Instances</SectionTitle>
  248. <Table>
  249. {data.selectedInstances.map(instance => {
  250. let flavorName = instance.flavor_name ? `/${instance.flavor_name}` : ''
  251. return (
  252. <Row key={instance.id}>
  253. <InstanceRowTitle>{instance.name}</InstanceRowTitle>
  254. <InstanceRowSubtitle>{`${instance.num_cpu}vCPU/${instance.memory_mb}MB${flavorName}`}</InstanceRowSubtitle>
  255. </Row>
  256. )
  257. })}
  258. </Table>
  259. </Section>
  260. )
  261. }
  262. renderOverviewSection() {
  263. let data = this.props.data
  264. let type = this.props.wizardType.charAt(0).toUpperCase() + this.props.wizardType.substr(1)
  265. return (
  266. <Section>
  267. <SectionTitle>Overview</SectionTitle>
  268. <Overview>
  269. <OverviewRow>
  270. <OverviewLabel>Source</OverviewLabel>
  271. <OverviewRowData>
  272. <StatusPill secondary small label={LabelDictionary.get(data.source.type).toUpperCase()} />
  273. <OverviewRowLabel>{data.source.name}</OverviewRowLabel>
  274. </OverviewRowData>
  275. </OverviewRow>
  276. <OverviewRow>
  277. <OverviewLabel>Target</OverviewLabel>
  278. <OverviewRowData>
  279. <StatusPill secondary small label={LabelDictionary.get(data.target.type).toUpperCase()} />
  280. <OverviewRowLabel>{data.target.name}</OverviewRowLabel>
  281. </OverviewRowData>
  282. </OverviewRow>
  283. <OverviewRow>
  284. <OverviewLabel>Type</OverviewLabel>
  285. <OverviewRowData>
  286. <StatusPill alert small label={this.props.wizardType.toUpperCase()} />
  287. <OverviewRowLabel>Coriolis {type}</OverviewRowLabel>
  288. </OverviewRowData>
  289. </OverviewRow>
  290. </Overview>
  291. </Section>
  292. )
  293. }
  294. render() {
  295. return (
  296. <Wrapper>
  297. <Column>
  298. {this.renderOverviewSection()}
  299. {this.renderInstancesSection()}
  300. {this.renderNetworksSection()}
  301. </Column>
  302. <Column>
  303. {this.renderOptionsSection()}
  304. {this.renderScheduleSection()}
  305. </Column>
  306. </Wrapper>
  307. )
  308. }
  309. }
  310. export default WizardSummary