mirror of
https://github.com/Kong/insomnia
synced 2024-11-12 17:26:32 +00:00
0a616fba6b
* VCS proof of concept underway! * Stuff * Some things * Replace deprecated Electron makeSingleInstance * Rename `window` variables so not to be confused with window object * Don't unnecessarily update request when URL does not change * Regenerate package-lock * Fix tests + ESLint * Publish - insomnia-app@1.0.49 - insomnia-cookies@0.0.12 - insomnia-httpsnippet@1.16.18 - insomnia-importers@2.0.13 - insomnia-libcurl@0.0.23 - insomnia-prettify@0.1.7 - insomnia-url@0.1.6 - insomnia-xpath@1.0.9 - insomnia-plugin-base64@1.0.6 - insomnia-plugin-cookie-jar@1.0.8 - insomnia-plugin-core-themes@1.0.5 - insomnia-plugin-default-headers@1.1.9 - insomnia-plugin-file@1.0.7 - insomnia-plugin-hash@1.0.7 - insomnia-plugin-jsonpath@1.0.12 - insomnia-plugin-now@1.0.11 - insomnia-plugin-os@1.0.13 - insomnia-plugin-prompt@1.1.9 - insomnia-plugin-request@1.0.18 - insomnia-plugin-response@1.0.16 - insomnia-plugin-uuid@1.0.10 * Broken but w/e * Some tweaks * Big refactor. Create local snapshots and push done * POC merging and a lot of improvements * Lots of work done on initial UI/UX * Fix old tests * Atomic writes and size-based batches * Update StageEntry definition once again to be better * Factor out GraphQL query logic * Merge algorithm, history modal, other minor things * Fix test * Merge, checkout, revert w/ user changes now work * Force UI to refresh when switching branches changes active request * Rough draft pull() and some cleanup * E2EE stuff and some refactoring * Add ability to share project with team and fixed tests * VCS now created in root component and better remote project handling * Remove unused definition * Publish - insomnia-account@0.0.2 - insomnia-app@1.1.1 - insomnia-cookies@0.0.14 - insomnia-httpsnippet@1.16.20 - insomnia-importers@2.0.15 - insomnia-libcurl@0.0.25 - insomnia-prettify@0.1.9 - insomnia-sync@0.0.2 - insomnia-url@0.1.8 - insomnia-xpath@1.0.11 - insomnia-plugin-base64@1.0.8 - insomnia-plugin-cookie-jar@1.0.10 - insomnia-plugin-core-themes@1.0.7 - insomnia-plugin-file@1.0.9 - insomnia-plugin-hash@1.0.9 - insomnia-plugin-jsonpath@1.0.14 - insomnia-plugin-now@1.0.13 - insomnia-plugin-os@1.0.15 - insomnia-plugin-prompt@1.1.11 - insomnia-plugin-request@1.0.20 - insomnia-plugin-response@1.0.18 - insomnia-plugin-uuid@1.0.12 * Move some deps around * Fix Flow errors * Update package.json * Fix eslint errors * Fix tests * Update deps * bootstrap insomnia-sync * TRy fixing appveyor * Try something else * Bump lerna * try powershell * Try again * Fix imports * Fixed errors * sync types refactor * Show remote projects in workspace dropdown * Improved pulling of non-local workspaces * Loading indicators and some tweaks * Clean up sync staging modal * Some sync improvements: - No longer store stage - Upgrade Electron - Sync UI/UX improvements * Fix snyc tests * Upgraded deps and hot loader tweaks (it's broken for some reason) * Fix tests * Branches dialog, network refactoring, some tweaks * Fixed merging when other branch is empty * A bunch of small fixes from real testing * Fixed pull merge logic * Fix tests * Some bug fixes * A few small tweaks * Conflict resolution and other improvements * Fix tests * Add revert changes * Deal with duplicate projects per workspace * Some tweaks and accessibility improvements * Tooltip accessibility * Fix API endpoint * Fix tests * Remove jest dep from insomnia-importers
498 lines
16 KiB
JavaScript
498 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 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,
|
|
isVariableUncovered: boolean,
|
|
handleRender: Function,
|
|
handleGetRenderContext: Function,
|
|
handleRemoveWorkspace: Function,
|
|
handleDuplicateWorkspace: Function,
|
|
handleClearAllResponses: 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 });
|
|
}
|
|
|
|
_handleSetModalRef(n: ?Modal) {
|
|
this.modal = n;
|
|
}
|
|
|
|
_handleRemoveWorkspace() {
|
|
this.props.handleRemoveWorkspace();
|
|
this.hide();
|
|
}
|
|
|
|
_handleClearAllResponses() {
|
|
this.props.handleClearAllResponses();
|
|
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();
|
|
}
|
|
|
|
static async _handleDeleteCertificate(certificate: ClientCertificate) {
|
|
await models.clientCertificate.remove(certificate);
|
|
}
|
|
|
|
static async _handleToggleCertificate(certificate: ClientCertificate) {
|
|
await models.clientCertificate.update(certificate, {
|
|
disabled: !certificate.disabled,
|
|
});
|
|
}
|
|
|
|
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={() => WorkspaceSettingsModal._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={() => WorkspaceSettingsModal._handleDeleteCertificate(certificate)}>
|
|
<i className="fa fa-trash-o" />
|
|
</PromptButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
renderModalBody() {
|
|
const {
|
|
clientCertificates,
|
|
workspace,
|
|
editorLineWrapping,
|
|
editorFontSize,
|
|
editorIndentSize,
|
|
editorKeyMap,
|
|
handleRender,
|
|
handleGetRenderContext,
|
|
nunjucksPowerUserMode,
|
|
isVariableUncovered,
|
|
} = 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 tabIndex="-1">
|
|
<button>Overview</button>
|
|
</Tab>
|
|
<Tab tabIndex="-1">
|
|
<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}
|
|
isVariableUncovered={isVariableUncovered}
|
|
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>
|
|
<PromptButton
|
|
onClick={this._handleClearAllResponses}
|
|
addIcon
|
|
className="width-auto btn btn--clicky inline-block space-left">
|
|
<i className="fa fa-trash-o" /> Clear All Responses
|
|
</PromptButton>
|
|
</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 />
|
|
Or
|
|
</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;
|