Skip to content

Commit 4bbf698

Browse files
committed
ZENKO-2483 create storage location
1 parent 3bd516e commit 4bbf698

29 files changed

+3422
-4451
lines changed

NOTE.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
issue => endpoint "/create" when editing object

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"dependencies": {
4040
"@fortawesome/fontawesome-free": "5.7.2",
4141
"@scality/core-ui": "github:scality/core-ui.git#add-dist-folder",
42-
"@scality/pensieve-front": "file:../pensieve-front",
4342
"async": "^3.2.0",
4443
"aws-sdk": "^2.616.0",
4544
"connected-react-router": "^6.7.0",

src/react/Routes.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Link, Route } from 'react-router-dom';
22
import DataBrowser from './databrowser/DataBrowser';
33
import Groups from './group/Groups';
4+
import LocationCreate from './monitor/location/LocationCreate';
45
import { Navbar } from '@scality/core-ui';
56
import React from 'react';
67
import ReplicationCreate from './workflow/replication/ReplicationCreate';
@@ -72,6 +73,7 @@ class Routes extends React.Component{
7273
<Route path="/databrowser" component={DataBrowser} />
7374
<Route exact path="/workflow" component={Workflows} />
7475
<Route path="/workflow/replication/create" component={ReplicationCreate} />
76+
<Route path="/location/create" component={LocationCreate} />
7577
</Layout>;
7678
}
7779
}

src/react/actions/location.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @noflow
2+
import { handleApiError, handleClientError } from './error';
3+
import { networkEnd, networkStart } from './network';
4+
import { getClients } from '../utils/actions';
5+
import { updateConfiguration } from './configuration';
6+
7+
export function saveLocation(location: Location): ThunkStatePromisedAction {
8+
return (dispatch, getState) => {
9+
const { pensieveClient, instanceId } = getClients(getState());
10+
const params = {
11+
uuid: instanceId,
12+
location,
13+
locationName: location.name,
14+
};
15+
16+
dispatch(networkStart('Saving Location'));
17+
const op = location.objectId ?
18+
pensieveClient.updateConfigurationOverlayLocation(params)
19+
:
20+
pensieveClient.createConfigurationOverlayLocation(params);
21+
return op.then(() => dispatch(updateConfiguration()))
22+
.catch(error => dispatch(handleClientError(error)))
23+
.catch(error => dispatch(handleApiError(error, 'byComponent')))
24+
.finally(() => dispatch(networkEnd()));
25+
};
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Button, Input } from '@scality/core-ui';
2+
import React, { useState } from 'react';
3+
import CreateContainer from '../../ui-elements/CreateContainer';
4+
import { connect } from 'react-redux';
5+
import { newLocationForm } from './utils';
6+
import { saveLocation } from '../../actions';
7+
8+
function LocationCreate(props) {
9+
const [location, setLocation] = useState(newLocationForm());
10+
11+
const onChange = (e) => {
12+
const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
13+
const l = {
14+
...location,
15+
[e.target.name]: value,
16+
};
17+
setLocation({ l });
18+
};
19+
20+
const save = (e) => {
21+
if (e) {
22+
e.preventDefault();
23+
}
24+
console.log('location!!!', location);
25+
};
26+
27+
const cancel = () => {
28+
console.log('cannot cancel yet!');
29+
};
30+
31+
return <CreateContainer>
32+
<div className='title'> Add new storage location </div>
33+
<div className='input'>
34+
<div className='name'> location name </div>
35+
<Input
36+
type='text'
37+
name='name'
38+
placeholder='Location Name'
39+
onChange={onChange}
40+
value={location.name}
41+
autoComplete='off' />
42+
</div>
43+
<div className='footer'>
44+
<Button outlined onClick={cancel} text='Cancel'/>
45+
<Button outlined onClick={save} text='Add'/>
46+
</div>
47+
</CreateContainer>;
48+
}
49+
50+
function mapDispatchToProps(dispatch) {
51+
return {
52+
saveLocation: (location: Location) => { dispatch(saveLocation(location)); },
53+
};
54+
}
55+
56+
export default connect(null, mapDispatchToProps)(LocationCreate);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// @flow
2+
3+
import type { EnabledState, InstanceStateSnapshot } from '../../../../types/stats';
4+
import type { LocationDetails as LocationFormDetails, LocationType } from '../../../../types/config';
5+
import React from 'react';
6+
import { Tooltip } from '@scality/core-ui';
7+
// import { TextWithTooltip } from '../../../ui-elements/TooltipComponents';
8+
import { calculateTimeDiffInHours } from '../../../utils';
9+
import { storageOptions } from './storageOptions';
10+
11+
type Props = {
12+
edit: boolean,
13+
locationType: LocationType,
14+
details: LocationFormDetails,
15+
editingExisting: boolean,
16+
repStatus?: EnabledState,
17+
repSchedule?: string,
18+
capabilities?: $PropertyType<InstanceStateSnapshot, 'capabilities'>,
19+
onChange: (v: LocationFormDetails) => void,
20+
};
21+
22+
export default class LocationDetails extends React.Component<Props> {
23+
render() {
24+
if (this.props.edit) {
25+
const loc = storageOptions[this.props.locationType];
26+
if (loc) {
27+
const Details = loc.formDetails;
28+
if (Details) {
29+
return Details &&
30+
<Details
31+
onChange={this.props.onChange}
32+
edit={this.props.edit}
33+
editingExisting={this.props.editingExisting}
34+
details={this.props.details}
35+
locationType={this.props.locationType}
36+
key={this.props.locationType}
37+
capabilities={this.props.capabilities}
38+
/>;
39+
}
40+
}
41+
return null;
42+
}
43+
44+
let msg = 'Replication to this location is paused. All changes queued ' +
45+
'for replication to this location will be processed on resume.';
46+
47+
if (this.props.repSchedule) {
48+
const diff = calculateTimeDiffInHours(this.props.repSchedule);
49+
if (diff && this.props.repSchedule) {
50+
const overlay = new Date(this.props.repSchedule).toString();
51+
msg = (
52+
<div>
53+
<span>Replication to this location is paused. All changes queued for replication to this location will be processed in&nbsp;</span>
54+
<Tooltip overlay={overlay}>
55+
{diff} hours.
56+
</Tooltip>
57+
</div>
58+
);
59+
} else if (this.props.repStatus === 'disabled') {
60+
msg = (
61+
<div>
62+
<span className="mr-2 fa fa-exclamation-circle" />
63+
<span>
64+
Your Zenko instance failed to automatically resume this location. Please resume manually.
65+
</span>
66+
</div>
67+
);
68+
}
69+
}
70+
71+
return (
72+
<div className="p-2">
73+
<div>
74+
Location Type:&nbsp;
75+
<span style={{fontFamily: 'nerissemibold'}} className="px-2">{storageOptions[this.props.locationType].name}</span><br/>
76+
</div>
77+
{
78+
this.props.repStatus === 'disabled' ?
79+
<div id="crr-status-info-text"
80+
className="self-align-center multiple-select-extra-info text-muted"
81+
>
82+
{msg}
83+
</div>
84+
: null
85+
}
86+
</div>
87+
);
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// @flow
2+
3+
import type { LocationDetails } from '../../../../types/config';
4+
import React from 'react';
5+
6+
type Props = {
7+
details: LocationDetails,
8+
onChange: (details: LocationDetails) => void,
9+
editingExisting: boolean,
10+
};
11+
12+
type State = {
13+
serverSideEncryption: boolean,
14+
bucketMatch: boolean,
15+
accessKey: string,
16+
secretKey: string,
17+
bucketName: string,
18+
};
19+
20+
const INIT_STATE: State = {
21+
serverSideEncryption: false,
22+
bucketMatch: false,
23+
accessKey: '',
24+
secretKey: '',
25+
bucketName: '',
26+
};
27+
28+
export default class LocationDetailsAws extends React.Component<Props, State> {
29+
constructor(props: Props) {
30+
super(props);
31+
this.state = Object.assign({}, INIT_STATE, this.props.details);
32+
// XXX disable changing it if not provided
33+
this.state.secretKey = '';
34+
}
35+
36+
onChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
37+
const target = e.target;
38+
const value = target.type === 'checkbox' ? target.checked : target.value;
39+
this.setState({ [target.name]: value });
40+
}
41+
42+
updateForm = () => {
43+
if (this.props.onChange) {
44+
this.props.onChange(this.state);
45+
}
46+
}
47+
48+
componentDidMount() {
49+
this.updateForm();
50+
}
51+
52+
shouldComponentUpdate(nextProps: Props, nextState: State) {
53+
return this.state !== nextState;
54+
}
55+
56+
componentDidUpdate() {
57+
this.updateForm();
58+
}
59+
60+
render() {
61+
return (
62+
<div>
63+
<fieldset className="form-group">
64+
<label htmlFor="accessKey">AWS Access Key</label>
65+
<input
66+
name="accessKey"
67+
id="accessKey"
68+
className="form-control"
69+
type="text"
70+
placeholder="AKI5HMPCLRB86WCKTN2C"
71+
value={this.state.accessKey}
72+
onChange={this.onChange}
73+
autoComplete="off"
74+
/>
75+
</fieldset>
76+
<fieldset className="form-group">
77+
<label htmlFor="secretKey">AWS Secret Key</label>
78+
<input
79+
name="secretKey"
80+
id="secretKey"
81+
className="form-control"
82+
type="password"
83+
placeholder="QFvIo6l76oe9xgCAw1N/zlPFtdTSZXMMUuANeXc6"
84+
value={this.state.secretKey}
85+
onChange={this.onChange}
86+
autoComplete="new-password"
87+
/>
88+
<small>
89+
Your credentials are encrypted in transit, then at rest using your
90+
Zenko instance&apos;s RSA key pair so that we&apos;re unable to see them.
91+
</small>
92+
</fieldset>
93+
<fieldset className="form-group">
94+
<label htmlFor="bucketName">Target Bucket Name</label>
95+
<input
96+
name="bucketName"
97+
id="bucketName"
98+
className="form-control"
99+
type="text"
100+
placeholder="Bucket Name"
101+
value={this.state.bucketName}
102+
onChange={this.onChange}
103+
autoComplete="off"
104+
/>
105+
</fieldset>
106+
<fieldset className="form-group">
107+
<label className="form-check-label">
108+
<input
109+
name="bucketMatch"
110+
className="form-check-input"
111+
type="checkbox"
112+
value={this.state.bucketMatch}
113+
checked={this.state.bucketMatch}
114+
disabled={this.props.editingExisting}
115+
onChange={this.onChange} />
116+
<span>Write objects without prefix</span><br />
117+
<small> Use this option for mirroring. <br /> </small>
118+
<small>Store objects in the target bucket without a source-bucket prefix.</small>
119+
{
120+
this.state.bucketMatch &&
121+
<div>
122+
<small className="text-danger">
123+
<i className="fa fa-exclamation-circle"></i>
124+
&nbsp; Storing multiple buckets in a location with this option enabled can lead to data loss.
125+
</small>
126+
</div>
127+
}
128+
</label>
129+
</fieldset>
130+
<fieldset className="form-group">
131+
<label className="form-check-label">
132+
<input
133+
name="serverSideEncryption"
134+
className="form-check-input"
135+
type="checkbox"
136+
value={this.state.serverSideEncryption}
137+
checked={this.state.serverSideEncryption}
138+
onChange={this.onChange} />
139+
<span>Server-Side Encryption</span>
140+
</label>
141+
</fieldset>
142+
</div>
143+
);
144+
}
145+
}

0 commit comments

Comments
 (0)