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