diff --git a/src/features/linodes/LinodesCreate/CALinodeCreate.tsx b/src/features/linodes/LinodesCreate/CALinodeCreate.tsx
index d243849e532..7de930a863a 100644
--- a/src/features/linodes/LinodesCreate/CALinodeCreate.tsx
+++ b/src/features/linodes/LinodesCreate/CALinodeCreate.tsx
@@ -9,6 +9,7 @@ import ErrorState from 'src/components/ErrorState';
import Grid from 'src/components/Grid';
import { getStackScriptsByUser } from 'src/features/StackScripts/stackScriptUtils';
import SubTabs, { Tab } from './CALinodeCreateSubTabs';
+import FromBackupsContent from './TabbedContent/FromBackupsContent';
import FromImageContent from './TabbedContent/FromImageContent';
import FromLinodeContent from './TabbedContent/FromLinodeContent';
import FromStackScriptContent from './TabbedContent/FromStackScriptContent';
@@ -180,7 +181,33 @@ export class LinodeCreate extends React.PureComponent<
title: 'Backups',
type: 'fromBackup',
render: () => {
- return ;
+ const {
+ history,
+ handleSelectUDFs,
+ selectedUDFs,
+ updateStackScript,
+ availableStackScriptImages,
+ availableUserDefinedFields,
+ selectedStackScriptID,
+ selectedStackScriptUsername,
+ selectedStackScriptLabel,
+ linodesLoading,
+ updateDiskSize,
+ updatePassword,
+ ...rest
+ } = this.props;
+ return (
+
+ );
}
},
{
diff --git a/src/features/linodes/LinodesCreate/LinodeCreateContainer.tsx b/src/features/linodes/LinodesCreate/LinodeCreateContainer.tsx
index 36b80b482da..c155cb5b14e 100644
--- a/src/features/linodes/LinodesCreate/LinodeCreateContainer.tsx
+++ b/src/features/linodes/LinodesCreate/LinodeCreateContainer.tsx
@@ -59,6 +59,7 @@ interface State {
selectedRegionID?: string;
selectedTypeID?: string;
selectedLinodeID?: number;
+ selectedBackupID?: number;
availableUserDefinedFields?: Linode.StackScript.UserDefinedField[];
availableStackScriptImages?: Linode.Image[];
selectedStackScriptID?: number;
@@ -91,6 +92,7 @@ const defaultState: State = {
label: '',
password: '',
selectedImageID: 'linode/debian9',
+ selectedBackupID: undefined,
selectedDiskSize: undefined,
selectedLinodeID: undefined,
selectedStackScriptID: undefined,
@@ -100,6 +102,14 @@ const defaultState: State = {
formIsSubmitting: false
};
+const getRegionIDFromLinodeID = (
+ linodes: Linode.Linode[],
+ id: number
+): string | undefined => {
+ const thisLinode = linodes.find(linode => linode.id === id);
+ return thisLinode ? thisLinode.region : undefined;
+};
+
class LinodeCreateContainer extends React.PureComponent {
state: State = defaultState;
@@ -130,6 +140,10 @@ class LinodeCreateContainer extends React.PureComponent {
return this.setState({ selectedImageID: id });
};
+ setBackupID = (id: number) => {
+ this.setState({ selectedBackupID: id });
+ };
+
setRegionID = (id: string) => this.setState({ selectedRegionID: id });
setTypeID = (id: string) => this.setState({ selectedTypeID: id });
@@ -139,12 +153,22 @@ class LinodeCreateContainer extends React.PureComponent {
/**
* reset selected plan and set the selectedDiskSize
* for the purpose of disabling plans that are smaller
- * than the clone source
+ * than the clone source.
+ *
+ * Also, when creating from backup, we set the region
+ * to the same region as the Linode that owns the backup,
+ * since the API does not infer this automatically.
*/
+
+ const selectedRegionID = getRegionIDFromLinodeID(
+ this.props.linodesData,
+ id
+ );
this.setState({
selectedLinodeID: id,
selectedDiskSize: diskSize,
- selectedTypeID: undefined
+ selectedTypeID: undefined,
+ selectedRegionID
});
}
};
@@ -239,7 +263,20 @@ class LinodeCreateContainer extends React.PureComponent {
);
}
- if (createType === 'fromStackScript' && !this.state.selectedStackScriptID) {
+ if (type === 'createFromBackup' && !this.state.selectedBackupID) {
+ /* a backup selection is also required */
+ this.setState(
+ {
+ errors: [{ field: 'backup_id', reason: 'You must select a Backup' }]
+ },
+ () => {
+ scrollErrorIntoView();
+ }
+ );
+ return;
+ }
+
+ if (type === 'createFromStackScript' && !this.state.selectedStackScriptID) {
return this.setState(
() => ({
errors: [
@@ -431,6 +468,8 @@ class LinodeCreateContainer extends React.PureComponent {
resetCreationState={this.clearCreationState}
userSSHKeys={this.props.userSSHKeys}
resetSSHKeys={this.props.resetSSHKeys}
+ selectedBackupID={this.state.selectedBackupID}
+ setBackupID={this.setBackupID}
/>
diff --git a/src/features/linodes/LinodesCreate/SelectLinodePanel.tsx b/src/features/linodes/LinodesCreate/SelectLinodePanel.tsx
index 4749f8220a5..9599711ae85 100644
--- a/src/features/linodes/LinodesCreate/SelectLinodePanel.tsx
+++ b/src/features/linodes/LinodesCreate/SelectLinodePanel.tsx
@@ -38,7 +38,7 @@ const styles: StyleRulesCallback = theme => ({
interface Props {
linodes: ExtendedLinode[];
selectedLinodeID?: number;
- handleSelection: (linode: Linode.Linode) => void;
+ handleSelection: (id: number, diskSize?: number) => void;
error?: string;
header?: string;
disabled?: boolean;
@@ -55,7 +55,7 @@ class SelectLinodePanel extends React.Component {
{
- handleSelection(linode);
+ handleSelection(linode.id, linode.specs.disk);
}}
checked={linode.id === Number(selectedLinodeID)}
heading={linode.heading}
diff --git a/src/features/linodes/LinodesCreate/TabbedContent/FromBackupsContent.tsx b/src/features/linodes/LinodesCreate/TabbedContent/FromBackupsContent.tsx
index 5ccaa5375fd..00a865cedc9 100644
--- a/src/features/linodes/LinodesCreate/TabbedContent/FromBackupsContent.tsx
+++ b/src/features/linodes/LinodesCreate/TabbedContent/FromBackupsContent.tsx
@@ -1,545 +1,387 @@
-// import * as Promise from 'bluebird';
-// import { InjectedNotistackProps, withSnackbar } from 'notistack';
-// import { compose as ramdaCompose, pathOr } from 'ramda';
-// import * as React from 'react';
-// import { Sticky, StickyProps } from 'react-sticky';
-// import { compose } from 'recompose';
-// import VolumeIcon from 'src/assets/addnewmenu/volume.svg';
-// import CheckoutBar from 'src/components/CheckoutBar';
-// import CircleProgress from 'src/components/CircleProgress';
-// import {
-// StyleRulesCallback,
-// withStyles,
-// WithStyles
-// } from 'src/components/core/styles';
-// import CreateLinodeDisabled from 'src/components/CreateLinodeDisabled';
-// import Grid from 'src/components/Grid';
-// import LabelAndTagsPanel from 'src/components/LabelAndTagsPanel';
-// import Notice from 'src/components/Notice';
-// import Placeholder from 'src/components/Placeholder';
-// import { Tag } from 'src/components/TagsInput';
-// import { resetEventsPolling } from 'src/events';
-// import { getLinodeBackups } from 'src/services/linodes';
-// import {
-// LinodeActionsProps,
-// withLinodeActions
-// } from 'src/store/linodes/linode.containers';
-// import { allocatePrivateIP } from 'src/utilities/allocateIPAddress';
-// import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
-// import getAPIErrorsFor from 'src/utilities/getAPIErrorFor';
-// import getLinodeInfo from 'src/utilities/getLinodeInfo';
-// import scrollErrorIntoView from 'src/utilities/scrollErrorIntoView';
-// import { aggregateBackups } from '../../LinodesDetail/LinodeBackup';
-// import AddonsPanel from '../AddonsPanel';
-// import SelectBackupPanel from '../SelectBackupPanel';
-// import SelectLinodePanel, { ExtendedLinode } from '../SelectLinodePanel';
-// import SelectPlanPanel, { ExtendedType } from '../SelectPlanPanel';
-// import { Info } from '../util';
-// import withLabelGenerator, { LabelProps } from '../withLabelGenerator';
-// import { renderBackupsDisplaySection } from './utils';
-
-// type ClassNames = 'root' | 'main' | 'sidebar';
-
-// const styles: StyleRulesCallback = theme => ({
-// root: {},
-// main: {},
-// sidebar: {
-// [theme.breakpoints.up('lg')]: {
-// marginTop: -130
-// }
-// }
-// });
-
-// export type TypeInfo =
-// | {
-// title: string;
-// details: string;
-// monthly: number;
-// backupsMonthly: number | null;
-// }
-// | undefined;
-
-// interface Props {
-// notice?: Notice;
-// linodes: Linode.Linode[];
-// types: ExtendedType[];
-// extendLinodes: (linodes: Linode.Linode[]) => ExtendedLinode[];
-// getBackupsMonthlyPrice: (selectedTypeID: string | null) => number | null;
-// getTypeInfo: (selectedTypeID: string | null) => TypeInfo;
-// getRegionInfo: (selectedRegionID: string | null) => Info;
-// history: any;
-// selectedBackupFromQuery?: number;
-// selectedLinodeFromQuery?: number;
-// selectedRegionIDFromLinode?: string;
-// accountBackups: boolean;
-// disabled?: boolean;
-// }
-
-// interface State {
-// linodesWithBackups: Linode.LinodeWithBackups[] | null;
-// isGettingBackups: boolean;
-// userHasBackups: boolean;
-// selectedLinodeID: number | undefined;
-// selectedBackupID: number | undefined;
-// selectedDiskSize: number | undefined;
-// selectedRegionID: string | null;
-// selectedTypeID: string | null;
-// label: string;
-// errors?: Linode.ApiFieldError[];
-// backups: boolean;
-// privateIP: boolean;
-// selectedBackupInfo: Info;
-// isMakingRequest: boolean;
-// backupInfo: Info;
-// tags: Tag[];
-// }
-
-// type CombinedProps = Props &
-// LinodeActionsProps &
-// InjectedNotistackProps &
-// LabelProps &
-// WithStyles;
-
-// interface Notice {
-// text: string;
-// level: 'warning' | 'error'; // most likely only going to need these two
-// }
-
-// const errorResources = {
-// type: 'A plan selection',
-// region: 'A region selection',
-// label: 'A label',
-// root_pass: 'A root password',
-// tags: 'Tags for this Linode'
-// };
-
-// const filterLinodesWithBackups = (linodes: Linode.LinodeWithBackups[]) => {
-// return linodes.filter(linode => {
-// const hasAutomaticBackups = !!linode.currentBackups.automatic.length;
-// const hasSnapshotBackup = !!linode.currentBackups.snapshot.current;
-// // backups both need to be enabled and some backups need to exist
-// // for the panel to show the Linode
-// return linode.backups.enabled && (hasAutomaticBackups || hasSnapshotBackup);
-// });
-// };
-
-// export class FromBackupsContent extends React.Component {
-// state: State = {
-// linodesWithBackups: [],
-// isGettingBackups: false,
-// userHasBackups: false,
-// selectedLinodeID: this.props.selectedLinodeFromQuery || undefined,
-// selectedBackupID: this.props.selectedBackupFromQuery || undefined,
-// selectedDiskSize: undefined,
-// selectedRegionID: this.props.selectedRegionIDFromLinode || null,
-// selectedTypeID: null,
-// label: '',
-// backups: false,
-// privateIP: false,
-// selectedBackupInfo: undefined,
-// isMakingRequest: false,
-// backupInfo: undefined,
-// tags: []
-// };
-
-// mounted: boolean = false;
-
-// getLinodesWithBackups = (linodes: Linode.Linode[]) => {
-// this.setState({ isGettingBackups: true });
-// return Promise.map(
-// linodes.filter(l => l.backups.enabled),
-// (linode: Linode.Linode) => {
-// return getLinodeBackups(linode.id).then(backups => {
-// return {
-// ...linode,
-// currentBackups: {
-// ...backups
-// }
-// };
-// });
-// }
-// )
-// .then(data => {
-// if (!this.mounted) {
-// return;
-// }
-// this.setState({ linodesWithBackups: data, isGettingBackups: false });
-// })
-// .catch(err => this.setState({ isGettingBackups: false }));
-// };
-
-// userHasBackups = () => {
-// const { linodesWithBackups } = this.state;
-// return linodesWithBackups!.some((linode: Linode.LinodeWithBackups) => {
-// // automatic backups is an array, but snapshots are either null or an object
-// // user can have up to 3 automatic backups, but one one snapshot
-// return (
-// !!linode.currentBackups.automatic.length ||
-// !!linode.currentBackups.snapshot.current
-// );
-// });
-// };
-
-// handleSelectLinode = (linode: Linode.Linode) => {
-// if (linode.id !== this.state.selectedLinodeID) {
-// this.setState({
-// selectedLinodeID: linode.id,
-// selectedTypeID: null,
-// selectedRegionID: linode.region,
-// selectedDiskSize: linode.specs.disk,
-// selectedBackupID: undefined
-// });
-// }
-// };
-
-// handleSelectBackupID = (id: number) => {
-// this.setState({ selectedBackupID: id });
-// };
-
-// handleSelectBackupInfo = (info: Info) => {
-// this.setState({ backupInfo: info });
-// };
-
-// handleSelectPlan = (id: string) => {
-// this.setState({ selectedTypeID: id });
-// };
-
-// handleSelectLabel = (e: any) => {
-// this.setState({ label: e.target.value });
-// };
-
-// handleChangeTags = (selected: Tag[]) => {
-// this.setState({ tags: selected });
-// };
-
-// handleToggleBackups = () => {
-// this.setState({ backups: !this.state.backups });
-// };
-
-// handleTogglePrivateIP = () => {
-// this.setState({ privateIP: !this.state.privateIP });
-// };
-
-// deployLinode = () => {
-// if (!this.state.selectedBackupID) {
-// /* a backup selection is also required */
-// this.setState(
-// {
-// errors: [{ field: 'backup_id', reason: 'You must select a Backup' }]
-// },
-// () => {
-// scrollErrorIntoView();
-// }
-// );
-// return;
-// }
-// this.createLinode();
-// };
-
-// createLinode = () => {
-// const {
-// history,
-// linodeActions: { createLinode }
-// } = this.props;
-// const {
-// selectedRegionID,
-// selectedTypeID,
-// backups,
-// privateIP,
-// selectedBackupID,
-// tags
-// } = this.state;
-
-// this.setState({ isMakingRequest: true });
-
-// const label = this.label();
-
-// createLinode({
-// region: selectedRegionID,
-// type: selectedTypeID,
-// backup_id: Number(selectedBackupID),
-// label: label ? label : null /* optional */,
-// backups_enabled: backups /* optional */,
-// booted: true,
-// tags: tags.map((item: Tag) => item.value)
-// })
-// .then(linode => {
-// if (privateIP) {
-// allocatePrivateIP(linode.id);
-// }
-
-// this.props.enqueueSnackbar(`Your Linode ${label} is being created.`, {
-// variant: 'success'
-// });
-
-// resetEventsPolling();
-// history.push('/linodes');
-// })
-// .catch(error => {
-// if (!this.mounted) {
-// return;
-// }
-
-// this.setState(() => ({
-// errors: getAPIErrorOrDefault(error)
-// }));
-// })
-// .finally(() => {
-// if (!this.mounted) {
-// return;
-// }
-// // regardless of whether request failed or not, change state and enable the submit btn
-// this.setState({ isMakingRequest: false });
-// });
-// };
-
-// componentWillUnmount() {
-// this.mounted = false;
-// }
-
-// componentDidMount() {
-// this.mounted = true;
-// this.getLinodesWithBackups(this.props.linodes);
-// const { selectedLinodeID } = this.state;
-// // If there is a selected Linode ID (from props), make sure its information
-// // is set to state as if it had been selected manually.
-// if (selectedLinodeID) {
-// const selectedLinode = getLinodeInfo(
-// selectedLinodeID,
-// this.props.linodes
-// );
-// if (selectedLinode) {
-// this.setState({
-// selectedLinodeID: selectedLinode.id,
-// selectedTypeID: null,
-// selectedRegionID: selectedLinode.region,
-// selectedDiskSize: selectedLinode.specs.disk
-// });
-// }
-// }
-// }
-
-// // Generate a default label name with a selected Linode and/or Backup name IF they are selected
-// label = () => {
-// const {
-// linodesWithBackups,
-// selectedBackupID,
-// selectedLinodeID
-// } = this.state;
-// const { getLabel } = this.props;
-
-// const selectedLinode =
-// linodesWithBackups &&
-// linodesWithBackups.find(l => l.id === selectedLinodeID);
-
-// if (!selectedLinode) {
-// return getLabel();
-// }
-
-// const selectedBackup = aggregateBackups(selectedLinode.currentBackups).find(
-// b => b.id === selectedBackupID
-// );
-
-// if (!selectedBackup) {
-// return getLabel(selectedLinode.label, 'backup');
-// }
-
-// const backup =
-// selectedBackup.type !== 'auto' ? selectedBackup.label : 'auto'; // automatic backups have a label of 'null', so use a custom string for these
-
-// return getLabel(selectedLinode.label, backup, 'backup');
-// };
-
-// render() {
-// const {
-// errors,
-// selectedBackupID,
-// selectedDiskSize,
-// selectedLinodeID,
-// tags,
-// selectedTypeID,
-// selectedRegionID,
-// backups,
-// linodesWithBackups,
-// privateIP,
-// selectedBackupInfo,
-// isMakingRequest
-// } = this.state;
-// const {
-// accountBackups,
-// extendLinodes,
-// getBackupsMonthlyPrice,
-// classes,
-// notice,
-// types,
-// getRegionInfo,
-// getTypeInfo,
-// updateCustomLabel,
-// disabled
-// } = this.props;
-// const hasErrorFor = getAPIErrorsFor(errorResources, errors);
-// const generalError = hasErrorFor('none');
-
-// const imageInfo = selectedBackupInfo;
-
-// const regionInfo = selectedRegionID && getRegionInfo(selectedRegionID);
-
-// const typeInfo = getTypeInfo(selectedTypeID);
-
-// const hasBackups = backups || accountBackups;
-
-// const label = this.label();
-
-// return (
-//
-//
-// {this.state.isGettingBackups ? (
-//
-// ) : !this.userHasBackups() ? (
-//
-// ) : (
-//
-//
-// {notice && !disabled && (
-//
-// )}
-// {generalError && }
-//
-// extendLinodes(linodes),
-// filterLinodesWithBackups
-// )(linodesWithBackups!)}
-// selectedLinodeID={selectedLinodeID}
-// handleSelection={this.handleSelectLinode}
-// updateFor={[selectedLinodeID, errors]}
-// disabled={disabled}
-// />
-// {
-// return linode.id === +selectedLinodeID!;
-// }
-// )}
-// selectedLinodeID={selectedLinodeID}
-// selectedBackupID={selectedBackupID}
-// handleChangeBackup={this.handleSelectBackupID}
-// handleChangeBackupInfo={this.handleSelectBackupInfo}
-// updateFor={[selectedLinodeID, selectedBackupID, errors]}
-// />
-//
-//
-//
-//
-// )}
-//
-// {!this.userHasBackups() ? (
-//
-// ) : (
-//
-//
-// {(props: StickyProps) => {
-// const displaySections = [];
-// if (imageInfo) {
-// displaySections.push(imageInfo);
-// }
-
-// if (regionInfo) {
-// displaySections.push({
-// title: regionInfo.title,
-// details: regionInfo.details
-// });
-// }
-
-// if (typeInfo) {
-// displaySections.push(typeInfo);
-// }
-
-// if (hasBackups && typeInfo && typeInfo.backupsMonthly) {
-// displaySections.push(
-// renderBackupsDisplaySection(
-// accountBackups,
-// typeInfo.backupsMonthly
-// )
-// );
-// }
-
-// let calculatedPrice = pathOr(0, ['monthly'], typeInfo);
-// if (hasBackups && typeInfo && typeInfo.backupsMonthly) {
-// calculatedPrice += typeInfo.backupsMonthly;
-// }
-
-// return (
-//
-// );
-// }}
-//
-//
-// )}
-//
-// );
-// }
-// }
-
-// const styled = withStyles(styles);
-
-// const enhanced = compose(
-// styled,
-// withSnackbar,
-// withLabelGenerator,
-// withLinodeActions
-// );
-
-// export default enhanced(FromBackupsContent);
+import * as Promise from 'bluebird';
+import { InjectedNotistackProps, withSnackbar } from 'notistack';
+import { compose as ramdaCompose, pathOr } from 'ramda';
+import * as React from 'react';
+import { Sticky, StickyProps } from 'react-sticky';
+import { compose } from 'recompose';
+import VolumeIcon from 'src/assets/addnewmenu/volume.svg';
+import CheckoutBar from 'src/components/CheckoutBar';
+import CircleProgress from 'src/components/CircleProgress';
+import {
+ StyleRulesCallback,
+ withStyles,
+ WithStyles
+} from 'src/components/core/styles';
+import CreateLinodeDisabled from 'src/components/CreateLinodeDisabled';
+import Grid from 'src/components/Grid';
+import LabelAndTagsPanel from 'src/components/LabelAndTagsPanel';
+import Notice from 'src/components/Notice';
+import Placeholder from 'src/components/Placeholder';
+import { getLinodeBackups } from 'src/services/linodes';
+import {
+ LinodeActionsProps,
+ withLinodeActions
+} from 'src/store/linodes/linode.containers';
+import getAPIErrorsFor from 'src/utilities/getAPIErrorFor';
+import AddonsPanel from '../AddonsPanel';
+import SelectBackupPanel from '../SelectBackupPanel';
+import SelectLinodePanel from '../SelectLinodePanel';
+import SelectPlanPanel from '../SelectPlanPanel';
+import {
+ BackupFormStateHandlers,
+ Info,
+ WithAll,
+ WithDisplayData
+} from '../types';
+import { extendLinodes } from '../utilities';
+import { renderBackupsDisplaySection } from './utils';
+
+type ClassNames = 'root' | 'main' | 'sidebar';
+
+const styles: StyleRulesCallback = theme => ({
+ root: {},
+ main: {},
+ sidebar: {
+ [theme.breakpoints.up('lg')]: {
+ marginTop: -130
+ }
+ }
+});
+
+interface Props {
+ notice?: Notice;
+ linodesData: Linode.Linode[];
+ selectedBackupFromQuery?: number;
+ selectedLinodeFromQuery?: number;
+ selectedRegionIDFromLinode?: string;
+ disabled?: boolean;
+}
+
+interface State {
+ linodesWithBackups: Linode.LinodeWithBackups[] | null;
+ userHasBackups: boolean;
+ backups: boolean;
+ selectedBackupInfo: Info;
+ backupInfo: Info;
+ isGettingBackups: boolean;
+}
+
+type CombinedProps = Props &
+ LinodeActionsProps &
+ InjectedNotistackProps &
+ BackupFormStateHandlers &
+ WithAll &
+ WithDisplayData &
+ WithStyles;
+
+interface Notice {
+ text: string;
+ level: 'warning' | 'error'; // most likely only going to need these two
+}
+
+const errorResources = {
+ type: 'A plan selection',
+ region: 'A region selection',
+ label: 'A label',
+ root_pass: 'A root password',
+ tags: 'Tags for this Linode'
+};
+
+const filterLinodesWithBackups = (linodes: Linode.LinodeWithBackups[]) => {
+ return linodes.filter(linode => {
+ const hasAutomaticBackups = !!linode.currentBackups.automatic.length;
+ const hasSnapshotBackup = !!linode.currentBackups.snapshot.current;
+ // backups both need to be enabled and some backups need to exist
+ // for the panel to show the Linode
+ return linode.backups.enabled && (hasAutomaticBackups || hasSnapshotBackup);
+ });
+};
+
+export class FromBackupsContent extends React.Component {
+ state: State = {
+ linodesWithBackups: [],
+ userHasBackups: false,
+ backups: false,
+ selectedBackupInfo: undefined,
+ backupInfo: undefined,
+ isGettingBackups: false
+ };
+
+ mounted: boolean = false;
+
+ getLinodesWithBackups = (linodes: Linode.Linode[]) => {
+ this.setState({ isGettingBackups: true });
+ return Promise.map(
+ linodes.filter(l => l.backups.enabled),
+ (linode: Linode.Linode) => {
+ return getLinodeBackups(linode.id).then(backups => {
+ return {
+ ...linode,
+ currentBackups: {
+ ...backups
+ }
+ };
+ });
+ }
+ )
+ .then(data => {
+ if (!this.mounted) {
+ return;
+ }
+ this.setState({ linodesWithBackups: data, isGettingBackups: false });
+ })
+ .catch(err => this.setState({ isGettingBackups: false }));
+ };
+
+ userHasBackups = () => {
+ const { linodesWithBackups } = this.state;
+ return linodesWithBackups!.some((linode: Linode.LinodeWithBackups) => {
+ // automatic backups is an array, but snapshots are either null or an object
+ // user can have up to 3 automatic backups, but one one snapshot
+ return (
+ !!linode.currentBackups.automatic.length ||
+ !!linode.currentBackups.snapshot.current
+ );
+ });
+ };
+
+ handleSelectBackupInfo = (info: Info) => {
+ this.setState({ backupInfo: info });
+ };
+
+ createLinode = () => {
+ const {
+ backupsEnabled,
+ privateIPEnabled,
+ selectedTypeID,
+ selectedRegionID,
+ selectedBackupID,
+ label,
+ tags
+ } = this.props;
+
+ const tagsToAdd = tags ? tags.map(item => item.value) : undefined;
+
+ this.props.handleSubmitForm('createFromBackup', {
+ region: selectedRegionID,
+ type: selectedTypeID,
+ private_ip: privateIPEnabled,
+ backup_id: Number(selectedBackupID),
+ label,
+ backups_enabled: backupsEnabled /* optional */,
+ booted: true,
+ tags: tagsToAdd
+ });
+ };
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.getLinodesWithBackups(this.props.linodesData);
+ // If there is a selected Linode ID (from props), make sure its information
+ // is set to state as if it had been selected manually.
+ }
+
+ render() {
+ const {
+ backups,
+ linodesWithBackups,
+ isGettingBackups,
+ selectedBackupInfo
+ } = this.state;
+ const {
+ accountBackupsEnabled,
+ classes,
+ errors,
+ notice,
+ privateIPEnabled,
+ selectedBackupID,
+ selectedDiskSize,
+ selectedLinodeID,
+ selectedTypeID,
+ setBackupID,
+ togglePrivateIPEnabled,
+ toggleBackupsEnabled,
+ regionDisplayInfo,
+ typeDisplayInfo,
+ disabled,
+ label,
+ tags,
+ typesData,
+ updateLinodeID,
+ updateTypeID,
+ updateTags,
+ backupsMonthlyPrice,
+ backupsEnabled,
+ updateLabel
+ } = this.props;
+ const hasErrorFor = getAPIErrorsFor(errorResources, errors);
+ const generalError = hasErrorFor('none');
+
+ const imageInfo = selectedBackupInfo;
+
+ const hasBackups = backups || accountBackupsEnabled;
+
+ return (
+
+
+ {this.state.isGettingBackups ? (
+
+ ) : !this.userHasBackups() ? (
+
+ ) : (
+
+
+ {notice && !disabled && (
+
+ )}
+ {generalError && }
+
+ extendLinodes(linodes),
+ filterLinodesWithBackups
+ )(linodesWithBackups!)}
+ selectedLinodeID={selectedLinodeID}
+ handleSelection={updateLinodeID}
+ updateFor={[selectedLinodeID, errors]}
+ disabled={disabled}
+ />
+ {
+ return linode.id === +selectedLinodeID!;
+ }
+ )}
+ selectedLinodeID={selectedLinodeID}
+ selectedBackupID={selectedBackupID}
+ handleChangeBackup={setBackupID}
+ handleChangeBackupInfo={this.handleSelectBackupInfo}
+ updateFor={[selectedLinodeID, selectedBackupID, errors]}
+ />
+
+
+
+
+ )}
+
+ {!this.userHasBackups() ? (
+
+ ) : (
+
+
+ {(props: StickyProps) => {
+ const displaySections = [];
+ if (imageInfo) {
+ displaySections.push(imageInfo);
+ }
+
+ if (regionDisplayInfo) {
+ displaySections.push({
+ title: regionDisplayInfo.title,
+ details: regionDisplayInfo.details
+ });
+ }
+
+ if (typeDisplayInfo) {
+ displaySections.push(typeDisplayInfo);
+ }
+
+ if (
+ hasBackups &&
+ typeDisplayInfo &&
+ typeDisplayInfo.backupsMonthly
+ ) {
+ displaySections.push(
+ renderBackupsDisplaySection(
+ accountBackupsEnabled,
+ typeDisplayInfo.backupsMonthly
+ )
+ );
+ }
+
+ let calculatedPrice = pathOr(0, ['monthly'], typeDisplayInfo);
+ if (
+ hasBackups &&
+ typeDisplayInfo &&
+ typeDisplayInfo.backupsMonthly
+ ) {
+ calculatedPrice += typeDisplayInfo.backupsMonthly;
+ }
+
+ return (
+
+ );
+ }}
+
+
+ )}
+
+ );
+ }
+}
+
+const styled = withStyles(styles);
+
+const enhanced = compose(
+ styled,
+ withSnackbar,
+ withLinodeActions
+);
+
+export default enhanced(FromBackupsContent);
diff --git a/src/features/linodes/LinodesCreate/TabbedContent/FromLinodeContent.tsx b/src/features/linodes/LinodesCreate/TabbedContent/FromLinodeContent.tsx
index 5dbd9fdbd4a..fab6de3ee7c 100644
--- a/src/features/linodes/LinodesCreate/TabbedContent/FromLinodeContent.tsx
+++ b/src/features/linodes/LinodesCreate/TabbedContent/FromLinodeContent.tsx
@@ -22,7 +22,7 @@ import SelectLinodePanel from '../SelectLinodePanel';
import SelectPlanPanel from '../SelectPlanPanel';
import { renderBackupsDisplaySection } from './utils';
-import { extendLinodes } from '../utilites';
+import { extendLinodes } from '../utilities';
import {
CloneFormStateHandlers,
@@ -66,8 +66,13 @@ type CombinedProps = Props &
export class FromLinodeContent extends React.PureComponent {
/** set the Linode ID and the disk size and reset the plan selection */
- handleSelectLinode = (linode: Linode.Linode) => {
- this.props.updateLinodeID(linode.id, linode.specs.disk);
+ handleSelectLinode = (linodeID: number) => {
+ const linode = this.props.linodesData.find(
+ linode => linode.id === linodeID
+ );
+ if (linode) {
+ this.props.updateLinodeID(linode.id, linode.specs.disk);
+ }
};
cloneLinode = () => {
diff --git a/src/features/linodes/LinodesCreate/types.ts b/src/features/linodes/LinodesCreate/types.ts
index 7577d4c10c7..20d3db71d05 100644
--- a/src/features/linodes/LinodesCreate/types.ts
+++ b/src/features/linodes/LinodesCreate/types.ts
@@ -55,7 +55,7 @@ export interface ReduxStateProps {
}
export type HandleSubmit = (
- type: 'create' | 'clone' | 'createFromStackScript',
+ type: 'create' | 'clone' | 'createFromStackScript' | 'createFromBackup',
payload: CreateLinodeRequest,
linodeID?: number
) => void;
@@ -123,9 +123,15 @@ export interface StackScriptFormStateHandlers extends BaseFormStateAndHandlers {
handleSelectUDFs: (stackScripts: any[]) => void;
}
+export interface BackupFormStateHandlers extends CloneFormStateHandlers {
+ selectedBackupID?: number;
+ setBackupID: (id: number) => void;
+}
+
export type AllFormStateAndHandlers = BaseFormStateAndHandlers &
CloneFormStateHandlers &
- StackScriptFormStateHandlers;
+ StackScriptFormStateHandlers &
+ BackupFormStateHandlers;
export type WithLinodesImagesTypesAndRegions = WithImagesProps &
WithLinodesProps &
diff --git a/src/features/linodes/LinodesCreate/utilites.ts b/src/features/linodes/LinodesCreate/utilities.ts
similarity index 100%
rename from src/features/linodes/LinodesCreate/utilites.ts
rename to src/features/linodes/LinodesCreate/utilities.ts