summaryrefslogtreecommitdiff
path: root/ui/app
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/components/main_panel/email_section.js97
-rw-r--r--ui/app/components/main_panel/imap_button.js121
-rw-r--r--ui/app/components/main_panel/main_panel.less1
-rw-r--r--ui/app/css/common.css28
-rw-r--r--ui/app/img/error.svg107
5 files changed, 346 insertions, 8 deletions
diff --git a/ui/app/components/main_panel/email_section.js b/ui/app/components/main_panel/email_section.js
index a3ff11c..2bb5865 100644
--- a/ui/app/components/main_panel/email_section.js
+++ b/ui/app/components/main_panel/email_section.js
@@ -1,10 +1,57 @@
import React from 'react'
-//import { Button, Glyphicon, Alert } from 'react-bootstrap'
+import { Button, Glyphicon, Alert } from 'react-bootstrap'
+
import SectionLayout from './section_layout'
+import IMAPButton from './imap_button'
+
import Account from 'models/account'
import Spinner from 'components/spinner'
import bitmask from 'lib/bitmask'
+const GENERAL_NOTICES = [
+ "KEYMANAGER_KEY_FOUND", // (address)
+ "KEYMANAGER_KEY_NOT_FOUND", // (address)
+ "KEYMANAGER_LOOKING_FOR_KEY", // (address)
+ "KEYMANAGER_DONE_UPLOADING_KEYS", // (address)
+
+ "SMTP_START_ENCRYPT_AND_SIGN", // (from_addr)
+ "SMTP_END_ENCRYPT_AND_SIGN", // (from_addr)
+ "SMTP_START_SIGN", // (from_addr)
+ "SMTP_END_SIGN", // (from_addr)
+ "SMTP_SEND_MESSAGE_START", // (from_addr)
+ "SMTP_SEND_MESSAGE_SUCCESS" // (from_addr)
+]
+
+const ACCOUNT_NOTICES = [
+ "IMAP_CLIENT_LOGIN", // (username)
+
+ "MAIL_FETCHED_INCOMING", // (userid)
+ "MAIL_MSG_DECRYPTED", // (userid)
+ "MAIL_MSG_DELETED_INCOMING", // (userid)
+ "MAIL_MSG_PROCESSING", // (userid)
+ "MAIL_MSG_SAVED_LOCALLY", // (userid)
+
+ "SMTP_RECIPIENT_ACCEPTED_ENCRYPTED", // (userid, dest)
+ "SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED", // (userid, dest)
+ "SMTP_RECIPIENT_REJECTED", // (userid, dest)
+ "SMTP_SEND_MESSAGE_ERROR" // (userid, dest)
+]
+
+const STATUSES = [
+ "KEYMANAGER_FINISHED_KEY_GENERATION", // (address)
+ "KEYMANAGER_STARTED_KEY_GENERATION", // (address)
+ "SMTP_SERVICE_STARTED",
+ "MAIL_UNREAD_MESSAGES", // (userid, number)
+ "IMAP_SERVICE_STARTED"
+]
+
+const STATUS_ERRORS = [
+ "IMAP_SERVICE_FAILED_TO_START",
+ "IMAP_UNHANDLED_ERROR",
+ "SMTP_SERVICE_FAILED_TO_START",
+ "SMTP_CONNECTION_LOST", // (userid, dest)
+]
+
export default class EmailSection extends React.Component {
static get defaultProps() {return{
@@ -14,17 +61,46 @@ export default class EmailSection extends React.Component {
constructor(props) {
super(props)
this.state = {
- status: null
+ status: 'unknown', // on, off, unknown, wait, disabled, error
+ messages: [],
+ expanded: true
}
+ this.expand = this.expand.bind(this)
this.openKeys = this.openKeys.bind(this)
this.openApp = this.openApp.bind(this)
this.openPrefs = this.openPrefs.bind(this)
+ this.logEvent = this.logEvent.bind(this)
+ }
+
+ componentWillMount() {
+ let events = [].concat(GENERAL_NOTICES, ACCOUNT_NOTICES, STATUSES, STATUS_ERRORS)
+ for (let event of events) {
+ bitmask.events.register(event, this.logEvent)
+ }
+ bitmask.mail.status().then(status => {
+ // either 'running' or 'disabled'
+ let newstatus = 'error'
+ if (status['mail'] == 'running') {
+ newstatus = 'on'
+ } else if (status['mail'] == 'disabled') {
+ newstatus = 'disabled'
+ }
+ this.setState({status: newstatus})
+ })
+ }
+
+ logEvent(event, msg) {
+ console.log("EVENT: " + event, msg)
}
openKeys() {}
openApp() {}
openPrefs() {}
+ expand() {
+ this.setState({expanded: !this.state.expanded})
+ }
+
render () {
//let message = null
//if (this.state.error) {
@@ -34,13 +110,22 @@ export default class EmailSection extends React.Component {
// )
//}
let button = null
- if (this.state.status == 'ready') {
+ let body = null
+ let header = <h1>Mail</h1>
+ if (this.state.status == 'on') {
button = <Button onClick={this.openApp}>Open Email</Button>
}
+ if (this.state.status == 'disabled') {
+ header = <h1>Mail Disabled</h1>
+ }
+ if (this.state.expanded) {
+ body = (
+ <IMAPButton account={this.props.account} />
+ )
+ }
return (
- <SectionLayout icon="envelope" status="on" button={button}>
- <h1>inbox: </h1>
- </SectionLayout>
+ <SectionLayout icon="envelope" status={this.state.status}
+ onExpand={this.expand} buttons={button} header={header} body={body} />
)
}
}
diff --git a/ui/app/components/main_panel/imap_button.js b/ui/app/components/main_panel/imap_button.js
new file mode 100644
index 0000000..98d8bad
--- /dev/null
+++ b/ui/app/components/main_panel/imap_button.js
@@ -0,0 +1,121 @@
+//
+// Button to show details for configuring mail clients
+//
+
+import React from 'react'
+import { Modal, Form, FormGroup, ControlLabel, FormControl, Col, Label, Button} from 'react-bootstrap'
+import Account from 'models/account'
+import bitmask from 'lib/bitmask'
+
+export default class IMAPButton extends React.Component {
+
+ static get defaultProps() {return{
+ account: null,
+ title: "Connect Mail Client"
+ }}
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ showModal: false,
+ imapPort: '1984',
+ smtpPort: '2013',
+ token: ''
+ }
+ this.onClick = this.onClick.bind(this)
+ this.onClose = this.onClose.bind(this)
+ }
+
+ onClose() {
+ this.setState({showModal: false})
+ }
+
+ onClick() {
+ if (!this.state.token) {
+ bitmask.mail.get_token().then(response => {
+ if (response.user == this.props.account.address) {
+ this.setState({token: response.token})
+ }
+ })
+ }
+ this.setState({showModal: true})
+ }
+
+ componentWillMount() {}
+
+ // don't allow fields to be changed
+ onChange() {}
+
+ render () {
+ let rowStyle = {height: '30px'} // to match bootstrap's input element height
+ let form = null
+ let modal = null
+
+ if (this.state.showModal) {
+ form = (
+ <Form horizontal>
+ <p>
+ You can use any application that supports IMAP to read and send
+ email through Bitmask.
+ </p>
+ <h3>Configuration for Thunderbird</h3>
+ <p>
+ For Thunderbird, you can use the Bitmask extension. Search for
+ "Bitmask" in Thunderbird's add-on manager.
+ </p>
+ <h3>Configuration for other mail clients</h3>
+ <p>
+ Alternately, configure your mail client with the following options:
+ </p>
+ <FormGroup>
+ <Col sm={2} componentClass={ControlLabel}>Username</Col>
+ <Col sm={10}>
+ <FormControl value={this.props.account.address} onChange={this.onChange}/>
+ </Col>
+ </FormGroup>
+ <FormGroup>
+ <Col sm={2} componentClass={ControlLabel}>Password</Col>
+ <Col sm={10}>
+ <FormControl value={this.state.token} onChange={this.onChange}/>
+ </Col>
+ </FormGroup>
+ <FormGroup>
+ <Col sm={2} componentClass={ControlLabel}>IMAP</Col>
+ <Col sm={10} className="center-vertical" style={rowStyle}>
+ <div className="center-item">
+ <Label>Host</Label> localhost
+ &nbsp;&nbsp;&nbsp;
+ <Label>Port</Label> {this.state.imapPort}
+ </div>
+ </Col>
+ </FormGroup>
+ <FormGroup>
+ <Col sm={2} componentClass={ControlLabel}>SMTP</Col>
+ <Col sm={10} className="center-vertical" style={rowStyle}>
+ <div className="center-item">
+ <Label>Host</Label> localhost
+ &nbsp;&nbsp;&nbsp;
+ <Label>Port</Label> {this.state.smtpPort}
+ </div>
+ </Col>
+ </FormGroup>
+ </Form>
+ )
+ modal = (
+ <Modal show={true} onHide={this.onClose}>
+ <Modal.Header closeButton>
+ <Modal.Title>{this.props.title}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ {form}
+ </Modal.Body>
+ </Modal>
+ )
+ }
+
+ return (
+ <Button onClick={this.onClick}>{this.props.title} {modal}</Button>
+ )
+ }
+
+}
diff --git a/ui/app/components/main_panel/main_panel.less b/ui/app/components/main_panel/main_panel.less
index 2e97913..7383a6c 100644
--- a/ui/app/components/main_panel/main_panel.less
+++ b/ui/app/components/main_panel/main_panel.less
@@ -229,6 +229,7 @@
}
.icon {
padding-right: @section-padding;
+ height: @icon-size;
img {
width: @icon-size;
height: @icon-size;
diff --git a/ui/app/css/common.css b/ui/app/css/common.css
index fcae7fa..a0d2c35 100644
--- a/ui/app/css/common.css
+++ b/ui/app/css/common.css
@@ -63,15 +63,38 @@ body {
*/
.center-container {
-
display: -webkit-flex;
display: flex;
+
-webkit-flex-flow: row nowrap;
flex-flow: row nowrap;
+
-webkit-justify-content: center;
justify-content: center;
+
+ -webkit-align-content: center;
+ align-content: center;
+
+ -webkit-align-items: center;
+ align-items: center;
+}
+
+.center-vertical {
+ display: -webkit-flex;
+ display: flex;
+
+ -webkit-flex-direction: row;
+ flex-direction: row;
+
+ -webkit-flex-flow: row nowrap;
+ flex-flow: row nowrap;
+
+ -webkit-justify-content: flex-start;
+ justify-content: flex-start;
+
-webkit-align-content: center;
align-content: center;
+
-webkit-align-items: center;
align-items: center;
}
@@ -84,7 +107,8 @@ body {
width: 100%;
}
-.center-container .center-item {
+.center-container .center-item,
+.center-vertical .center-item {
-webkit-flex: 0 1 auto;
flex: 0 1 auto;
}
diff --git a/ui/app/img/error.svg b/ui/app/img/error.svg
new file mode 100644
index 0000000..69024a4
--- /dev/null
+++ b/ui/app/img/error.svg
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="32px"
+ height="32px"
+ id="svg3243"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="error.svg">
+ <defs
+ id="defs3245" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313709"
+ inkscape:cx="13.669808"
+ inkscape:cy="12.217922"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:snap-global="false"
+ inkscape:window-width="1330"
+ inkscape:window-height="813"
+ inkscape:window-x="473"
+ inkscape:window-y="143"
+ inkscape:window-maximized="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3273" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3248">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#808080;stroke:#000000;stroke-width:1.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:FontAwesome;-inkscape-font-specification:FontAwesome;fill-opacity:1"
+ d="m 26.607099,9.8590516 c 1.095245,1.8715414 1.642877,3.9185224 1.642901,6.1409504 -2.4e-5,2.222445 -0.547656,4.274746 -1.642901,6.156901 -1.095291,1.87153 -2.584007,3.354927 -4.466146,4.450195 C 20.269405,27.702363 18.222426,28.249996 16,28.25 13.77755,28.249996 11.725251,27.702363 9.8430986,26.607098 7.9715637,25.51183 6.4881674,24.028433 5.3929018,22.156903 4.2976327,20.274748 3.75,18.222447 3.75,16.000002 3.75,13.777574 4.2976327,11.730593 5.3929018,9.8590516 6.4881674,7.9769071 7.9715637,6.4881926 9.8430986,5.392906 11.725251,4.2976563 13.77755,3.7500236 16,3.75 c 2.222426,2.36e-5 4.269405,0.5476563 6.140953,1.642906 1.882139,1.0952866 3.370855,2.5840011 4.466146,4.4661456"
+ id="path3275"
+ inkscape:connector-curvature="0" />
+ <g
+ id="g3758"
+ transform="matrix(0.865313,0,0,0.865313,2.225876,2.050981)"
+ style="fill:#d50000;stroke:#000000;stroke-opacity:1;stroke-width:0.88407316;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:round">
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:#d50000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.73702008;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path4142"
+ sodipodi:sides="3"
+ sodipodi:cx="16.06254"
+ sodipodi:cy="16.047972"
+ sodipodi:r1="8.6266594"
+ sodipodi:r2="4.3161001"
+ sodipodi:arg1="1.1296168"
+ sodipodi:arg2="2.2594356"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 19.746178,23.848617 13.319714,19.380482 7.4651642,15.337774 14.547914,12.006359 20.976278,8.9575249 20.319992,16.757074 Z"
+ inkscape:transform-center-x="0.0023687104"
+ inkscape:transform-center-y="-2.2364873"
+ transform="matrix(0.98627727,-0.68272586,0.68272586,0.98627727,-10.759086,12.115158)" />
+ </g>
+ <rect
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.06500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4163"
+ width="1.5"
+ height="1.5"
+ x="15.275888"
+ y="18.618069"
+ ry="0.9375"
+ rx="0.9375" />
+ <rect
+ rx="0.9375"
+ ry="0.93750006"
+ y="10.975301"
+ x="15.272097"
+ height="6.9514756"
+ width="1.5"
+ id="rect4165"
+ style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.06500006;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+</svg>