Refactor browser dropdown links and components (#5562)
* refactor browser links and about modal Moved about modal to separate component and added unit tests. * refactor change password modal component * added unit tests for ChangePasswordModal * fix logout function in browser dropdown * remove older unused BrowserDropdown component * remove unused variables from BrowserDropdown component * show BrowserDropdown and StorageInfo only for LoggedIn users Non-loggedIn users will see a 'Login' buttonmaster
parent
6a42727e00
commit
bb0adea494
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { Modal } from "react-bootstrap" |
||||||
|
import logo from "../../img/logo.svg" |
||||||
|
|
||||||
|
export const AboutModal = ({ serverInfo, hideAbout }) => { |
||||||
|
const { version, memory, platform, runtime } = serverInfo |
||||||
|
return ( |
||||||
|
<Modal |
||||||
|
className="modal-about modal-dark" |
||||||
|
animation={false} |
||||||
|
show={true} |
||||||
|
onHide={hideAbout} |
||||||
|
> |
||||||
|
<button className="close" onClick={hideAbout}> |
||||||
|
<span>×</span> |
||||||
|
</button> |
||||||
|
<div className="ma-inner"> |
||||||
|
<div className="mai-item hidden-xs"> |
||||||
|
<a href="https://minio.io" target="_blank"> |
||||||
|
<img className="maii-logo" src={logo} alt="" /> |
||||||
|
</a> |
||||||
|
</div> |
||||||
|
<div className="mai-item"> |
||||||
|
<ul className="maii-list"> |
||||||
|
<li> |
||||||
|
<div>Version</div> |
||||||
|
<small>{version}</small> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<div>Memory</div> |
||||||
|
<small>{memory}</small> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<div>Platform</div> |
||||||
|
<small>{platform}</small> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<div>Runtime</div> |
||||||
|
<small>{runtime}</small> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default AboutModal |
@ -0,0 +1,156 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2016, 2017, 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { connect } from "react-redux" |
||||||
|
import { Dropdown } from "react-bootstrap" |
||||||
|
import * as browserActions from "./actions" |
||||||
|
import web from "../web" |
||||||
|
import history from "../history" |
||||||
|
import AboutModal from "./AboutModal" |
||||||
|
import ChangePasswordModal from "./ChangePasswordModal" |
||||||
|
|
||||||
|
export class BrowserDropdown extends React.Component { |
||||||
|
constructor(props) { |
||||||
|
super(props) |
||||||
|
this.state = { |
||||||
|
showAboutModal: false, |
||||||
|
showChangePasswordModal: false |
||||||
|
} |
||||||
|
} |
||||||
|
showAbout(e) { |
||||||
|
e.preventDefault() |
||||||
|
this.setState({ |
||||||
|
showAboutModal: true |
||||||
|
}) |
||||||
|
} |
||||||
|
hideAbout() { |
||||||
|
this.setState({ |
||||||
|
showAboutModal: false |
||||||
|
}) |
||||||
|
} |
||||||
|
showChangePassword(e) { |
||||||
|
e.preventDefault() |
||||||
|
this.setState({ |
||||||
|
showChangePasswordModal: true |
||||||
|
}) |
||||||
|
} |
||||||
|
hideChangePassword() { |
||||||
|
this.setState({ |
||||||
|
showChangePasswordModal: false |
||||||
|
}) |
||||||
|
} |
||||||
|
componentDidMount() { |
||||||
|
const { fetchServerInfo } = this.props |
||||||
|
fetchServerInfo() |
||||||
|
} |
||||||
|
fullScreen(e) { |
||||||
|
e.preventDefault() |
||||||
|
let el = document.documentElement |
||||||
|
if (el.requestFullscreen) { |
||||||
|
el.requestFullscreen() |
||||||
|
} |
||||||
|
if (el.mozRequestFullScreen) { |
||||||
|
el.mozRequestFullScreen() |
||||||
|
} |
||||||
|
if (el.webkitRequestFullscreen) { |
||||||
|
el.webkitRequestFullscreen() |
||||||
|
} |
||||||
|
if (el.msRequestFullscreen) { |
||||||
|
el.msRequestFullscreen() |
||||||
|
} |
||||||
|
} |
||||||
|
logout(e) { |
||||||
|
e.preventDefault() |
||||||
|
web.Logout() |
||||||
|
history.replace("/login") |
||||||
|
} |
||||||
|
render() { |
||||||
|
const { serverInfo } = this.props |
||||||
|
return ( |
||||||
|
<li> |
||||||
|
<Dropdown pullRight id="top-right-menu"> |
||||||
|
<Dropdown.Toggle noCaret> |
||||||
|
<i className="fa fa-reorder" /> |
||||||
|
</Dropdown.Toggle> |
||||||
|
<Dropdown.Menu className="dropdown-menu-right"> |
||||||
|
<li> |
||||||
|
<a target="_blank" href="https://github.com/minio/minio"> |
||||||
|
Github <i className="fa fa-github" /> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="" onClick={this.fullScreen}> |
||||||
|
Fullscreen <i className="fa fa-expand" /> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a target="_blank" href="https://docs.minio.io/"> |
||||||
|
Documentation <i className="fa fa-book" /> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a target="_blank" href="https://slack.minio.io"> |
||||||
|
Ask for help <i className="fa fa-question-circle" /> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="" id="show-about" onClick={this.showAbout.bind(this)}> |
||||||
|
About <i className="fa fa-info-circle" /> |
||||||
|
</a> |
||||||
|
{this.state.showAboutModal && ( |
||||||
|
<AboutModal |
||||||
|
serverInfo={serverInfo} |
||||||
|
hideAbout={this.hideAbout.bind(this)} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="" onClick={this.showChangePassword.bind(this)}> |
||||||
|
Change Password <i className="fa fa-cog" /> |
||||||
|
</a> |
||||||
|
{this.state.showChangePasswordModal && ( |
||||||
|
<ChangePasswordModal |
||||||
|
serverInfo={serverInfo} |
||||||
|
hideChangePassword={this.hideChangePassword.bind(this)} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="" id="logout" onClick={this.logout}> |
||||||
|
Sign Out <i className="fa fa-sign-out" /> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
</Dropdown.Menu> |
||||||
|
</Dropdown> |
||||||
|
</li> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
serverInfo: state.browser.serverInfo |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
fetchServerInfo: () => dispatch(browserActions.fetchServerInfo()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(BrowserDropdown) |
@ -0,0 +1,201 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { connect } from "react-redux" |
||||||
|
import web from "../web" |
||||||
|
import * as alertActions from "../alert/actions" |
||||||
|
|
||||||
|
import { |
||||||
|
Tooltip, |
||||||
|
Modal, |
||||||
|
ModalBody, |
||||||
|
ModalHeader, |
||||||
|
OverlayTrigger |
||||||
|
} from "react-bootstrap" |
||||||
|
import InputGroup from "./InputGroup" |
||||||
|
|
||||||
|
export class ChangePasswordModal extends React.Component { |
||||||
|
constructor(props) { |
||||||
|
super(props) |
||||||
|
this.state = { |
||||||
|
accessKey: "", |
||||||
|
secretKey: "", |
||||||
|
keysReadOnly: false |
||||||
|
} |
||||||
|
} |
||||||
|
// When its shown, it loads the access key and secret key.
|
||||||
|
componentWillMount() { |
||||||
|
const { serverInfo } = this.props |
||||||
|
|
||||||
|
// Check environment variables first.
|
||||||
|
if (serverInfo.info.isEnvCreds) { |
||||||
|
this.setState({ |
||||||
|
accessKey: "xxxxxxxxx", |
||||||
|
secretKey: "xxxxxxxxx", |
||||||
|
keysReadOnly: true |
||||||
|
}) |
||||||
|
} else { |
||||||
|
web.GetAuth().then(data => { |
||||||
|
this.setState({ |
||||||
|
accessKey: data.accessKey, |
||||||
|
secretKey: data.secretKey |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Handle field changes from inside the modal.
|
||||||
|
accessKeyChange(e) { |
||||||
|
this.setState({ |
||||||
|
accessKey: e.target.value |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
secretKeyChange(e) { |
||||||
|
this.setState({ |
||||||
|
secretKey: e.target.value |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
secretKeyVisible(secretKeyVisible) { |
||||||
|
this.setState({ |
||||||
|
secretKeyVisible |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Save the auth params and set them.
|
||||||
|
setAuth(e) { |
||||||
|
const { showAlert } = this.props |
||||||
|
const accessKey = this.state.accessKey |
||||||
|
const secretKey = this.state.secretKey |
||||||
|
web |
||||||
|
.SetAuth({ |
||||||
|
accessKey, |
||||||
|
secretKey |
||||||
|
}) |
||||||
|
.then(data => { |
||||||
|
showAlert({ |
||||||
|
type: "success", |
||||||
|
message: "Changed credentials" |
||||||
|
}) |
||||||
|
}) |
||||||
|
.catch(err => { |
||||||
|
showAlert({ |
||||||
|
type: "danger", |
||||||
|
message: err.message |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
generateAuth(e) { |
||||||
|
web.GenerateAuth().then(data => { |
||||||
|
this.setState({ |
||||||
|
accessKey: data.accessKey, |
||||||
|
secretKey: data.secretKey, |
||||||
|
secretKeyVisible: true |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { hideChangePassword } = this.props |
||||||
|
return ( |
||||||
|
<Modal bsSize="sm" animation={false} show={true}> |
||||||
|
<ModalHeader>Change Password</ModalHeader> |
||||||
|
<ModalBody className="m-t-20"> |
||||||
|
<InputGroup |
||||||
|
value={this.state.accessKey} |
||||||
|
onChange={this.accessKeyChange.bind(this)} |
||||||
|
id="accessKey" |
||||||
|
label="Access Key" |
||||||
|
name="accesskey" |
||||||
|
type="text" |
||||||
|
spellCheck="false" |
||||||
|
required="required" |
||||||
|
autoComplete="false" |
||||||
|
align="ig-left" |
||||||
|
readonly={this.state.keysReadOnly} |
||||||
|
/> |
||||||
|
<i |
||||||
|
onClick={this.secretKeyVisible.bind( |
||||||
|
this, |
||||||
|
!this.state.secretKeyVisible |
||||||
|
)} |
||||||
|
className={ |
||||||
|
"toggle-password fa fa-eye " + |
||||||
|
(this.state.secretKeyVisible ? "toggled" : "") |
||||||
|
} |
||||||
|
/> |
||||||
|
<InputGroup |
||||||
|
value={this.state.secretKey} |
||||||
|
onChange={this.secretKeyChange.bind(this)} |
||||||
|
id="secretKey" |
||||||
|
label="Secret Key" |
||||||
|
name="accesskey" |
||||||
|
type={this.state.secretKeyVisible ? "text" : "password"} |
||||||
|
spellCheck="false" |
||||||
|
required="required" |
||||||
|
autoComplete="false" |
||||||
|
align="ig-left" |
||||||
|
readonly={this.state.keysReadOnly} |
||||||
|
/> |
||||||
|
</ModalBody> |
||||||
|
<div className="modal-footer"> |
||||||
|
<button |
||||||
|
id="generate-keys" |
||||||
|
className={ |
||||||
|
"btn btn-primary " + (this.state.keysReadOnly ? "hidden" : "") |
||||||
|
} |
||||||
|
onClick={this.generateAuth.bind(this)} |
||||||
|
> |
||||||
|
Generate |
||||||
|
</button> |
||||||
|
<button |
||||||
|
id="update-keys" |
||||||
|
className={ |
||||||
|
"btn btn-success " + (this.state.keysReadOnly ? "hidden" : "") |
||||||
|
} |
||||||
|
onClick={this.setAuth.bind(this)} |
||||||
|
> |
||||||
|
Update |
||||||
|
</button> |
||||||
|
<button |
||||||
|
id="cancel-change-password" |
||||||
|
className="btn btn-link" |
||||||
|
onClick={hideChangePassword} |
||||||
|
> |
||||||
|
Cancel |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
serverInfo: state.browser.serverInfo |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
showAlert: alert => dispatch(alertActions.set(alert)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChangePasswordModal) |
@ -0,0 +1,70 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2016, 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
|
||||||
|
let InputGroup = ({ |
||||||
|
label, |
||||||
|
id, |
||||||
|
name, |
||||||
|
value, |
||||||
|
onChange, |
||||||
|
type, |
||||||
|
spellCheck, |
||||||
|
required, |
||||||
|
readonly, |
||||||
|
autoComplete, |
||||||
|
align, |
||||||
|
className |
||||||
|
}) => { |
||||||
|
var input = ( |
||||||
|
<input |
||||||
|
id={id} |
||||||
|
name={name} |
||||||
|
value={value} |
||||||
|
onChange={onChange} |
||||||
|
className="ig-text" |
||||||
|
type={type} |
||||||
|
spellCheck={spellCheck} |
||||||
|
required={required} |
||||||
|
autoComplete={autoComplete} |
||||||
|
/> |
||||||
|
) |
||||||
|
if (readonly) |
||||||
|
input = ( |
||||||
|
<input |
||||||
|
id={id} |
||||||
|
name={name} |
||||||
|
value={value} |
||||||
|
onChange={onChange} |
||||||
|
className="ig-text" |
||||||
|
type={type} |
||||||
|
spellCheck={spellCheck} |
||||||
|
required={required} |
||||||
|
autoComplete={autoComplete} |
||||||
|
disabled |
||||||
|
/> |
||||||
|
) |
||||||
|
return ( |
||||||
|
<div className={"input-group " + align + " " + className}> |
||||||
|
{input} |
||||||
|
<i className="ig-helpers" /> |
||||||
|
<label className="ig-label">{label}</label> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default InputGroup |
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { shallow } from "enzyme" |
||||||
|
import { AboutModal } from "../AboutModal" |
||||||
|
|
||||||
|
describe("AboutModal", () => { |
||||||
|
const serverInfo = { |
||||||
|
version: "test", |
||||||
|
memory: "test", |
||||||
|
platform: "test", |
||||||
|
runtime: "test" |
||||||
|
} |
||||||
|
|
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<AboutModal serverInfo={serverInfo} />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call hideAbout when close button is clicked", () => { |
||||||
|
const hideAbout = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<AboutModal serverInfo={serverInfo} hideAbout={hideAbout} /> |
||||||
|
) |
||||||
|
wrapper.find("button").simulate("click") |
||||||
|
expect(hideAbout).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,63 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { shallow } from "enzyme" |
||||||
|
import { BrowserDropdown } from "../BrowserDropdown" |
||||||
|
|
||||||
|
describe("BrowserDropdown", () => { |
||||||
|
const serverInfo = { |
||||||
|
version: "test", |
||||||
|
memory: "test", |
||||||
|
platform: "test", |
||||||
|
runtime: "test" |
||||||
|
} |
||||||
|
|
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow( |
||||||
|
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} /> |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call fetchServerInfo after its mounted", () => { |
||||||
|
const fetchServerInfo = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<BrowserDropdown |
||||||
|
serverInfo={serverInfo} |
||||||
|
fetchServerInfo={fetchServerInfo} |
||||||
|
/> |
||||||
|
) |
||||||
|
expect(fetchServerInfo).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
|
||||||
|
it("should show AboutModal when About link is clicked", () => { |
||||||
|
const wrapper = shallow( |
||||||
|
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} /> |
||||||
|
) |
||||||
|
wrapper.find("#show-about").simulate("click", { preventDefault: jest.fn() }) |
||||||
|
wrapper.update() |
||||||
|
expect(wrapper.state("showAboutModal")).toBeTruthy() |
||||||
|
expect(wrapper.find("AboutModal").length).toBe(1) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should logout and redirect to /login when logout is clicked", () => { |
||||||
|
const wrapper = shallow( |
||||||
|
<BrowserDropdown serverInfo={serverInfo} fetchServerInfo={jest.fn()} /> |
||||||
|
) |
||||||
|
wrapper.find("#logout").simulate("click", { preventDefault: jest.fn() }) |
||||||
|
expect(window.location.pathname.endsWith("/login")).toBeTruthy() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,109 @@ |
|||||||
|
/* |
||||||
|
* Minio Cloud Storage (C) 2018 Minio, Inc. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import React from "react" |
||||||
|
import { shallow, mount } from "enzyme" |
||||||
|
import { ChangePasswordModal } from "../ChangePasswordModal" |
||||||
|
|
||||||
|
jest.mock("../../web", () => ({ |
||||||
|
GetAuth: jest.fn(() => { |
||||||
|
return Promise.resolve({ accessKey: "test1", secretKey: "test2" }) |
||||||
|
}), |
||||||
|
GenerateAuth: jest.fn(() => { |
||||||
|
return Promise.resolve({ accessKey: "gen1", secretKey: "gen2" }) |
||||||
|
}), |
||||||
|
SetAuth: jest.fn(({ accessKey, secretKey }) => { |
||||||
|
if (accessKey == "test3" && secretKey == "test4") { |
||||||
|
return Promise.resolve({}) |
||||||
|
} else { |
||||||
|
return Promise.reject({ message: "Error" }) |
||||||
|
} |
||||||
|
}) |
||||||
|
})) |
||||||
|
|
||||||
|
describe("ChangePasswordModal", () => { |
||||||
|
const serverInfo = { |
||||||
|
version: "test", |
||||||
|
memory: "test", |
||||||
|
platform: "test", |
||||||
|
runtime: "test", |
||||||
|
info: { isEnvCreds: false } |
||||||
|
} |
||||||
|
|
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<ChangePasswordModal serverInfo={serverInfo} />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should get the keys when its rendered", () => { |
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />) |
||||||
|
setImmediate(() => { |
||||||
|
expect(wrapper.state("accessKey")).toBe("test1") |
||||||
|
expect(wrapper.state("secretKey")).toBe("test2") |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should show readonly keys when isEnvCreds is true", () => { |
||||||
|
const newServerInfo = { ...serverInfo, info: { isEnvCreds: true } } |
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={newServerInfo} />) |
||||||
|
expect(wrapper.state("accessKey")).toBe("xxxxxxxxx") |
||||||
|
expect(wrapper.state("secretKey")).toBe("xxxxxxxxx") |
||||||
|
expect(wrapper.find("#accessKey").prop("readonly")).toBeTruthy() |
||||||
|
expect(wrapper.find("#secretKey").prop("readonly")).toBeTruthy() |
||||||
|
expect(wrapper.find("#generate-keys").hasClass("hidden")).toBeTruthy() |
||||||
|
expect(wrapper.find("#update-keys").hasClass("hidden")).toBeTruthy() |
||||||
|
}) |
||||||
|
|
||||||
|
it("should generate accessKey and secretKey when Generate buttons is clicked", () => { |
||||||
|
const wrapper = shallow(<ChangePasswordModal serverInfo={serverInfo} />) |
||||||
|
wrapper.find("#generate-keys").simulate("click") |
||||||
|
setImmediate(() => { |
||||||
|
expect(wrapper.state("accessKey")).toBe("gen1") |
||||||
|
expect(wrapper.state("secretKey")).toBe("gen2") |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should update accessKey and secretKey when Update button is clicked", () => { |
||||||
|
const showAlert = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<ChangePasswordModal serverInfo={serverInfo} showAlert={showAlert} /> |
||||||
|
) |
||||||
|
wrapper |
||||||
|
.find("#accessKey") |
||||||
|
.simulate("change", { target: { value: "test3" } }) |
||||||
|
wrapper |
||||||
|
.find("#secretKey") |
||||||
|
.simulate("change", { target: { value: "test4" } }) |
||||||
|
wrapper.find("#update-keys").simulate("click") |
||||||
|
setImmediate(() => { |
||||||
|
expect(showAlert).toHaveBeenCalledWith({ |
||||||
|
type: "success", |
||||||
|
message: "Changed credentials" |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call hideChangePassword when Cancel button is clicked", () => { |
||||||
|
const hideChangePassword = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<ChangePasswordModal |
||||||
|
serverInfo={serverInfo} |
||||||
|
hideChangePassword={hideChangePassword} |
||||||
|
/> |
||||||
|
) |
||||||
|
wrapper.find("#cancel-change-password").simulate("click") |
||||||
|
expect(hideChangePassword).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
}) |
@ -1,56 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage (C) 2016, 2017 Minio, Inc. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import React from 'react' |
|
||||||
import connect from 'react-redux/lib/components/connect' |
|
||||||
import Dropdown from 'react-bootstrap/lib/Dropdown' |
|
||||||
|
|
||||||
let BrowserDropdown = ({fullScreenFunc, aboutFunc, settingsFunc, logoutFunc}) => { |
|
||||||
return ( |
|
||||||
<li> |
|
||||||
<Dropdown pullRight id="top-right-menu"> |
|
||||||
<Dropdown.Toggle noCaret> |
|
||||||
<i className="fa fa-reorder"></i> |
|
||||||
</Dropdown.Toggle> |
|
||||||
<Dropdown.Menu className="dropdown-menu-right"> |
|
||||||
<li> |
|
||||||
<a target="_blank" href="https://github.com/minio/minio">Github <i className="fa fa-github"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a href="" onClick={ fullScreenFunc }>Fullscreen <i className="fa fa-expand"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a target="_blank" href="https://docs.minio.io/">Documentation <i className="fa fa-book"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a target="_blank" href="https://slack.minio.io">Ask for help <i className="fa fa-question-circle"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a href="" onClick={ aboutFunc }>About <i className="fa fa-info-circle"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a href="" onClick={ settingsFunc }>Change Password <i className="fa fa-cog"></i></a> |
|
||||||
</li> |
|
||||||
<li> |
|
||||||
<a href="" onClick={ logoutFunc }>Sign Out <i className="fa fa-sign-out"></i></a> |
|
||||||
</li> |
|
||||||
</Dropdown.Menu> |
|
||||||
</Dropdown> |
|
||||||
</li> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
export default connect(state => state)(BrowserDropdown) |
|
@ -1,49 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import React from 'react' |
|
||||||
|
|
||||||
let InputGroup = ({label, id, name, value, onChange, type, spellCheck, required, readonly, autoComplete, align, className}) => { |
|
||||||
var input = <input id={ id } |
|
||||||
name={ name } |
|
||||||
value={ value } |
|
||||||
onChange={ onChange } |
|
||||||
className="ig-text" |
|
||||||
type={ type } |
|
||||||
spellCheck={ spellCheck } |
|
||||||
required={ required } |
|
||||||
autoComplete={ autoComplete } /> |
|
||||||
if (readonly) |
|
||||||
input = <input id={ id } |
|
||||||
name={ name } |
|
||||||
value={ value } |
|
||||||
onChange={ onChange } |
|
||||||
className="ig-text" |
|
||||||
type={ type } |
|
||||||
spellCheck={ spellCheck } |
|
||||||
required={ required } |
|
||||||
autoComplete={ autoComplete } |
|
||||||
disabled /> |
|
||||||
return <div className={ "input-group " + align + ' ' + className }> |
|
||||||
{ input } |
|
||||||
<i className="ig-helpers"></i> |
|
||||||
<label className="ig-label"> |
|
||||||
{ label } |
|
||||||
</label> |
|
||||||
</div> |
|
||||||
} |
|
||||||
|
|
||||||
export default InputGroup |
|
@ -1,204 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage (C) 2016 Minio, Inc. |
|
||||||
* |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
import React from 'react' |
|
||||||
import connect from 'react-redux/lib/components/connect' |
|
||||||
import * as actions from '../actions' |
|
||||||
|
|
||||||
import Tooltip from 'react-bootstrap/lib/Tooltip' |
|
||||||
import Modal from 'react-bootstrap/lib/Modal' |
|
||||||
import ModalBody from 'react-bootstrap/lib/ModalBody' |
|
||||||
import ModalHeader from 'react-bootstrap/lib/ModalHeader' |
|
||||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger' |
|
||||||
import InputGroup from './InputGroup' |
|
||||||
|
|
||||||
class SettingsModal extends React.Component { |
|
||||||
|
|
||||||
// When the settings are shown, it loads the access key and secret key.
|
|
||||||
componentWillMount() { |
|
||||||
const {web, dispatch} = this.props |
|
||||||
const {serverInfo} = this.props |
|
||||||
|
|
||||||
let accessKeyEnv = '' |
|
||||||
let secretKeyEnv = '' |
|
||||||
// Check environment variables first.
|
|
||||||
if (serverInfo.info.isEnvCreds) { |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: 'xxxxxxxxx', |
|
||||||
secretKey: 'xxxxxxxxx', |
|
||||||
keysReadOnly: true |
|
||||||
})) |
|
||||||
} else { |
|
||||||
web.GetAuth() |
|
||||||
.then(data => { |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: data.accessKey, |
|
||||||
secretKey: data.secretKey |
|
||||||
})) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// When they are re-hidden, the keys are unloaded from memory.
|
|
||||||
componentWillUnmount() { |
|
||||||
const {dispatch} = this.props |
|
||||||
|
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: '', |
|
||||||
secretKey: '', |
|
||||||
secretKeyVisible: false |
|
||||||
})) |
|
||||||
dispatch(actions.hideSettings()) |
|
||||||
} |
|
||||||
|
|
||||||
// Handle field changes from inside the modal.
|
|
||||||
accessKeyChange(e) { |
|
||||||
const {dispatch} = this.props |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: e.target.value |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
secretKeyChange(e) { |
|
||||||
const {dispatch} = this.props |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
secretKey: e.target.value |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
secretKeyVisible(secretKeyVisible) { |
|
||||||
const {dispatch} = this.props |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
secretKeyVisible |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
// Save the auth params and set them.
|
|
||||||
setAuth(e) { |
|
||||||
e.preventDefault() |
|
||||||
const {web, dispatch} = this.props |
|
||||||
|
|
||||||
let accessKey = document.getElementById('accessKey').value |
|
||||||
let secretKey = document.getElementById('secretKey').value |
|
||||||
web.SetAuth({ |
|
||||||
accessKey, |
|
||||||
secretKey |
|
||||||
}) |
|
||||||
.then(data => { |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: '', |
|
||||||
secretKey: '', |
|
||||||
secretKeyVisible: false |
|
||||||
})) |
|
||||||
dispatch(actions.hideSettings()) |
|
||||||
dispatch(actions.showAlert({ |
|
||||||
type: 'success', |
|
||||||
message: 'Changed credentials' |
|
||||||
})) |
|
||||||
}) |
|
||||||
.catch(err => { |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: '', |
|
||||||
secretKey: '', |
|
||||||
secretKeyVisible: false |
|
||||||
})) |
|
||||||
dispatch(actions.hideSettings()) |
|
||||||
dispatch(actions.showAlert({ |
|
||||||
type: 'danger', |
|
||||||
message: err.message |
|
||||||
})) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
generateAuth(e) { |
|
||||||
e.preventDefault() |
|
||||||
const {dispatch} = this.props |
|
||||||
|
|
||||||
web.GenerateAuth() |
|
||||||
.then(data => { |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
secretKeyVisible: true |
|
||||||
})) |
|
||||||
dispatch(actions.setSettings({ |
|
||||||
accessKey: data.accessKey, |
|
||||||
secretKey: data.secretKey |
|
||||||
})) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
hideSettings(e) { |
|
||||||
e.preventDefault() |
|
||||||
|
|
||||||
const {dispatch} = this.props |
|
||||||
dispatch(actions.hideSettings()) |
|
||||||
} |
|
||||||
|
|
||||||
render() { |
|
||||||
let {settings} = this.props |
|
||||||
|
|
||||||
return ( |
|
||||||
<Modal bsSize="sm" animation={ false } show={ true }> |
|
||||||
<ModalHeader> |
|
||||||
Change Password |
|
||||||
</ModalHeader> |
|
||||||
<ModalBody className="m-t-20"> |
|
||||||
<InputGroup value={ settings.accessKey } |
|
||||||
onChange={ this.accessKeyChange.bind(this) } |
|
||||||
id="accessKey" |
|
||||||
label="Access Key" |
|
||||||
name="accesskey" |
|
||||||
type="text" |
|
||||||
spellCheck="false" |
|
||||||
required="required" |
|
||||||
autoComplete="false" |
|
||||||
align="ig-left" |
|
||||||
readonly={ settings.keysReadOnly }></InputGroup> |
|
||||||
<i onClick={ this.secretKeyVisible.bind(this, !settings.secretKeyVisible) } className={ "toggle-password fa fa-eye " + (settings.secretKeyVisible ? "toggled" : "") } /> |
|
||||||
<InputGroup value={ settings.secretKey } |
|
||||||
onChange={ this.secretKeyChange.bind(this) } |
|
||||||
id="secretKey" |
|
||||||
label="Secret Key" |
|
||||||
name="accesskey" |
|
||||||
type={ settings.secretKeyVisible ? "text" : "password" } |
|
||||||
spellCheck="false" |
|
||||||
required="required" |
|
||||||
autoComplete="false" |
|
||||||
align="ig-left" |
|
||||||
readonly={ settings.keysReadOnly }></InputGroup> |
|
||||||
</ModalBody> |
|
||||||
<div className="modal-footer"> |
|
||||||
<button className={ "btn btn-primary " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.generateAuth.bind(this) }> |
|
||||||
Generate |
|
||||||
</button> |
|
||||||
<button href="" className={ "btn btn-success " + (settings.keysReadOnly ? "hidden" : "") } onClick={ this.setAuth.bind(this) }> |
|
||||||
Update |
|
||||||
</button> |
|
||||||
<button href="" className="btn btn-link" onClick={ this.hideSettings.bind(this) }> |
|
||||||
Cancel |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
</Modal> |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default connect(state => { |
|
||||||
return { |
|
||||||
web: state.web, |
|
||||||
settings: state.settings, |
|
||||||
serverInfo: state.serverInfo |
|
||||||
} |
|
||||||
})(SettingsModal) |
|
Loading…
Reference in new issue