Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@import '~@patternfly/patternfly/sass-utilities/colors';

.add-capacity-modal--padding {
.co-storage-class-dropdown {
.dropdown {
width: 20.25rem;
button {
width: 100%;
}
}
}
.form-group input {
max-width: 15rem;
margin-right: 5px;
}
.toolTip_dropdown {
button {
position: absolute;
left: 7rem;
top: 8.5rem;
}
}
padding-top: 2em;
}

.add-capacity-modal__span {
margin-left: 3em;
color: $pf-color-black-500;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as React from 'react';
import * as _ from 'lodash';
import { DashboardCardHelp } from '@console/internal/components/dashboard/dashboard-card/card-help';
import { RequestSizeInput, withHandlePromise } from '@console/internal/components/utils/index';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alpha imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

import {
createModalLauncher,
ModalBody,
ModalSubmitFooter,
ModalTitle,
} from '@console/internal/components/factory';
import { k8sPatch, K8sResourceKind } from '@console/internal/module/k8s';
import { OCSServiceModel } from '../../../models';
import './_add-capacity-modal.scss';
import { OCSStorageClassDropdown } from '../storage-class-dropdown';

export const AddCapacityModal = withHandlePromise((props: AddCapacityModalProps) => {
const { ocsConfig, close, cancel } = props;
const dropdownUnits = {
Ti: 'Ti',
};
const requestSizeUnit = dropdownUnits.Ti;
const [requestSizeValue, setRequestSizeValue] = React.useState('');
const [storageClass, setStorageClass] = React.useState('');
const [inProgress, setProgress] = React.useState(false);
const [errorMessage, setError] = React.useState('');
const storageClassTooltip =
'The Storage Class will be used to request storage from the underlying infrastructure to create the backing persistent volumes that will be used to provide the OpenShift Container Storage (OCS) service.';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - remove this empty line

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@a2batic is the alignment for the Used next to requested capacity correct?

@yuvalgalanti ^^

I think the fields should be

@a2batic is the alignment for the Used next to requested capacity correct?

@yuvalgalanti ^^

see the attached design. the text area is bigger and the dropdown is smaller
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

const submit = (event: React.FormEvent<EventTarget>) => {
event.preventDefault();
setProgress(true);
const presentCapacity = _.get(ocsConfig, 'spec.storageDeviceSets[0].count');
const newValue = parseInt(presentCapacity, 10) + parseInt(requestSizeValue, 10) * 3;
const patch = {
op: 'replace',
path: `/spec/storageDeviceSets/0/count`,
value: newValue,
};
props
.handlePromise(k8sPatch(OCSServiceModel, ocsConfig, [patch]))
.then(() => {
setProgress(false);
close();
})
.catch((error) => {
setError(error);
setProgress(false);
throw error;
});
};

const handleRequestSizeInputChange = (capacityObj: any) => {
setRequestSizeValue(capacityObj.value);
};

const handleStorageClass = (sc: K8sResourceKind) => {
setStorageClass(sc.metadata.name);
};

return (
<form onSubmit={submit} className="modal-content modal-content--no-inner-scroll">
<ModalTitle>Add Capacity</ModalTitle>
<ModalBody>
Increase the capacity of <strong>{ocsConfig.metadata.name}</strong>.
<div className="add-capacity-modal--padding">
<div className="form-group">
<label className="control-label" htmlFor="request-size-input">
Requested Capacity
<span className="add-capacity-modal__span">
Used:{' '}
{_.get(
ocsConfig,
'spec.storageDeviceSets[0].dataPVCTemplate.spec.resources.requests.storage',
)}
</span>
</label>
<RequestSizeInput
name="requestSize"
placeholder={requestSizeValue}
onChange={handleRequestSizeInputChange}
defaultRequestSizeUnit={requestSizeUnit}
defaultRequestSizeValue={requestSizeValue}
dropdownUnits={dropdownUnits}
required
/>
</div>
<div className="toolTip_dropdown">
<DashboardCardHelp>{storageClassTooltip}</DashboardCardHelp>
</div>
<OCSStorageClassDropdown
onChange={handleStorageClass}
name="storageClass"
defaultClass={storageClass}
required
/>
</div>
</ModalBody>
<ModalSubmitFooter
inProgress={inProgress}
errorMessage={errorMessage}
submitText="Add"
cancel={cancel}
/>
</form>
);
});

export type AddCapacityModalProps = {
kind?: any;
ocsConfig?: any;
handlePromise: <T>(promise: Promise<T>) => Promise<T>;
cancel?: () => void;
close?: () => void;
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are handlePromise, isProgress, errorMessage props being used?

Copy link
Contributor Author

@a2batic a2batic Aug 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I will remove them from Props
.

export const addCapacityModal = createModalLauncher(AddCapacityModal);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';
import * as _ from 'lodash';
import { Firehose } from '@console/internal/components/utils';
import { StorageClassDropdownInner } from '@console/internal/components/utils/storage-class-dropdown';
import { K8sResourceKind } from '@console/internal/module/k8s';

const cephStorageProvisioners = ['ceph.rook.io/block', 'cephfs.csi.ceph.com', 'rbd.csi.ceph.com'];

export const OCSStorageClassDropdown: React.FC<OCSStorageClassDropdownProps> = (props) => (
<Firehose resources={[{ kind: 'StorageClass', prop: 'StorageClass', isList: true }]}>
<StorageClassDropdown {...props} />
</Firehose>
);

const StorageClassDropdown = (props: any) => {
const scConfig = _.cloneDeep(props);
/* 'S' of Storage should be Capital as its defined key in resourses object */
const scLoaded = _.get(scConfig.resources.StorageClass, 'loaded');
const scData = _.get(scConfig.resources.StorageClass, 'data', []) as K8sResourceKind[];

const filteredSCData = scData.filter((sc: K8sResourceKind) => {
return cephStorageProvisioners.every(
(provisioner: string) => !_.get(sc, 'provisioner').includes(provisioner),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want includes? That is a substring match.

Copy link
Contributor Author

@a2batic a2batic Aug 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spadgett right now provisioners are coming in the form of NAMESPACE.cephfs.csi.ceph.com NAMESPACE.rbd.csi.ceph.com ceph.rook.io/block, where Namespace is not constant and can be more than one word, so used includes to check for substring that will be present for sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see. The storage stuff is not in my wheelhouse :)

Consider https://lodash.com/docs/4.17.15#endsWith

);
});

if (scLoaded) {
scConfig.resources.StorageClass.data = filteredSCData;
}

return <StorageClassDropdownInner {...scConfig} />;
};

type OCSStorageClassDropdownProps = {
id?: string;
loaded?: boolean;
resources?: any;
name: string;
onChange: (object) => void;
describedBy?: string;
defaultClass: string;
required?: boolean;
};
21 changes: 20 additions & 1 deletion frontend/packages/ceph-storage-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Plugin,
DashboardsOverviewQuery,
RoutePage,
ClusterServiceVersionAction,
} from '@console/plugin-sdk';
import { GridPosition } from '@console/internal/components/dashboard';
import { OverviewQuery } from '@console/internal/components/dashboards-page/overview-dashboard/queries';
Expand All @@ -29,7 +30,8 @@ type ConsumedExtensions =
| DashboardsCard
| DashboardsOverviewHealthPrometheusSubsystem
| DashboardsOverviewQuery
| RoutePage;
| RoutePage
| ClusterServiceVersionAction;

const CEPH_FLAG = 'CEPH';
// keeping this for testing, will be removed once ocs operator available
Expand Down Expand Up @@ -196,6 +198,23 @@ const plugin: Plugin<ConsumedExtensions> = [
required: CEPH_FLAG,
},
},
{
type: 'ClusterServiceVersion/Action',
properties: {
kind: 'StorageCluster',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This action is missing an accessReview

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spadgett how should we pass object to accessReview?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    accessReview: asAccessReview(kind, obj, 'patch'),

You'd need to add another extension property to ClusterServiceVersionAction to handle this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a follow on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, Thank you Sam

label: 'Add Capacity',
callback: (kind, ocsConfig) => () => {
const clusterObject = { ocsConfig };
import(
'./components/modals/add-capacity-modal/add-capacity-modal' /* webpackChunkName: "ceph-storage-add-capacity-modal" */
)
.then((m) => m.addCapacityModal(clusterObject))
.catch((e) => {
throw e;
});
},
},
},
];

export default plugin;
5 changes: 5 additions & 0 deletions frontend/packages/console-plugin-sdk/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
isOverviewResourceTab,
isOverviewCRD,
isGlobalConfig,
isClusterServiceVersionAction,
} from './typings';

/**
Expand Down Expand Up @@ -108,4 +109,8 @@ export class ExtensionRegistry {
public getGlobalConfigs() {
return this.extensions.filter(isGlobalConfig);
}

public getClusterServiceVersionActions() {
return this.extensions.filter(isClusterServiceVersionAction);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { K8sResourceKindReference } from '@console/internal/module/k8s';
import { Extension } from './extension';

namespace ExtensionProperties {
export interface ClusterServiceVersionAction {
/** the kind this action is for */
kind: K8sResourceKindReference;
/** label of action */
label: string;
/** action callback */
callback: (kind: K8sResourceKindReference, obj: any) => () => any;
}
}

export interface ClusterServiceVersionAction
extends Extension<ExtensionProperties.ClusterServiceVersionAction> {
type: 'ClusterServiceVersion/Action';
}

export const isClusterServiceVersionAction = (
e: Extension<any>,
): e is ClusterServiceVersionAction => e.type === 'ClusterServiceVersion/Action';
1 change: 1 addition & 0 deletions frontend/packages/console-plugin-sdk/src/typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './pages';
export * from './perspectives';
export * from './yaml-templates';
export * from './global-configs';
export * from './clusterserviceversions';
78 changes: 45 additions & 33 deletions frontend/public/components/operator-lifecycle-manager/operand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,50 @@ import { apiVersionForReference, kindForReference, K8sResourceKind, OwnerReferen
import { ClusterServiceVersionModel } from '../../models';
import { deleteModal } from '../modals';
import { RootState } from '../../redux';
import * as plugins from '../../plugins';

const csvName = () => location.pathname.split('/').find((part, i, allParts) => allParts[i - 1] === referenceForModel(ClusterServiceVersionModel) || allParts[i - 1] === ClusterServiceVersionModel.plural);

const actions = [
(kind, obj) => ({
label: `Edit ${kind.label}`,
href: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${csvName()}/${referenceFor(obj)}/${obj.metadata.name}/yaml`,
accessReview: {
group: kind.apiGroup,
resource: kind.plural,
name: obj.metadata.name,
namespace: obj.metadata.namespace,
verb: 'update',
},
}),
(kind, obj) => ({
label: `Delete ${kind.label}`,
callback: () => deleteModal({
kind,
resource: obj,
namespace: obj.metadata.namespace,
redirectTo: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${csvName()}/${referenceFor(obj)}`,
const getActions = (selectedObj: any) => {
const actions = plugins.registry.getClusterServiceVersionActions().filter(action =>
action.properties.kind === selectedObj.kind
);
const pluginActions = actions.map(action => (kind, ocsObj) => ({
label: action.properties.label,
callback: action.properties.callback(kind, ocsObj),
}));
return [
...pluginActions,
(kind, obj) => ({
label: `Edit ${kind.label}`,
href: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${csvName()}/${referenceFor(obj)}/${obj.metadata.name}/yaml`,
accessReview: {
group: kind.apiGroup,
resource: kind.plural,
name: obj.metadata.name,
namespace: obj.metadata.namespace,
verb: 'update',
},
}),
accessReview: {
group: kind.apiGroup,
resource: kind.plural,
name: obj.metadata.name,
namespace: obj.metadata.namespace,
verb: 'delete',
},
}),
] as KebabAction[];

(kind, obj) => ({
label: `Delete ${kind.label}`,
callback: () => deleteModal({
kind,
resource: obj,
namespace: obj.metadata.namespace,
redirectTo: `/k8s/ns/${obj.metadata.namespace}/${ClusterServiceVersionModel.plural}/${csvName()}/${referenceFor(obj)}`,
}),
accessReview: {
group: kind.apiGroup,
resource: kind.plural,
name: obj.metadata.name,
namespace: obj.metadata.namespace,
verb: 'delete',
},
}),
] as KebabAction[];
};

const tableColumnClasses = [
classNames('col-lg-2', 'col-md-3', 'col-sm-4', 'col-xs-6'),
Expand Down Expand Up @@ -124,7 +136,7 @@ export const OperandTableRow: React.FC<OperandTableRowProps> = ({obj, index, key
<Timestamp timestamp={obj.metadata.creationTimestamp} />
</TableData>
<TableData className={tableColumnClasses[6]}>
<ResourceKebab actions={actions} kind={referenceFor(obj)} resource={obj} />
<ResourceKebab actions={getActions(obj)} kind={referenceFor(obj)} resource={obj} />
</TableData>
</TableRow>
);
Expand Down Expand Up @@ -263,9 +275,9 @@ export const OperandDetails = connectToModel((props: OperandDetailsProps) => {
<ResourceSummary resource={props.obj} />
</div>
{ currentStatus &&
<div className="col-xs-6" key={currentStatus.path}>
<StatusDescriptor namespace={metadata.namespace} obj={props.obj} model={props.kindObj} descriptor={currentStatus} value={blockValue(currentStatus, status)} />
</div>
<div className="col-xs-6" key={currentStatus.path}>
<StatusDescriptor namespace={metadata.namespace} obj={props.obj} model={props.kindObj} descriptor={currentStatus} value={blockValue(currentStatus, status)} />
</div>
}

{ specDescriptors.map((specDescriptor: Descriptor, i) =>
Expand Down Expand Up @@ -312,7 +324,7 @@ export const OperandDetailsPage: React.SFC<OperandDetailsPageProps> = (props) =>
resources={[
{kind: referenceForModel(ClusterServiceVersionModel), name: props.match.params.appName, namespace: props.namespace, isList: false, prop: 'csv'},
]}
menuActions={actions}
menuActions={getActions(props.kind)}
breadcrumbsFor={() => [
{name: 'Installed Operators', path: `/k8s/ns/${props.match.params.ns}/${ClusterServiceVersionModel.plural}`},
{name: props.match.params.appName, path: props.match.url.slice(0, props.match.url.lastIndexOf('/'))},
Expand Down
Loading