Skip to content

Commit

Permalink
Merge pull request #47 from secretin/ImportExport
Browse files Browse the repository at this point in the history
Import export
  • Loading branch information
agix authored Sep 21, 2017
2 parents 70b7efe + e0834d2 commit ba86040
Show file tree
Hide file tree
Showing 18 changed files with 567 additions and 517 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"react-dom": "^15.6.1",
"react-overlays": "^0.8.0",
"react-router-dom": "^4.1.2",
"secretin": "^1.8.1",
"secretin": "^1.8.3",
"url-join": "^2.0.2",
"uuid": "^3.1.0"
},
Expand Down
59 changes: 59 additions & 0 deletions src/actions/ImportActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { findKey } from 'lodash';

import alt from 'utils/alt';
import importers from 'utils/importers';

class ImportActions {
constructor() {
this.generateActions(
'importSecretsProgress',
'importSecretsSuccess',
'importSecretsFailure',
'detectTypeSuccess',
'detectTypeFailure',
'defaultStore',
'changeMandatoryField'
);
}

detectType({ file }) {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = ({ target }) => {
const file = target.result;
const importType = findKey(importers, importer => importer.detect(file));

if (typeof importType !== 'undefined') {
const mandatoryFields = importers[importType].mandatoryFields;
this.detectTypeSuccess({ file, importType, mandatoryFields });
} else {
this.detectTypeFailure({ error: 'Invalid type' });
}
};
return reader;
}

importSecrets({ file, type, mandatoryFields }) {
return dispatch => {
dispatch();
importers[type]
.parse(
file,
mandatoryFields.toJS(),
({ state: importStatus, total: importTotal }) =>
this.importSecretsProgress({
importStatus,
importTotal,
})
)
.then(() => {
this.importSecretsSuccess();
})
.catch(error => {
this.importSecretsFailure({ error });
});
};
}
}

export default alt.createActions(ImportActions);
23 changes: 0 additions & 23 deletions src/actions/OptionsActions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import alt from 'utils/alt';
import secretin from 'utils/secretin';
import { parseKeepass } from 'utils/import';
import uuid from 'uuid';

class OptionsActions {
Expand All @@ -14,11 +13,6 @@ class OptionsActions {
'activateShortLoginFailure',
'deactivateShortLoginSuccess',
'deactivateShortLoginFailure',
'showImportKeepass',
'hideImportKeepass',
'importKeepassProgress',
'importKeepassSuccess',
'importKeepassFailure',
'hideQRCode',
'hideShortLogin',
'changeDelaySuccess',
Expand Down Expand Up @@ -101,23 +95,6 @@ class OptionsActions {
return this.deactivateTotp();
}

importKeepass({ file }) {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = readedFile =>
parseKeepass(readedFile.target.result, (importStatus, importTotal) =>
this.importKeepassProgress({ importStatus, importTotal })
)
.then(() => {
this.importKeepassSuccess();
})
.catch(error => {
this.importKeepassFailure({ error });
});

return reader;
}

showRescueCodes() {
return dispatch => {
dispatch();
Expand Down
2 changes: 2 additions & 0 deletions src/components/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Sidebar from 'components/Sidebar';
import SecretShow from 'components/secrets/SecretShow';
import SecretListContainer from 'components/secrets/SecretListContainer';
import OptionsContainer from 'components/options/OptionsContainer';
import ImportContainer from 'components/import/ImportContainer';

function Layout() {
return (
Expand All @@ -32,6 +33,7 @@ function Layout() {
render={props => <SecretListContainer {...props} />}
/>
<Route path="/settings/" component={OptionsContainer} />
<Route path="/import/" component={ImportContainer} />
<Redirect to="/secrets/" />
</Switch>
</div>
Expand Down
37 changes: 37 additions & 0 deletions src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@ import React from 'react';
import PropTypes from 'prop-types';
import Link from 'react-router-dom/Link';
import NavLink from 'react-router-dom/NavLink';
import moment from 'moment';

import secretin from 'utils/secretin';
import AppUIStore from 'stores/AppUIStore';
import Icon from 'components/utilities/Icon';

function download(filename, text) {
var element = document.createElement('a');
element.setAttribute(
'href',
`data:application/json;charset=utf-8,${encodeURIComponent(text)}`
);
element.setAttribute('download', filename);

element.style.display = 'none';
document.body.appendChild(element);

element.click();

document.body.removeChild(element);
}

function SidebarMenuLink({ children, ...props }) {
return (
<li className="sidebar-menu-item">
Expand All @@ -28,6 +45,15 @@ SidebarMenuLink.propTypes = {
]),
};

function exportDb() {
secretin.exportDb().then(db => {
download(
`Secret-in_${secretin.currentUser.username}_${moment().format()}.json`,
db
);
});
}

function Sidebar() {
const currentUser = AppUIStore.getCurrentUser();
return (
Expand Down Expand Up @@ -61,6 +87,17 @@ function Sidebar() {
<Icon id="gear" size="base" />
Settings
</SidebarMenuLink>
<div className="sidebar-separator" />
<li className="sidebar-menu-item">
<a className="sidebar-menu-link" onClick={exportDb}>
<Icon id="export" size="base" />
Export secrets
</a>
</li>
<SidebarMenuLink to="/import/">
<Icon id="import" size="base" />
Import secrets
</SidebarMenuLink>
</ul>
</div>
</div>
Expand Down
133 changes: 133 additions & 0 deletions src/components/import/ImportContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import connectToStores from 'alt-utils/lib/connectToStores';

import Title from 'components/utilities/Title';
import Button from 'components/utilities/Button';
import Icon from 'components/utilities/Icon';
import Spinner from 'components/utilities/Spinner';

import ImportStore from 'stores/ImportStore';
import ImportActions from 'actions/ImportActions';
import MetadataActions from 'actions/MetadataActions';

import ImportFileChooser from './ImportFileChooser';
import ImportMandatoryFields from './ImportMandatoryFields';

class ImportContainer extends Component {
static propTypes = {
importType: PropTypes.string,
importing: PropTypes.bool,
importStatus: PropTypes.number,
importTotal: PropTypes.number,
success: PropTypes.bool,
file: PropTypes.string,
error: PropTypes.string,
mandatoryFields: PropTypes.instanceOf(Immutable.Map),
};

static getStores() {
return [ImportStore];
}

static getPropsFromStores() {
const state = ImportStore.getState();

return {
error: state.get('error'),
importType: state.get('importType'),
importing: state.get('importing'),
importStatus: state.get('importStatus'),
importTotal: state.get('importTotal'),
success: state.get('success'),
file: state.get('file'),
mandatoryFields: state.get('mandatoryFields'),
};
}

constructor(props) {
super(props);

this.handleFileChoosen = this.handleFileChoosen.bind(this);
this.handleStartParsing = this.handleStartParsing.bind(this);
}

handleFileChoosen(file) {
ImportActions.detectType({ file });
}

handleStartParsing() {
ImportActions.importSecrets({
file: this.props.file,
mandatoryFields: this.props.mandatoryFields,
type: this.props.importType,
});
}

shouldComponentUpdate(nextProps, nextState) {
return (
this.props.success !== true || nextProps.success !== this.props.success
);
}

componentDidUpdate() {
if (this.props.success) {
MetadataActions.loadMetadata();
setTimeout(function() {
ImportActions.defaultStore();
}, 1500);
}
}

render() {
return (
<div className="page">
<div className="page-header">
<div className="breadcrumb">
<Title link="/import/" icon="import" title="Import" />
</div>
</div>

<div className="page-content options">
<span>
Supported type are <i>secret-in</i>, <i>keepass</i>
</span>
{((this.props.success || this.props.importing) &&
((this.props.success &&
<div className="import-progress">
<Icon id="done" size={120} />
<div className="import-progress-title">Done!</div>
</div>) ||
<div className="import-progress">
<Spinner />
{this.props.importTotal !== 0 &&
<div className="import-progress-title">
{`Importing... ${this.props.importStatus} / ${this.props
.importTotal}`}
</div>}
</div>)) ||
<ImportFileChooser onFileChoosen={this.handleFileChoosen} />}
{this.props.error !== '' &&
<span>
{this.props.error}
</span>}
{this.props.mandatoryFields.size > 0 &&
<ImportMandatoryFields
mandatoryFields={this.props.mandatoryFields}
/>}
{this.props.importType !== '' &&
<Button
buttonStyle="primary"
className="button button--style-default button--size-base"
onClick={this.handleStartParsing}
>
Import from {this.props.importType}
</Button>}
</div>
</div>
);
}
}

export default connectToStores(ImportContainer);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import HTML5Backend, { NativeTypes } from 'react-dnd-html5-backend';

import Icon from 'components/utilities/Icon';

class FileChooser extends Component {
class ImportFileChooser extends Component {
static propTypes = {
onFileChoosen: PropTypes.func,
connectDropTarget: PropTypes.func,
Expand Down Expand Up @@ -52,10 +52,10 @@ function itemTargetCollect(connect, monitor) {
};
}

const FileChooserTarget = new DropTarget(
const ImportFileChooserTarget = new DropTarget(
NativeTypes.FILE,
itemTarget,
itemTargetCollect
)(FileChooser);
)(ImportFileChooser);

export default new DragDropContext(HTML5Backend)(FileChooserTarget);
export default new DragDropContext(HTML5Backend)(ImportFileChooserTarget);
42 changes: 42 additions & 0 deletions src/components/import/ImportMandatoryField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import Input from 'components/utilities/Input';

class ImportMandatoryField extends Component {
static propTypes = {
field: PropTypes.instanceOf(Immutable.Map),
onChange: PropTypes.func,
};

constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange({ value }) {
const params = {
field: this.props.field,
value,
};

this.props.onChange(params);
}

render() {
return (
<Input
ref={ref => {
this.input = ref;
}}
label={this.props.field.get('name')}
name={this.props.field.get('name')}
value={this.props.field.get('value')}
onChange={this.handleChange}
type={this.props.field.get('type')}
/>
);
}
}

export default ImportMandatoryField;
Loading

0 comments on commit ba86040

Please sign in to comment.