Skip to content

Commit

Permalink
Permissions (#15)
Browse files Browse the repository at this point in the history
* Local settings menu

* Move signer minimize out of local state

* Update settings icon

* Initial Signer Indicator (#9)

* Update indicator, add signer settings menu

* Add local node settings

* Dapp permission requests

* Add ability to toggle permissions

* Add functionality to test dapp (#14)

Add functionality to test dapp

* Toggle dapp permissions

* Apply style to local settings

* Hide permissions when unselected, hide window on blur
  • Loading branch information
floating authored Mar 21, 2018
1 parent c5e1a17 commit 12aecf9
Show file tree
Hide file tree
Showing 20 changed files with 622 additions and 115 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,25 @@ Frame interfaces with the Ethereum network and signature providers (such as a Le
- Frame stays out of the way and sits quietly in your menu bar until it's needed.
- **Cross Platform**
- macOS, Windows and Linux!

### Downloads
- **Frame is currently under development.** macOS, Windows and Linux distributions will be made available soon.
- **Frame is currently under development.** macOS, Windows and Linux distributions will be made available soon.

### Try it!
```bash
# Clone Frame
› git clone https://github.com/floating/frame

# Run Frame Demo
› npm run demo
# Build Frame
› npm run build

# Run Frame
› npm run dev
```

**On Windows:** Run `npm install --global --production windows-build-tools` as administrator **before** running the demo. You can find more info about this here: https://github.com/felixrieseberg/windows-build-tools.

**On Ubuntu:** Run `sudo apt-get install libappindicator1` **before** running the demo. You can find more info about this here: https://github.com/electron/electron/issues/1347.

### Related
- [Restore](https://github.com/floating/restore) - A predictable and observable state container for React apps.
26 changes: 26 additions & 0 deletions app/App/Panel/Settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import Restore from 'react-restore'

class Settings extends React.Component {
render () {
return (
<div className='signerSettings'>
<div className='signerSettingsTitle'>{'Local Settings'}</div>
<div className='signerPermission' onClick={_ => this.store.runLocalNode()}>
<div className='signerPermissionOrigin'>{'Run Local Node'}</div>
<div className={this.store('local.node.run') ? 'signerPermissionToggle signerPermissionToggleOn' : 'signerPermissionToggle'}>
<div className='signerPermissionToggleSwitch' />
</div>
</div>
<div className='signerPermission' onClick={_ => this.store.runOnStartup()}>
<div className='signerPermissionOrigin'>{'Run on Startup'}</div>
<div className={this.store('local.startup') ? 'signerPermissionToggle signerPermissionToggleOn' : 'signerPermissionToggle'}>
<div className='signerPermissionToggleSwitch' />
</div>
</div>
</div>
)
}
}

export default Restore.connect(Settings)
35 changes: 35 additions & 0 deletions app/App/Panel/Settings/style/index.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.settings {
width 100%
display flex
justify-content center
align-items center
padding 0px 0px 20px 0px
flex-direction column
}

.settingsTitle {
width 100%
display flex
justify-content center
align-items center
padding 20px 0px 20px 0px
background rgba(255, 255, 255, 0.8)
}

.settingsItems {
width 100%
display flex
justify-content center
align-items center
padding 20px 0px 20px 0px
background rgba(0,0,50,0.1)
flex-direction column
}

.settingsItem {
width 100%
display flex
justify-content flex-start
align-items center
padding 20px
}
87 changes: 58 additions & 29 deletions app/App/Panel/Signer/Requests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,43 +35,72 @@ class Requests extends React.Component {
decline (reqId) {
this.store.declineRequest(reqId)
}
transactionRequest (req, i) {
let requestClass = 'signerRequest'
if (req.status === 'success') requestClass += ' signerRequestSuccess'
if (req.status === 'declined') requestClass += ' signerRequestDeclined'
if (req.status === 'pending') requestClass += ' signerRequestPending'
return (
<div key={i} className={requestClass}>
{req.type === 'approveTransaction' ? (
<div className='approveTransaction'>
<div className='approveTransactionTitle'>
{'Approve Transaction'}
</div>
<div className='approveTransactionPayload'>
{req.notice ? (
<div className='requestNotice'>{req.notice}</div>
) : (
Object.keys(req.data).map(p => <div key={p}>{p + ': ' + req.data[p]}</div>)
)}
</div>
</div>
) : (
<div className='unknownType'>{'Unknown: ' + req.type}</div>
)}
<div className='requestApprove'>
<div className='requestDecline' onClick={() => this.decline(req.handlerId)}>{'Decline'}</div>
<div className='requestSign' onClick={() => this.approve(req.handlerId, req)}>{'Sign'}</div>
</div>
</div>
)
}
providerRequest (req, i) {
let requestClass = 'signerRequest'
if (req.status === 'success') requestClass += ' signerRequestSuccess'
if (req.status === 'declined') requestClass += ' signerRequestDeclined'
if (req.status === 'pending') requestClass += ' signerRequestPending'
return (
<div key={i} className={requestClass}>
{req.type === 'requestProvider' ? (
<div className='approveTransaction'>
<div className='approveTransactionTitle'>{'Request Provider Access'}</div>
{req.notice ? <div className='requestNotice'>{req.notice}</div> : null}
</div>
) : (
<div className='unknownType'>{'Unknown: ' + req.type}</div>
)}
<div className='requestApprove'>
<div className='requestDecline' onClick={() => this.store.giveAccess(req.origin, false)}>{'Decline'}</div>
<div className='requestSign' onClick={() => this.store.giveAccess(req.origin, true)}>{'Approve'}</div>
</div>
</div>
)
}
render () {
let current = this.store('signer.current') === this.props.id
let selected = current && !this.props.minimized
let requests = this.store('signer.requests')
requests = Object.keys(requests).map(key => requests[key]).filter(req => this.props.accounts.map(a => a.toLowerCase()).indexOf(req && req.data ? req.data.from.toLowerCase() : null) > -1)
requests = Object.keys(requests).map(key => requests[key]).filter(req => {
if (req.type === 'approveTransaction') return this.props.accounts.map(a => a.toLowerCase()).indexOf(req && req.data ? req.data.from.toLowerCase() : null) > -1
return true
})
return (selected ? (
<div className='signerRequests'>
{requests.length > 0 ? (
requests.map((req, i) => {
let requestClass = 'signerRequest'
if (req.status === 'success') requestClass += ' signerRequestSuccess'
if (req.status === 'declined') requestClass += ' signerRequestDeclined'
if (req.status === 'pending') requestClass += ' signerRequestPending'
return (
<div key={i} className={requestClass}>
{req.type === 'approveTransaction' ? (
<div className='approveTransaction'>
<div className='approveTransactionTitle'>
{'Approve Transaction'}
</div>
<div className='approveTransactionPayload'>
{req.notice ? (
<div className='requestNotice'>{req.notice}</div>
) : (
Object.keys(req.data).map(p => <div key={p}>{p + ': ' + req.data[p]}</div>)
)}
</div>
</div>
) : (
<div className='unknownType'>{'Unknown: ' + req.type}</div>
)}
<div className='requestApprove'>
<div className='requestDecline' onClick={() => this.decline(req.handlerId)}>{'Decline'}</div>
<div className='requestSign' onClick={() => this.approve(req.handlerId, req)}>{'Sign'}</div>
</div>
</div>
)
if (req.type === 'approveTransaction') return this.transactionRequest(req, i)
if (req.type === 'requestProvider') return this.providerRequest(req, i)
})
) : (
<div className='signerRequests'>
Expand Down
42 changes: 33 additions & 9 deletions app/App/Panel/Signer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import rpc from '../../../rpc'
import Requests from './Requests'

class Signer extends React.Component {
constructor (...args) {
super(...args)
this.state = {minimized: true}
}
trezorPin (num) {
this.tPin = this.tPin ? this.tPin + num.toString() : num.toString()
if (this.tPin.length === 4) {
Expand All @@ -22,27 +18,39 @@ class Signer extends React.Component {
}
}
select () {
this.setState({minimized: !this.state.minimized})
this.store.toggleMinimized()
let current = this.store('signer.current') === this.props.id
if (!current) {
rpc('setSigner', this.props.id, (err, status) => {
if (err) return console.log(err)
// Signer Change Success
})
}
}
render () {
let current = this.store('signer.current') === this.props.id
this.selected = current && !this.state.minimized
let minimized = this.store('signer.minimized')
this.selected = current && !minimized
let type = this.props.type
let signerClass = current ? 'signer signer.current' : 'signer'
let signerClass = current ? 'signer current' : 'signer'
let signerIndicator = current ? 'signerIndicator signerIndicatorActive' : 'signerIndicator'
let signerSettings = this.selected ? 'signerSettingsMenu signerSettingsActive' : 'signerSettingsMenu'
if (this.selected) signerClass += ' selectedSigner'
if (this.props.status === 'ok') signerClass += ' okSigner'
if (this.props.status === 'loading') return null
return (
<div className={signerClass}>
<div className='signerWrap'>
<div className='signerTop'>
<div className={signerIndicator}>
<div className='signerIndicatorIcon'>
<span dangerouslySetInnerHTML={{__html: octicons['pulse'].toSVG({height: 23})}} />
</div>
</div>
<div onClick={() => this.store.toggelSignerSettings()} className={signerSettings}>
<div className='signerIndicatorIcon'>
<span dangerouslySetInnerHTML={{__html: octicons['gear'].toSVG({height: 21})}} />
</div>
</div>
<div className='signerType' onClick={() => { if (this.props.status === 'ok') this.select() }}>
<div className='signerImage'>
{(_ => {
Expand All @@ -68,7 +76,23 @@ class Signer extends React.Component {
<div className='signerAddress'>{this.props.accounts}</div>
</div>
) : <div className='signerStatus'>{this.props.status}</div>}
<Requests id={this.props.id} accounts={this.props.accounts} minimized={this.state.minimized} />
{this.selected && this.store('signer.view') === 'settings' ? (
<div className='signerSettings'>
<div className='signerSettingsTitle'>{'Dapp Permissions'}</div>
{Object.keys(this.store('permissions')).sort().map(o => {
return (
<div className='signerPermission' onClick={_ => this.store.toggleAccess(o)}>
<div className='signerPermissionOrigin'>{o}</div>
<div className={this.store('permissions', o).provider ? 'signerPermissionToggle signerPermissionToggleOn' : 'signerPermissionToggle'}>
<div className='signerPermissionToggleSwitch' />
</div>
</div>
)
})}
</div>
) : (
<Requests id={this.props.id} accounts={this.props.accounts} minimized={minimized} />
)}
{type === 'Trezor' && this.props.status === 'Need Pin' ? (
<div className='trezorPinWrap'>
<div className='trezorPinInput'>
Expand Down
Loading

0 comments on commit 12aecf9

Please sign in to comment.