Skip to content

Commit e27fc46

Browse files
committed
WIP
1 parent 474d21e commit e27fc46

File tree

10 files changed

+1220
-1158
lines changed

10 files changed

+1220
-1158
lines changed

src/components/common/dialog.tsx

Lines changed: 285 additions & 43 deletions
Large diffs are not rendered by default.

src/components/storagePools/storageVolumeCreate.tsx

Lines changed: 118 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
import React from 'react';
2121

2222
import type { StoragePool } from '../../types';
23-
import type { DialogValues, ValidationFailed } from './storageVolumeCreateBody';
24-
import type { Dialogs } from 'dialogs';
23+
import { useDialogs } from 'dialogs';
2524

2625
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
2726
import { Form } from "@patternfly/react-core/dist/esm/components/Form";
@@ -31,165 +30,138 @@ import {
3130
import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";
3231
import cockpit from 'cockpit';
3332

34-
import { ModalError } from 'cockpit-components-inline-notification.jsx';
35-
import { DialogsContext } from 'dialogs.jsx';
36-
import { units, getDefaultVolumeFormat, convertToUnit, isEmpty } from '../../helpers.js';
33+
import { convertToUnit } from '../../helpers.js';
3734
import { storageVolumeCreate } from '../../libvirtApi/storageVolume.js';
38-
import { VolumeCreateBody } from './storageVolumeCreateBody.jsx';
35+
36+
import {
37+
type VolumeCreateValue, VolumeCreate, init_VolumeCreate, validate_VolumeCreate,
38+
} from './storageVolumeCreateBody.jsx';
39+
import {
40+
useDialogState,
41+
DialogError, DialogErrorMessage,
42+
DialogActionButton, DialogCancelButton,
43+
} from '../common/dialog';
3944

4045
const _ = cockpit.gettext;
4146

42-
interface CreateStorageVolumeModalProps {
43-
idPrefix: string;
44-
storagePool: StoragePool;
47+
interface CreateStorageVolumeValues {
48+
volume: VolumeCreateValue,
4549
}
4650

47-
interface CreateStorageVolumeModalState extends DialogValues {
48-
createInProgress: boolean,
49-
validate?: boolean;
50-
dialogError: string | undefined,
51-
dialogErrorDetail?: string | undefined;
52-
}
51+
const CreateStorageVolumeModal = ({
52+
idPrefix,
53+
storagePool,
54+
} : {
55+
idPrefix: string;
56+
storagePool: StoragePool;
57+
}) => {
58+
const Dialogs = useDialogs();
5359

54-
class CreateStorageVolumeModal extends React.Component<CreateStorageVolumeModalProps, CreateStorageVolumeModalState> {
55-
static contextType = DialogsContext;
56-
declare context: Dialogs;
57-
58-
constructor(props: CreateStorageVolumeModalProps) {
59-
super(props);
60-
this.state = {
61-
createInProgress: false,
62-
dialogError: undefined,
63-
volumeName: '',
64-
size: 1,
65-
unit: units.GiB.name,
66-
format: getDefaultVolumeFormat(props.storagePool),
60+
function init() {
61+
return {
62+
volume: init_VolumeCreate(storagePool),
6763
};
68-
this.dialogErrorSet = this.dialogErrorSet.bind(this);
69-
this.onCreateClicked = this.onCreateClicked.bind(this);
70-
this.onValueChanged = this.onValueChanged.bind(this);
71-
this.validateParams = this.validateParams.bind(this);
7264
}
7365

74-
dialogErrorSet(text: string, detail: string) {
75-
this.setState({ dialogError: text, dialogErrorDetail: detail });
66+
function validate() {
67+
validate_VolumeCreate(dlg.value("volume"));
7668
}
7769

78-
onValueChanged<K extends keyof DialogValues>(key: K, value: DialogValues[K]) {
79-
this.setState({ [key]: value } as Pick<CreateStorageVolumeModalState, K>);
80-
}
81-
82-
validateParams() {
83-
const validationFailed: ValidationFailed = {};
84-
85-
if (isEmpty(this.state.volumeName.trim()))
86-
validationFailed.volumeName = _("Name must not be empty");
87-
const poolCapacity = convertToUnit(this.props.storagePool.capacity, units.B, this.state.unit);
88-
if (this.state.size > poolCapacity)
89-
validationFailed.size = cockpit.format(_("Storage volume size must not exceed the storage pool's capacity ($0 $1)"), poolCapacity.toFixed(2), this.state.unit);
90-
91-
return validationFailed;
92-
}
93-
94-
onCreateClicked() {
95-
const Dialogs = this.context;
96-
const validation = this.validateParams();
97-
if (Object.getOwnPropertyNames(validation).length > 0) {
98-
this.setState({ createInProgress: false, validate: true });
99-
} else {
100-
this.setState({ createInProgress: true, validate: false });
101-
102-
const { volumeName, format } = this.state;
103-
const { name, connectionName } = this.props.storagePool;
104-
const size = convertToUnit(this.state.size, this.state.unit, 'MiB');
105-
106-
storageVolumeCreate({ connectionName, poolName: name, volName: volumeName, size, format })
107-
.then(() => Dialogs.close())
108-
.catch(exc => {
109-
this.setState({ createInProgress: false });
110-
this.dialogErrorSet(_("Volume failed to be created"), exc.message);
111-
});
70+
const dlg = useDialogState<CreateStorageVolumeValues>(init, validate);
71+
72+
async function create(values: CreateStorageVolumeValues) {
73+
const { name: volName, format, size: volSize, unit } = values.volume;
74+
const { name: poolName, connectionName } = storagePool;
75+
const size = convertToUnit(volSize, unit, 'MiB');
76+
77+
try {
78+
await storageVolumeCreate({
79+
connectionName,
80+
poolName,
81+
volName,
82+
size,
83+
format
84+
});
85+
} catch (ex) {
86+
throw DialogError.fromError(_("Volume failed to be created"), ex);
11287
}
11388
}
11489

115-
render() {
116-
const Dialogs = this.context;
117-
const idPrefix = `${this.props.idPrefix}-dialog`;
118-
const validationFailed = this.state.validate ? this.validateParams() : {};
119-
120-
return (
121-
<Modal position="top" variant="medium" id={`${idPrefix}-modal`} className='volume-create' isOpen onClose={Dialogs.close}>
122-
<ModalHeader title={_("Create storage volume")} />
123-
<ModalBody>
124-
<Form isHorizontal>
125-
{this.state.dialogError &&
126-
<ModalError
127-
dialogError={this.state.dialogError}
128-
{...this.state.dialogErrorDetail && { dialogErrorDetail: this.state.dialogErrorDetail } }
129-
/>
130-
}
131-
<VolumeCreateBody format={this.state.format}
132-
idPrefix={idPrefix}
133-
onValueChanged={this.onValueChanged}
134-
size={this.state.size}
135-
storagePool={this.props.storagePool}
136-
unit={this.state.unit}
137-
validationFailed={validationFailed}
138-
volumeName={this.state.volumeName} />
139-
</Form>
140-
</ModalBody>
141-
<ModalFooter>
142-
<Button variant="primary" onClick={this.onCreateClicked} isLoading={this.state.createInProgress} isDisabled={this.state.createInProgress}>
143-
{_("Create")}
144-
</Button>
145-
<Button variant='link' onClick={Dialogs.close}>
146-
{_("Cancel")}
147-
</Button>
148-
</ModalFooter>
149-
</Modal>
150-
);
151-
}
152-
}
153-
154-
interface StorageVolumeCreateProps {
90+
return (
91+
<Modal
92+
position="top"
93+
variant="medium"
94+
id={`${idPrefix}-dialog-modal`}
95+
className='volume-create'
96+
isOpen
97+
onClose={Dialogs.close}
98+
>
99+
<ModalHeader title={_("Create storage volume")} />
100+
<ModalBody>
101+
<DialogErrorMessage dialog={dlg} />
102+
<Form isHorizontal>
103+
<VolumeCreate value={dlg.value("volume")} />
104+
</Form>
105+
</ModalBody>
106+
<ModalFooter>
107+
<DialogActionButton dialog={dlg} onClose={Dialogs.close} action={create}>
108+
{_("Create")}
109+
</DialogActionButton>
110+
<DialogCancelButton dialog={dlg} onClose={Dialogs.close} />
111+
</ModalFooter>
112+
</Modal>
113+
);
114+
};
115+
116+
export const StorageVolumeCreate = ({
117+
storagePool,
118+
} : {
155119
storagePool: StoragePool,
156-
}
157-
158-
export class StorageVolumeCreate extends React.Component<StorageVolumeCreateProps> {
159-
static contextType = DialogsContext;
160-
declare context: Dialogs;
161-
162-
render() {
163-
const Dialogs = this.context;
164-
const idPrefix = `${this.props.storagePool.name}-${this.props.storagePool.connectionName}-create-volume`;
165-
const poolTypesNotSupportingVolumeCreation = ['iscsi', 'iscsi-direct', 'gluster', 'mpath'];
166-
167-
const createButton = () => {
168-
if (!poolTypesNotSupportingVolumeCreation.includes(this.props.storagePool.type) && this.props.storagePool.active) {
169-
return (
170-
<Button id={`${idPrefix}-button`}
120+
}) => {
121+
const Dialogs = useDialogs();
122+
const idPrefix = `${storagePool.name}-${storagePool.connectionName}-create-volume`;
123+
const poolTypesNotSupportingVolumeCreation = ['iscsi', 'iscsi-direct', 'gluster', 'mpath'];
124+
125+
const createButton = () => {
126+
if (!poolTypesNotSupportingVolumeCreation.includes(storagePool.type) && storagePool.active) {
127+
return (
128+
<Button id={`${idPrefix}-button`}
129+
variant='secondary'
130+
onClick={
131+
() => Dialogs.show(
132+
<CreateStorageVolumeModal
133+
idPrefix="create-volume"
134+
storagePool={storagePool}
135+
/>
136+
)
137+
}
138+
>
139+
{_("Create volume")}
140+
</Button>
141+
);
142+
} else {
143+
return (
144+
<Tooltip
145+
id='create-tooltip'
146+
content={
147+
storagePool.active
148+
? _("Pool type doesn't support volume creation")
149+
: _("Pool needs to be active to create volume")
150+
}
151+
>
152+
<span>
153+
<Button
154+
id={`${idPrefix}-button`}
171155
variant='secondary'
172-
onClick={() => Dialogs.show(<CreateStorageVolumeModal idPrefix="create-volume"
173-
storagePool={this.props.storagePool} />)}>
174-
{_("Create volume")}
175-
</Button>
176-
);
177-
} else {
178-
return (
179-
<Tooltip id='create-tooltip'
180-
content={this.props.storagePool.active ? _("Pool type doesn't support volume creation") : _("Pool needs to be active to create volume")}>
181-
<span>
182-
<Button id={`${idPrefix}-button`}
183-
variant='secondary'
184-
isDisabled>
185-
{_("Create volume")}
186-
</Button>
187-
</span>
188-
</Tooltip>
189-
);
190-
}
191-
};
156+
isDisabled
157+
>
158+
{_("Create volume")}
159+
</Button>
160+
</span>
161+
</Tooltip>
162+
);
163+
}
164+
};
192165

193-
return createButton();
194-
}
195-
}
166+
return createButton();
167+
};

0 commit comments

Comments
 (0)