insomnia/packages/insomnia-app/app/ui/components/modals/workspace-settings-modal.js
Gregory Schier 549ce23ce8
Merge All Repositories into Monorepo for easier maintenance (#629)
* All projects into monorepo

* Update CI

* More CI updates

* Extracted a bunch of things into packages

* Publish

 - insomnia-plugin-base64@1.0.1
 - insomnia-plugin-default-headers@1.0.2
 - insomnia-plugin-file@1.0.1
 - insomnia-plugin-hash@1.0.1
 - insomnia-plugin-now@1.0.1
 - insomnia-plugin-request@1.0.1
 - insomnia-plugin-response@1.0.1
 - insomnia-plugin-uuid@1.0.1
 - insomnia-cookies@0.0.2
 - insomnia-importers@1.5.2
 - insomnia-prettify@0.0.3
 - insomnia-url@0.0.2
 - insomnia-xpath@0.0.2

* A bunch of small fixes

* Improved build script

* Fixed

* Merge dangling files

* Usability refactor

* Handle duplicate plugin names
2017-11-26 20:45:40 +00:00

486 lines
16 KiB
JavaScript

// @flow
import * as React from 'react';
import autobind from 'autobind-decorator';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import DebouncedInput from '../base/debounced-input';
import FileInputButton from '../base/file-input-button';
import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import HelpTooltip from '../help-tooltip';
import PromptButton from '../base/prompt-button';
import * as models from '../../../models/index';
import {trackEvent} from '../../../common/analytics';
import MarkdownEditor from '../markdown-editor';
import type {Workspace} from '../../../models/workspace';
import type {ClientCertificate} from '../../../models/client-certificate';
type Props = {
clientCertificates: Array<ClientCertificate>,
workspace: Workspace,
editorFontSize: number,
editorIndentSize: number,
editorKeyMap: string,
editorLineWrapping: boolean,
nunjucksPowerUserMode: boolean,
handleRender: Function,
handleGetRenderContext: Function,
handleRemoveWorkspace: Function,
handleDuplicateWorkspace: Function
};
type State = {
showAddCertificateForm: boolean,
host: string,
crtPath: string,
keyPath: string,
pfxPath: string,
isPrivate: boolean,
passphrase: string,
showDescription: boolean,
defaultPreviewMode: boolean
};
@autobind
class WorkspaceSettingsModal extends React.PureComponent<Props, State> {
modal: Modal | null;
constructor (props: Props) {
super(props);
this.state = {
showAddCertificateForm: false,
host: '',
crtPath: '',
keyPath: '',
pfxPath: '',
passphrase: '',
isPrivate: false,
showDescription: false,
defaultPreviewMode: false
};
}
_workspaceUpdate (patch: Object) {
models.workspace.update(this.props.workspace, patch);
}
_handleAddDescription () {
this.setState({showDescription: true});
trackEvent('Workspace', 'Add Description');
}
_handleSetModalRef (n: ?Modal) {
this.modal = n;
}
_handleRemoveWorkspace () {
this.props.handleRemoveWorkspace();
this.hide();
}
_handleDuplicateWorkspace () {
this.props.handleDuplicateWorkspace(() => {
this.hide();
});
}
_handleToggleCertificateForm () {
this.setState(state => ({
showAddCertificateForm: !state.showAddCertificateForm,
crtPath: '',
keyPath: '',
pfxPath: '',
host: '',
passphrase: '',
isPrivate: false
}));
}
_handleRename (name: string) {
this._workspaceUpdate({name});
}
_handleDescriptionChange (description: string) {
this._workspaceUpdate({description});
if (this.state.defaultPreviewMode !== false) {
this.setState({defaultPreviewMode: false});
}
}
_handleCreateHostChange (e: SyntheticEvent<HTMLInputElement>) {
this.setState({host: e.currentTarget.value});
}
_handleCreatePfxChange (pfxPath: string) {
this.setState({pfxPath});
}
_handleCreateCrtChange (crtPath: string) {
this.setState({crtPath});
}
_handleCreateKeyChange (keyPath: string) {
this.setState({keyPath});
}
_handleCreatePassphraseChange (e: SyntheticEvent<HTMLInputElement>) {
this.setState({passphrase: e.currentTarget.value});
}
_handleCreateIsPrivateChange (e: SyntheticEvent<HTMLInputElement>) {
this.setState({isPrivate: e.currentTarget.checked});
}
async _handleCreateCertificate (e: SyntheticEvent<HTMLFormElement>) {
e.preventDefault();
const {workspace} = this.props;
const {pfxPath, crtPath, keyPath, host, passphrase, isPrivate} = this.state;
const certificate = {
host,
isPrivate,
parentId: workspace._id,
passphrase: passphrase || null,
disabled: false,
cert: crtPath || null,
key: keyPath || null,
pfx: pfxPath || null
};
await models.clientCertificate.create(certificate);
this._handleToggleCertificateForm();
trackEvent('Certificates', 'Create');
}
async _handleDeleteCertificate (certificate: ClientCertificate) {
await models.clientCertificate.remove(certificate);
trackEvent('Certificates', 'Delete');
}
async _handleToggleCertificate (certificate: ClientCertificate) {
await models.clientCertificate.update(certificate, {disabled: !certificate.disabled});
trackEvent('Certificates', 'Toggle');
}
show () {
const hasDescription = !!this.props.workspace.description;
this.setState({
showDescription: hasDescription,
defaultPreviewMode: hasDescription,
showAddCertificateForm: false
});
this.modal && this.modal.show();
}
hide () {
this.modal && this.modal.hide();
}
renderModalHeader () {
const {workspace} = this.props;
return (
<ModalHeader key={`header::${workspace._id}`}>
Workspace Settings
{' '}
<div className="txt-sm selectable faint monospace">
{workspace ? workspace._id : ''}
</div>
</ModalHeader>
);
}
renderCertificate (certificate: ClientCertificate) {
return (
<div key={certificate._id}>
<div className="row-spaced">
<div>
<span className="pad-right no-wrap">
<strong>PFX:</strong>
{' '}
{certificate.pfx
? <i className="fa fa-check"/>
: <i className="fa fa-remove"/>
}
</span>
<span className="pad-right no-wrap">
<strong>CRT:</strong>
{' '}
{certificate.cert
? <i className="fa fa-check"/>
: <i className="fa fa-remove"/>
}
</span>
<span className="pad-right no-wrap">
<strong>Key:</strong>
{' '}
{certificate.key
? <i className="fa fa-check"/>
: <i className="fa fa-remove"/>
}
</span>
<span className="pad-right no-wrap" title={certificate.passphrase || null}>
<strong>Passphrase:</strong>
{' '}
{certificate.passphrase
? <i className="fa fa-check"/>
: <i className="fa fa-remove"/>
}
</span>
<span className="pad-right">
<strong>Host:</strong>
{' '}
<span className="monospace selectable">{certificate.host}</span>
</span>
</div>
<div className="no-wrap">
<button className="btn btn--super-compact width-auto"
title="Enable or disable certificate"
onClick={() => this._handleToggleCertificate(certificate)}>
{certificate.disabled
? <i className="fa fa-square-o"/>
: <i className="fa fa-check-square-o"/>
}
</button>
<PromptButton className="btn btn--super-compact width-auto"
confirmMessage=" "
addIcon
onClick={() => this._handleDeleteCertificate(certificate)}>
<i className="fa fa-trash-o"/>
</PromptButton>
</div>
</div>
</div>
);
}
renderModalBody () {
const {
clientCertificates,
workspace,
editorLineWrapping,
editorFontSize,
editorIndentSize,
editorKeyMap,
handleRender,
handleGetRenderContext,
nunjucksPowerUserMode
} = this.props;
const publicCertificates = clientCertificates.filter(c => !c.isPrivate);
const privateCertificates = clientCertificates.filter(c => c.isPrivate);
const {
pfxPath,
crtPath,
keyPath,
isPrivate,
showAddCertificateForm,
showDescription,
defaultPreviewMode
} = this.state;
return (
<ModalBody key={`body::${workspace._id}`} noScroll>
<Tabs forceRenderTabPanel className="react-tabs">
<TabList>
<Tab>
<button>Overview</button>
</Tab>
<Tab>
<button>Client Certificates</button>
</Tab>
</TabList>
<TabPanel className="react-tabs__tab-panel pad scrollable pad-top-sm">
<div className="form-control form-control--outlined">
<label>Name
<DebouncedInput
type="text"
delay={500}
placeholder="Awesome API"
defaultValue={workspace.name}
onChange={this._handleRename}
/>
</label>
</div>
<div>
{showDescription ? (
<MarkdownEditor
className="margin-top"
defaultPreviewMode={defaultPreviewMode}
fontSize={editorFontSize}
indentSize={editorIndentSize}
keyMap={editorKeyMap}
placeholder="Write a description"
lineWrapping={editorLineWrapping}
handleRender={handleRender}
handleGetRenderContext={handleGetRenderContext}
nunjucksPowerUserMode={nunjucksPowerUserMode}
defaultValue={workspace.description}
onChange={this._handleDescriptionChange}
/>
) : (
<button onClick={this._handleAddDescription}
className="btn btn--outlined btn--super-duper-compact">
Add Description
</button>
)}
</div>
<h2>Workspace Actions</h2>
<div className="form-control form-control--padded">
<PromptButton onClick={this._handleRemoveWorkspace}
addIcon
className="width-auto btn btn--clicky inline-block">
<i className="fa fa-trash-o"/> Delete
</PromptButton>
<button onClick={this._handleDuplicateWorkspace}
className="width-auto btn btn--clicky inline-block space-left">
<i className="fa fa-copy"/> Duplicate
</button>
</div>
</TabPanel>
<TabPanel className="react-tabs__tab-panel pad scrollable">
{!showAddCertificateForm ? (
<div>
{clientCertificates.length === 0 ? (
<p className="notice surprise margin-top-sm">
You have not yet added any certificates
</p>
) : null}
{publicCertificates.length > 0
? publicCertificates.map(this.renderCertificate)
: null
}
{privateCertificates.length > 0 ? (
<div>
<h2>
Private Certificates
<HelpTooltip position="right" className="space-left">
Private certificates will not by synced.
</HelpTooltip>
</h2>
{privateCertificates.map(this.renderCertificate)}
</div>
) : null}
<hr className="hr--spaced"/>
<div className="text-center">
<button className="btn btn--clicky auto"
onClick={this._handleToggleCertificateForm}>
New Certificate
</button>
</div>
</div>
) : (
<form onSubmit={this._handleCreateCertificate}>
<div className="form-control form-control--outlined no-pad-top">
<label>Host
<HelpTooltip position="right" className="space-left">
The host for which this client certificate is valid.
Port number is optional and * can be used as a wildcard.
</HelpTooltip>
<input
type="text"
required="required"
placeholder="my-api.com"
autoFocus="autoFocus"
onChange={this._handleCreateHostChange}
/>
</label>
</div>
<div className="form-row">
<div className="form-control width-auto">
<label>PFX <span className="faint">(or PKCS12)</span>
<FileInputButton
className="btn btn--clicky"
onChange={this._handleCreatePfxChange}
path={pfxPath}
showFileName
/>
</label>
</div>
<div className="text-center">
<br/><br/>
&nbsp;&nbsp;Or&nbsp;&nbsp;
</div>
<div className="row-fill">
<div className="form-control">
<label>CRT File
<FileInputButton
className="btn btn--clicky"
name="Cert"
onChange={this._handleCreateCrtChange}
path={crtPath}
showFileName
/>
</label>
</div>
<div className="form-control">
<label>Key File
<FileInputButton
className="btn btn--clicky"
name="Key"
onChange={this._handleCreateKeyChange}
path={keyPath}
showFileName/>
</label>
</div>
</div>
</div>
<div className="form-control form-control--outlined">
<label>Passphrase
<input
type="password"
placeholder="•••••••••••"
onChange={this._handleCreatePassphraseChange}
/>
</label>
</div>
<div className="form-control form-control--slim">
<label>Private
<HelpTooltip className="space-left">
Private certificates will not be synced
</HelpTooltip>
<input
type="checkbox"
value={isPrivate}
onChange={this._handleCreateIsPrivateChange}
/>
</label>
</div>
<br/>
<div className="pad-top text-right">
<button type="button"
className="btn btn--super-compact space-right"
onClick={this._handleToggleCertificateForm}>
Cancel
</button>
<button className="btn btn--clicky space-right" type="submit">
Create Certificate
</button>
</div>
</form>
)}
</TabPanel>
</Tabs>
</ModalBody>
);
}
render () {
const {workspace} = this.props;
return (
<Modal ref={this._handleSetModalRef} freshState>
{workspace ? this.renderModalHeader() : null}
{workspace ? this.renderModalBody() : null}
</Modal>
);
}
}
export default WorkspaceSettingsModal;