-
Notifications
You must be signed in to change notification settings - Fork 667
kubevirt: Edit VM flavor #2392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
kubevirt: Edit VM flavor #2392
Changes from all commits
149427a
5e345eb
c3a819e
08921db
bc8a538
54bab6d
ef3a10c
a343eb6
d5666a9
36facf0
1c4b36e
40be319
816feb6
4b08bd5
0c28e9d
f0e35fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import * as React from 'react'; | ||
|
|
||
| export const EditButton: React.FC<EditButtonProps> = (props) => { | ||
| const { canEdit, onClick, children } = props; | ||
|
|
||
| if (canEdit) { | ||
| return ( | ||
| <button | ||
| type="button" | ||
| className="btn btn-link co-modal-btn-link co-modal-btn-link--left" | ||
| onClick={onClick} | ||
| > | ||
| {children} | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| return null; | ||
| }; | ||
|
|
||
| type EditButtonProps = { | ||
| children: any; | ||
| canEdit: boolean; | ||
| onClick: React.MouseEventHandler; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import * as React from 'react'; | ||
| import * as _ from 'lodash'; | ||
| import { convertToBaseValue, humanizeDecimalBytes } from '@console/internal/components/utils'; | ||
| import { getFlavor, vCPUCount, getCPU, getMemory, asVM } from '../selectors/vm'; | ||
| import { VMLikeEntityKind } from '../types'; | ||
|
|
||
| export const FlavorText: React.FC<FlavorTextProps> = (props) => { | ||
| const { vmLike } = props; | ||
| const vm = asVM(vmLike); | ||
|
|
||
| const flavor = _.capitalize(getFlavor(vmLike)); | ||
|
|
||
| const vcpusCount = vCPUCount(getCPU(vm)); | ||
| const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; | ||
|
|
||
| const memoryBase = convertToBaseValue(getMemory(vm)); | ||
| const memoryText = humanizeDecimalBytes(memoryBase).string; | ||
|
|
||
| return <>{`${flavor || ''}${flavor ? ': ' : ''}${vcpusText}, ${memoryText} Memory`}</>; | ||
| }; | ||
|
|
||
| type FlavorTextProps = { | ||
| vmLike: VMLikeEntityKind; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| export * from './create-vm-wizard'; | ||
| export * from './delete-device-modal'; | ||
| export * from './modal-resource-launcher'; | ||
| export * from './vm-description-modal'; | ||
| export * from './vm-flavor-modal'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,10 +9,12 @@ import { | |
| ModalComponentProps, | ||
| } from '@console/internal/components/factory'; | ||
| import { k8sPatch } from '@console/internal/module/k8s'; | ||
| import { getDescription } from '../../../selectors/selectors'; | ||
| import { VMLikeEntityKind } from '../../../types'; | ||
| import { getDescription, getVMLikeModel } from '../../../selectors/selectors'; | ||
| import { getVMLikeModel } from '../../../selectors/vm'; | ||
| import { getUpdateDescriptionPatches } from '../../../k8s/patches/vm/vm-patches'; | ||
|
|
||
| // TODO: should be moved under kubevirt-plugin/src/style.scss | ||
|
||
| import './_vm-description-modal.scss'; | ||
|
|
||
| export const VMDescriptionModal = withHandlePromise((props: VMDescriptionModalProps) => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| .kubevirt-vm-flavor-modal__content-custom { | ||
| height: 20em; | ||
| } | ||
|
|
||
| .kubevirt-vm-flavor-modal__content-generic { | ||
| min-height: 13em; | ||
| .modal-body { | ||
| overflow-y: unset; /* Hotfix for PF3 */ | ||
| } | ||
| } | ||
|
|
||
| .kubevirt-vm-flavor-modal__form { | ||
| resize: vertical; | ||
| } | ||
|
|
||
| .kubevirt-vm-flavor-modal__dropdown { | ||
| .dropdown { | ||
| width: 100%; | ||
| } | ||
| } | ||
|
|
||
| .kubevirt-vm-flavor-modal__dropdown-button { | ||
| width: 100%; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './vm-flavor-modal'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import * as React from 'react'; | ||
| import * as _ from 'lodash'; | ||
| import * as classNames from 'classnames'; | ||
| import { getResource } from 'kubevirt-web-ui-components'; | ||
| import { | ||
| HandlePromiseProps, | ||
| withHandlePromise, | ||
| Dropdown, | ||
| convertToBaseValue, | ||
| Firehose, | ||
| } from '@console/internal/components/utils'; | ||
| import { TemplateModel } from '@console/internal/models'; | ||
| import { | ||
| createModalLauncher, | ||
| ModalTitle, | ||
| ModalBody, | ||
| ModalComponentProps, | ||
| ModalFooter, | ||
| } from '@console/internal/components/factory'; | ||
| import { k8sPatch, TemplateKind } from '@console/internal/module/k8s'; | ||
| import { Form, FormGroup, TextInput } from '@patternfly/react-core'; | ||
| import { VMKind, VMLikeEntityKind } from '../../../types'; | ||
| import { getFlavor, getMemory, getCPU, isVM, vCPUCount } from '../../../selectors/vm'; | ||
| import { selectVM, getFlavors } from '../../../selectors/vm-template/selectors'; | ||
| import { getUpdateFlavorPatches } from '../../../k8s/patches/vm/vm-patches'; | ||
| import { VirtualMachineModel } from '../../../models'; | ||
| import { | ||
| CUSTOM_FLAVOR, | ||
| NAMESPACE_OPENSHIFT, | ||
| TEMPLATE_TYPE_LABEL, | ||
| TEMPLATE_TYPE_BASE, | ||
| } from '../../../constants'; | ||
| import './_vm-flavor-modal.scss'; | ||
|
|
||
| const MB = 1000 ** 2; | ||
|
|
||
| const getId = (field: string) => `vm-flavor-modal-${field}`; | ||
| const dehumanizeMemory = (memory?: string) => { | ||
| if (!memory) { | ||
| return null; | ||
| } | ||
|
|
||
| return convertToBaseValue(memory) / MB; | ||
| }; | ||
|
|
||
| const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { | ||
| const { | ||
| vmLike, | ||
| templates, | ||
| inProgress, | ||
| errorMessage, | ||
| handlePromise, | ||
| close, | ||
| cancel, | ||
| loadError, | ||
| loaded, | ||
| } = props; | ||
|
|
||
| const flattenTemplates = _.get(templates, 'data', []) as TemplateKind[]; | ||
|
||
|
|
||
| const vmFlavor = getFlavor(vmLike); | ||
| const flavors = getFlavors(vmLike, flattenTemplates); | ||
|
|
||
| const sourceMemory = isVM(vmLike) | ||
| ? getMemory(vmLike as VMKind) | ||
| : getMemory(selectVM(vmLike as TemplateKind)); | ||
|
|
||
| const sourceCPURaw = isVM(vmLike) | ||
| ? getCPU(vmLike as VMKind) | ||
| : getCPU(selectVM(vmLike as TemplateKind)); | ||
| const sourceCPU = vCPUCount(sourceCPURaw); | ||
|
|
||
| const [flavor, setFlavor] = React.useState(vmFlavor); | ||
| const [mem, setMem] = React.useState( | ||
| vmFlavor === CUSTOM_FLAVOR ? dehumanizeMemory(sourceMemory) : 1, | ||
| ); | ||
| const [cpu, setCpu] = React.useState(vmFlavor === CUSTOM_FLAVOR ? sourceCPU : 1); | ||
|
|
||
| const submit = (e) => { | ||
| e.preventDefault(); | ||
|
|
||
| const patches = getUpdateFlavorPatches(vmLike, flattenTemplates, flavor, cpu, `${mem}M`); | ||
| if (patches.length === 0) { | ||
| close(); | ||
| } else { | ||
| const model = isVM(vmLike) ? VirtualMachineModel : TemplateModel; | ||
| const promise = k8sPatch(model, vmLike, patches); | ||
| handlePromise(promise).then(close); // eslint-disable-line promise/catch-or-return | ||
| } | ||
| }; | ||
|
|
||
| const topClass = classNames('modal-content', { | ||
| 'kubevirt-vm-flavor-modal__content-custom': flavor === CUSTOM_FLAVOR, | ||
| 'kubevirt-vm-flavor-modal__content-generic': flavor !== CUSTOM_FLAVOR, | ||
| }); | ||
|
|
||
| return ( | ||
| <div className={topClass}> | ||
| <ModalTitle>Edit Flavor</ModalTitle> | ||
| <ModalBody> | ||
| <Form onSubmit={submit} className="kubevirt-vm-flavor-modal__form" isHorizontal> | ||
| <FormGroup label="Flavor" fieldId={getId('flavor')}> | ||
| <Dropdown | ||
| items={flavors} | ||
| id={getId('flavor-dropdown')} | ||
| onChange={(f) => setFlavor(f)} | ||
| selectedKey={_.capitalize(flavor) || CUSTOM_FLAVOR} | ||
| title={_.capitalize(flavor)} | ||
| className="kubevirt-vm-flavor-modal__dropdown" | ||
| buttonClassName="kubevirt-vm-flavor-modal__dropdown-button" | ||
| /> | ||
| </FormGroup> | ||
|
|
||
| {flavor === CUSTOM_FLAVOR && ( | ||
| <React.Fragment> | ||
| <FormGroup label="CPUs" isRequired fieldId={getId('cpu')}> | ||
| <TextInput | ||
| isRequired | ||
| type="number" | ||
| id={getId('cpu')} | ||
| value={cpu} | ||
| onChange={(v) => setCpu(parseInt(v, 10) || 1)} | ||
| aria-label="CPU count" | ||
| /> | ||
| </FormGroup> | ||
| <FormGroup label="Memory (MB)" isRequired fieldId={getId('memory')}> | ||
| <TextInput | ||
| isRequired | ||
| type="number" | ||
| id={getId('memory')} | ||
| value={mem} | ||
| onChange={(v) => setMem(parseInt(v, 10) || 1)} | ||
| aria-label="Memory" | ||
| /> | ||
| </FormGroup> | ||
| </React.Fragment> | ||
| )} | ||
| </Form> | ||
| </ModalBody> | ||
| <ModalFooter | ||
| inProgress={inProgress || !loaded} | ||
| errorMessage={errorMessage || _.get(loadError, 'message')} | ||
| > | ||
| <button type="button" onClick={cancel} className="btn btn-default"> | ||
| Cancel | ||
| </button> | ||
| <button type="button" onClick={submit} className="btn btn-primary" id="confirm-action"> | ||
| Save | ||
| </button> | ||
| </ModalFooter> | ||
| </div> | ||
| ); | ||
| }); | ||
|
|
||
| const VMFlavorModalFirehose = (props) => { | ||
| const resources = [ | ||
| getResource(TemplateModel, { | ||
| namespace: NAMESPACE_OPENSHIFT, | ||
| prop: 'templates', | ||
| matchLabels: { [TEMPLATE_TYPE_LABEL]: TEMPLATE_TYPE_BASE }, | ||
| }), | ||
| ]; | ||
|
|
||
| return ( | ||
| <Firehose resources={resources}> | ||
| <VMFlavorModal {...props} /> | ||
| </Firehose> | ||
| ); | ||
| }; | ||
|
|
||
| export type VMFlavornModalProps = HandlePromiseProps & | ||
| ModalComponentProps & { | ||
| vmLike: VMLikeEntityKind; | ||
| templates?: any; | ||
| loadError?: any; | ||
| loaded: boolean; | ||
| }; | ||
|
|
||
| export const vmFlavorModal = createModalLauncher(VMFlavorModalFirehose); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not to use Button from PF4 ? I think console code is starting to use it too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved back to
buttonas styles got broken with moving the component to the "value section".I would need to remove what PF4 Button is adding, so the "raw way" is simpler.