diff --git a/app/clients/aws.js b/app/clients/aws.js
index 905c3cd..08f3601 100644
--- a/app/clients/aws.js
+++ b/app/clients/aws.js
@@ -1,5 +1,7 @@
import AWS from 'aws-sdk';
-import localStore from '../store/localStore';
+import { Agent as httpsAgent } from 'https';
+import { readFileSync as fsReadFileSync } from 'fs';
+import localStore, { availableSettings } from '../store/localStore';
process.env.AWS_SDK_LOAD_CONFIG = true;
@@ -7,18 +9,49 @@ const credentialProvider = new AWS.CredentialProviderChain([
() => new AWS.EnvironmentCredentials('AWS'),
() => new AWS.EnvironmentCredentials('AMAZON'),
() =>
- new AWS.SharedIniFileCredentials({ profile: localStore.get('profile') }),
- () => new AWS.ProcessCredentials({ profile: localStore.get('profile') })
+ new AWS.SharedIniFileCredentials({
+ profile: localStore.get(availableSettings.profile)
+ }),
+ () =>
+ new AWS.ProcessCredentials({
+ profile: localStore.get(availableSettings.profile)
+ })
// TODO: Add more credential providers as needed. https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CredentialProviderChain.html#providers-property
]);
-const ssm = region =>
- new AWS.SSM({ region, credentials: null, credentialProvider });
-const kms = region =>
- new AWS.KMS({ region, credentials: null, credentialProvider });
+const caBundlePath = localStore.get(availableSettings.caBundlePath);
+
+const ssm = region => {
+ const awsConfig: AWS.SSM.ClientConfiguration = {
+ region,
+ credentials: null,
+ credentialProvider
+ };
+ if (caBundlePath) {
+ awsConfig.httpOptions = {
+ // eslint-disable-next-line new-cap
+ agent: new httpsAgent({ ca: fsReadFileSync(caBundlePath) })
+ };
+ }
+ return new AWS.SSM(awsConfig);
+};
+const kms = region => {
+ const awsConfig: AWS.KMS.ClientConfiguration = {
+ region,
+ credentials: null,
+ credentialProvider
+ };
+ if (caBundlePath) {
+ awsConfig.httpOptions = {
+ // eslint-disable-next-line new-cap
+ agent: new httpsAgent({ ca: fsReadFileSync(caBundlePath) })
+ };
+ }
+ return new AWS.KMS(awsConfig);
+};
-const getSSM = () => ssm(localStore.get('ssmRegion'));
-const getKMS = () => kms(localStore.get('kmsRegion'));
+const getSSM = () => ssm(localStore.get(availableSettings.ssmRegion));
+const getKMS = () => kms(localStore.get(availableSettings.kmsRegion));
export default {
getSSM,
diff --git a/app/components/CreationForm.js b/app/components/CreationForm.js
index d974eb0..4e016e1 100644
--- a/app/components/CreationForm.js
+++ b/app/components/CreationForm.js
@@ -14,11 +14,14 @@ import {
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
+import JSONInput from 'react-json-editor-ajrm';
+import locale from 'react-json-editor-ajrm/locale/en';
import { formShape, formDataShape } from './formDataShape.propType';
import {
actions as parameterActions,
selectors as parameterSelectors
} from '../ducks/parameters';
+import { valueIsJson } from '../utils/valueIsJson';
const { Option } = Select;
const { TextArea } = Input;
@@ -79,6 +82,8 @@ class CreationForm extends React.Component {
} = this.props;
const { validateFields } = form;
validateFields((validationErr, values) => {
+ const params = values;
+ params.value = values.value.json || values.value; // handle json editor
if (!validationErr) {
const { creationType } = this.state;
const creationFn =
@@ -86,7 +91,7 @@ class CreationForm extends React.Component {
? createServiceParameters
: createGenericParameter;
this.setState({ creationState: ENTITY_STATUS.loading });
- creationFn(values, !!editFlow)
+ creationFn(params, !!editFlow)
.then(res => {
notification.success({
message: editFlow
@@ -113,6 +118,21 @@ class CreationForm extends React.Component {
this.setState({ creationType: e.target.value });
};
+ validateValue = async (rule, value, callback) => {
+ if (!value || !value.plainText || !value.plainText.length === 0) {
+ return callback('Value is required');
+ }
+ if (value.error && value.error.reason) {
+ return callback(value.error.reason);
+ }
+ if (value.plainText.length > 4096) {
+ return callback(
+ 'The maximum allowed size of 4096 characters (assuming all chars are one byte).'
+ );
+ }
+ return callback();
+ };
+
render() {
const {
form,
@@ -130,6 +150,8 @@ class CreationForm extends React.Component {
wrapperCol: { span: 14 }
};
+ let useJsonInput = valueIsJson(initialFormData.value);
+
return (
{!editFlow && (
@@ -247,8 +269,8 @@ class CreationForm extends React.Component {
String
SecureString
-
- StringList (Not supported yet)
+
+ StringList (comma separated string, no spaces)
)}
@@ -285,19 +307,34 @@ class CreationForm extends React.Component {
)}
)}
-
- {getFieldDecorator('value', {
- initialValue: initialFormData.value,
- rules: [
- { required: true, message: 'Please provide the value.' },
- {
- max: 4096,
- message: 'The maximum allowed length is 4096 characters.'
- }
- ]
- })()}
-
-
+ {useJsonInput && (
+
+ {getFieldDecorator('value', {
+ initialValue: initialFormData.value,
+ rules: [{ validator: this.validateValue }]
+ })(
+
+ )}
+
+ )}
+ {!useJsonInput && (
+
+ {getFieldDecorator('value', {
+ initialValue: initialFormData.value,
+ rules: [
+ { required: true, message: 'Please provide the value.' },
+ {
+ max: 4096,
+ message: 'The maximum allowed length is 4096 characters.'
+ }
+ ]
+ })()}
+
+ )}
{!editFlow &&
creationType === 'service' &&
form.getFieldValue('serviceName') &&
diff --git a/app/components/Home.js b/app/components/Home.js
index 62e2fd2..e9bb0bb 100644
--- a/app/components/Home.js
+++ b/app/components/Home.js
@@ -11,7 +11,8 @@ import {
Typography
} from 'antd';
import PropTypes from 'prop-types';
-
+import JSONInput from 'react-json-editor-ajrm';
+import locale from 'react-json-editor-ajrm/locale/en';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import ReactTimeAgo from 'react-time-ago';
@@ -25,6 +26,7 @@ import CreationFormButton from './CreationFormButton';
import DeleteButton from './DeleteButton';
import localStore, { availableSettings } from '../store/localStore';
import SettingsButton from './SettingsButton';
+import { valueIsJson } from '../utils/valueIsJson';
const { TreeNode } = Tree;
const { Paragraph } = Typography;
@@ -121,6 +123,18 @@ class Home extends Component {
})
: parameters;
+ const descWidth = 250;
+ const typeWidth = 120;
+ const modifiedWidth = 175;
+ let valueWidth = 300;
+ valueWidth += localStore.get(availableSettings.hideDescription)
+ ? descWidth
+ : 0;
+ valueWidth += localStore.get(availableSettings.hideLastModifiedDate)
+ ? modifiedWidth
+ : 0;
+ valueWidth += localStore.get(availableSettings.hideType) ? typeWidth : 0;
+
const columns = [
{
title: 'Name',
@@ -164,24 +178,41 @@ class Home extends Component {
);
}
},
-
{
title: 'Value',
dataIndex: 'Value',
key: 'Value',
- width: 300,
+ width: valueWidth,
- render: value => (
-
- {value}
-
- )
- },
- {
+ render: value => {
+ let useJsonInput = valueIsJson(value);
+ if (useJsonInput) {
+ return (
+
+ );
+ }
+ return (
+
+ {value}
+
+ );
+ }
+ }
+ ];
+ if (!localStore.get(availableSettings.hideDescription)) {
+ columns.push({
title: 'Description',
dataIndex: 'Description',
key: 'Description',
- width: 250,
+ width: descWidth,
render: value => {
return value ? (
@@ -191,17 +222,24 @@ class Home extends Component {
No Description
);
}
- },
- {
+ });
+ }
+
+ if (!localStore.get(availableSettings.hideType)) {
+ columns.push({
title: 'Type',
dataIndex: 'Type',
key: 'Type',
- width: 120
- },
- {
+ width: typeWidth
+ });
+ }
+
+ if (!localStore.get(availableSettings.hideLastModifiedDate)) {
+ columns.push({
title: 'LastModifiedDate',
dataIndex: 'LastModifiedDate',
key: 'LastModifiedDate',
+ width: modifiedWidth,
sorter: (a, b) =>
new Date(a.LastModifiedDate) - new Date(b.LastModifiedDate),
render: date => (
@@ -209,42 +247,43 @@ class Home extends Component {
{} ({date.toLocaleString()})
)
- },
- {
- title: 'Actions',
- key: 'Actions',
- width: 100,
- fixed: 'right',
- render: e => {
- const currentData = {
- name: e.Name,
- description: e.Description,
- type: e.Type,
- value: e.Value,
- kmsKey: e.KeyId
- };
- const { deleteParameter } = this.props;
- return (
-
-
-
-
-
- );
- }
+ });
+ }
+
+ columns.push({
+ title: 'Actions',
+ key: 'Actions',
+ width: 125,
+ fixed: 'right',
+ render: e => {
+ const currentData = {
+ name: e.Name,
+ description: e.Description,
+ type: e.Type,
+ value: e.Value,
+ kmsKey: e.KeyId
+ };
+ const { deleteParameter } = this.props;
+ return (
+
+
+
+
+
+ );
}
- ];
+ });
return (
diff --git a/app/components/SettingsButton.js b/app/components/SettingsButton.js
index 6a23559..05ee911 100644
--- a/app/components/SettingsButton.js
+++ b/app/components/SettingsButton.js
@@ -1,6 +1,15 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { Button, Form, Icon, Input, message, Modal, Tooltip } from 'antd';
+import {
+ Button,
+ Checkbox,
+ Form,
+ Icon,
+ Input,
+ message,
+ Modal,
+ Tooltip
+} from 'antd';
import localStore, { availableSettings } from '../store/localStore';
import { formShape } from './formDataShape.propType';
@@ -100,6 +109,21 @@ class SettingsButton extends Component {
]
})()}
+
+ Path Filter
+
+
+
+
+ }
+ {...formItemLayout}
+ >
+ {getFieldDecorator(availableSettings.pathFilter, {
+ initialValue: localStore.get(availableSettings.pathFilter)
+ })()}
+
{getFieldDecorator(availableSettings.ssmRegion, {
initialValue: localStore.get(availableSettings.ssmRegion),
@@ -143,6 +167,49 @@ class SettingsButton extends Component {
]
})()}
+
+ AWS CA Bundle Path
+
+
+
+
+ }
+ {...formItemLayout}
+ >
+ {getFieldDecorator(availableSettings.caBundlePath, {
+ initialValue: localStore.get(availableSettings.caBundlePath),
+ rules: [
+ {
+ required: false,
+ message: 'Optional'
+ }
+ ]
+ })()}
+
+
+ {getFieldDecorator(availableSettings.hideDescription, {
+ valuePropName: 'checked',
+ initialValue: localStore.get(
+ availableSettings.hideDescription
+ )
+ })()}
+
+
+ {getFieldDecorator(availableSettings.hideLastModifiedDate, {
+ valuePropName: 'checked',
+ initialValue: localStore.get(
+ availableSettings.hideLastModifiedDate
+ )
+ })()}
+
+
+ {getFieldDecorator(availableSettings.hideType, {
+ valuePropName: 'checked',
+ initialValue: localStore.get(availableSettings.hideType)
+ })()}
+