You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/08/10 13:15:21 UTC

[couchdb-fauxton] branch master updated: (#947) Refactor CORS addon

This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new bc94e63  (#947) Refactor CORS addon
bc94e63 is described below

commit bc94e637fd8b50db6f1febbb7567451d41a41c26
Author: Antonio Maranhao <30...@users.noreply.github.com>
AuthorDate: Thu Aug 10 09:15:19 2017 -0400

    (#947) Refactor CORS addon
    
    * Moved components to individual files
    * Fixed components. All tests pass
    * Clean up old code
    * Remove use of 'var' and changed name of action functions
    * Use fetch instead of Backbone models
    * Remove Backbone related files and code cleanup
    * Jest tests updated
---
 app/addons/config/layout.js                  |   2 +-
 app/addons/config/routes.js                  |   2 -
 app/addons/cors/__tests__/actions.test.js    | 111 ++++-----
 app/addons/cors/__tests__/components.test.js | 156 +++++++-----
 app/addons/cors/__tests__/helpers.test.js    |  53 ++++
 app/addons/cors/__tests__/resources.test.js  |  74 ------
 app/addons/cors/__tests__/stores.test.js     | 100 --------
 app/addons/cors/actions.js                   | 234 ++++++------------
 app/addons/cors/actiontypes.js               |   7 +-
 app/addons/cors/api.js                       | 120 +++++++++
 app/addons/cors/base.js                      |   8 +-
 app/addons/cors/components.js                | 356 +--------------------------
 app/addons/cors/components/CORSContainer.js  |  46 ++++
 app/addons/cors/components/CORSScreen.js     | 150 +++++++++++
 app/addons/cors/components/OriginInput.js    |  60 +++++
 app/addons/cors/components/OriginRow.js      |  86 +++++++
 app/addons/cors/components/OriginTable.js    |  48 ++++
 app/addons/cors/components/Origins.js        |  45 ++++
 app/addons/cors/{base.js => helpers.js}      |  33 ++-
 app/addons/cors/reducers.js                  |  67 +++++
 app/addons/cors/resources.js                 | 110 ---------
 app/addons/cors/stores.js                    | 190 --------------
 22 files changed, 946 insertions(+), 1112 deletions(-)

diff --git a/app/addons/config/layout.js b/app/addons/config/layout.js
index bc8a504..1a75288 100644
--- a/app/addons/config/layout.js
+++ b/app/addons/config/layout.js
@@ -51,7 +51,7 @@ export const ConfigLayout = ({showCors, docURL, node, endpoint, crumbs}) => {
     }
   ];
   const selectedTab = showCors ? 'CORS' : 'Main config';
-  const content = showCors ? <CORSComponents.CORSController/> : <ConfigComponents.ConfigTableController node={node} />;
+  const content = showCors ? <CORSComponents.CORSContainer node={node} url={endpoint}/> : <ConfigComponents.ConfigTableController node={node} />;
   return (
     <div id="dashboard" className="with-sidebar">
       <ConfigHeader
diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js
index 370d126..4b924d4 100644
--- a/app/addons/config/routes.js
+++ b/app/addons/config/routes.js
@@ -13,7 +13,6 @@
 import React from 'react';
 import FauxtonAPI from "../../core/api";
 import Config from "./resources";
-import CORSActions from "../cors/actions";
 import ClusterActions from "../cluster/cluster.actions";
 import ConfigActions from "./actions";
 import Layout from './layout';
@@ -67,7 +66,6 @@ var ConfigPerNodeRouteObject = FauxtonAPI.RouteObject.extend({
   },
 
   configCorsForNode: function (node) {
-    CORSActions.fetchAndEditCors(node);
     return <Layout
       node={node}
       docURL={this.configs.documentation}
diff --git a/app/addons/cors/__tests__/actions.test.js b/app/addons/cors/__tests__/actions.test.js
index 124845d..10f7a41 100644
--- a/app/addons/cors/__tests__/actions.test.js
+++ b/app/addons/cors/__tests__/actions.test.js
@@ -11,7 +11,8 @@
 // the License.
 import utils from "../../../../test/mocha/testUtils";
 import FauxtonAPI from "../../../core/api";
-import Actions from "../actions";
+import * as Actions from "../actions";
+import * as CorsAPI from "../api";
 import sinon from "sinon";
 
 const assert = utils.assert;
@@ -21,103 +22,81 @@ describe('CORS actions', () => {
 
   describe('save', () => {
 
-    let localNode = 'node2@127.0.0.1';
+    const localNode = 'node2@127.0.0.1';
+    const baseURL = 'http://localhost:8000/#_config/couchdb@localhost/cors';
+    const dispatch = sinon.stub();
+    const spyUpdateEnableCorsToHttpd = sinon.stub(CorsAPI, 'updateEnableCorsToHttpd');
+    const spyUpdateCorsOrigins = sinon.stub(CorsAPI, 'updateCorsOrigins');
+    const spyUpdateCorsCredentials = sinon.stub(CorsAPI, 'updateCorsCredentials');
+    const spyUpdateCorsHeaders = sinon.stub(CorsAPI, 'updateCorsHeaders');
+    const spyUpdateCorsMethods = sinon.stub(CorsAPI, 'updateCorsMethods');
 
     afterEach(() => {
-      restore(Actions.saveCorsOrigins);
-
-      restore(FauxtonAPI.when);
+      restore(FauxtonAPI.Promise.all);
       restore(FauxtonAPI.addNotification);
-    });
-
-    it('should save cors enabled to httpd', () => {
-      var spy = sinon.spy(Actions, 'saveEnableCorsToHttpd');
-
-      Actions.saveCors({
-        enableCors: false,
-        node: localNode
-      });
 
-      assert.ok(spy.calledWith(false));
+      spyUpdateEnableCorsToHttpd.reset();
+      spyUpdateCorsOrigins.reset();
+      spyUpdateCorsCredentials.reset();
+      spyUpdateCorsHeaders.reset();
+      spyUpdateCorsMethods.reset();
     });
 
-    it('does not save cors origins if cors not enabled', () => {
-      var spy = sinon.spy(Actions, 'saveCorsOrigins');
-
-      Actions.saveCors({
-        enableCors: false,
-        origins: ['*'],
+    it('should save enable_cors to httpd', () => {
+      Actions.saveCors(baseURL, {
+        corsEnabled: false,
         node: localNode
-      });
+      })(dispatch);
 
-      assert.notOk(spy.calledOnce);
+      assert.ok(spyUpdateEnableCorsToHttpd.calledWith(baseURL, localNode, false));
     });
 
-    it('saves cors origins', () => {
-      var spy = sinon.spy(Actions, 'saveCorsOrigins');
-
-      Actions.saveCors({
-        enableCors: true,
+    it('does not save CORS origins if CORS is not enabled', () => {
+      Actions.saveCors(baseURL, {
+        corsEnabled: false,
         origins: ['*'],
         node: localNode
-      });
+      })(dispatch);
 
-      assert.ok(spy.calledWith('*'));
+      assert.notOk(spyUpdateCorsOrigins.called);
     });
 
-    it('saves cors allow credentials', () => {
-      var spy = sinon.spy(Actions, 'saveCorsCredentials');
-
-      Actions.saveCors({
-        enableCors: true,
-        origins: ['https://testdomain.com'],
-        node: localNode
-      });
-
-      assert.ok(spy.calledOnce);
-    });
-
-    it('saves cors headers', () => {
-      var spy = sinon.spy(Actions, 'saveCorsHeaders');
-
-      Actions.saveCors({
-        enableCors: true,
-        origins: ['https://testdomain.com'],
+    it('saves CORS origins', () => {
+      Actions.saveCors(baseURL, {
+        corsEnabled: true,
+        origins: ['*'],
         node: localNode
-      });
+      })(dispatch);
 
-      assert.ok(spy.calledOnce);
+      assert.ok(spyUpdateCorsOrigins.calledWith(baseURL, localNode, '*'));
     });
 
-    it('saves cors methods', () => {
-      var spy = sinon.spy(Actions, 'saveCorsMethods');
-
-      Actions.saveCors({
-        enableCors: true,
+    it('saves CORS credentials, headers and methods', () => {
+      Actions.saveCors(baseURL, {
+        corsEnabled: true,
         origins: ['https://testdomain.com'],
         node: localNode
-      });
-
-      assert.ok(spy.calledOnce);
+      })(dispatch);
 
+      assert.ok(spyUpdateCorsCredentials.calledOnce);
+      assert.ok(spyUpdateCorsHeaders.calledOnce);
+      assert.ok(spyUpdateCorsMethods.calledOnce);
     });
 
     it('shows notification on successful save', () => {
-      var stub = sinon.stub(FauxtonAPI, 'when');
-      var spy = sinon.spy(FauxtonAPI, 'addNotification');
-      var promise = FauxtonAPI.Deferred();
-      promise.resolve();
+      const stub = sinon.stub(FauxtonAPI.Promise, 'all');
+      const spyAddNotification = sinon.spy(FauxtonAPI, 'addNotification');
+      const promise = FauxtonAPI.Promise.resolve();
       stub.returns(promise);
 
-      Actions.saveCors({
+      return Actions.saveCors(baseURL, {
         enableCors: true,
         origins: ['https://testdomain.com'],
         node: localNode
+      })(dispatch).then(() => {
+        assert.ok(spyAddNotification.called);
       });
-
-      assert.ok(spy.calledOnce);
     });
-
   });
 
   describe('Sanitize origins', () => {
diff --git a/app/addons/cors/__tests__/components.test.js b/app/addons/cors/__tests__/components.test.js
index d61f7e9..d264ed9 100644
--- a/app/addons/cors/__tests__/components.test.js
+++ b/app/addons/cors/__tests__/components.test.js
@@ -10,10 +10,8 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import FauxtonAPI from "../../../core/api";
+import * as Helpers from "../helpers";
 import Views from "../components";
-import Actions from "../actions";
-import Resources from "../resources";
-import Stores from "../stores";
 import utils from "../../../../test/mocha/testUtils";
 import React from "react";
 import ReactDOM from "react-dom";
@@ -21,19 +19,11 @@ import sinon from "sinon";
 import { shallow, mount } from 'enzyme';
 
 FauxtonAPI.router = new FauxtonAPI.Router([]);
-const {assert, restore} = utils;
-const corsStore = Stores.corsStore;
+const { assert, restore } = utils;
 
 describe('CORS Components', () => {
 
-  describe('CorsController', () => {
-
-    beforeEach(() => {
-      corsStore._origins = ['http://hello.com'];
-      corsStore._node = 'node2@127.0.0.1';
-      corsStore._isEnabled = true;
-      corsStore._configChanged = true;
-    });
+  describe('CORSContainer tests', () => {
 
     afterEach(() => {
       restore(window.confirm);
@@ -43,7 +33,14 @@ describe('CORS Components', () => {
       const spy = sinon.stub(window, 'confirm');
       spy.returns(false);
 
-      const wrapper = shallow(<Views.CORSController />);
+      const wrapper = shallow(<Views.CORSScreen
+        corsEnabled={true}
+        isAllOrigins={false}
+        origins={['https://localhost']}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+      />);
+
       wrapper.find('.enable-disable .btn').simulate('click');
       assert.ok(spy.calledOnce);
     });
@@ -52,10 +49,13 @@ describe('CORS Components', () => {
       const spy = sinon.stub(window, 'confirm');
       spy.returns(false);
 
-      // Set selected origins to empty
-      corsStore._origins = [];
-
-      const wrapper = shallow(<Views.CORSController />);
+      const wrapper = shallow(<Views.CORSScreen
+        corsEnabled={true}
+        isAllOrigins={true}
+        origins={[]}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+      />);
       wrapper.find('.enable-disable .btn').simulate('click');
       assert.ok(spy.notCalled);
     });
@@ -64,8 +64,15 @@ describe('CORS Components', () => {
       const spy = sinon.stub(window, 'confirm');
       spy.returns(false);
 
-      const wrapper = mount(<Views.CORSController />);
-      wrapper.find('input').at(0).simulate('change', {target: {checked: true, value: 'all'}});
+      const wrapper = mount(<Views.CORSScreen
+        corsEnabled={true}
+        isAllOrigins={false}
+        origins={['http://localhost']}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+        fetchAndLoadCORSOptions={sinon.stub()}
+      />);
+      wrapper.find('input').at(0).simulate('change', { target: { checked: true, value: 'all' } });
       assert.ok(spy.calledOnce);
     });
 
@@ -73,23 +80,42 @@ describe('CORS Components', () => {
       const spy = sinon.stub(window, 'confirm');
       spy.returns(false);
 
-      // Set selected origins to empty
-      corsStore._origins = [];
-
-      const wrapper = mount(<Views.CORSController />);
-      wrapper.find('input').at(0).simulate('change', {target: {checked: true, value: 'all'}});
+      const wrapper = shallow(<Views.CORSScreen
+        corsEnabled={true}
+        isAllOrigins={false}
+        origins={[]}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+      />);
+      wrapper.find('input').at(0).simulate('change', { target: { checked: true, value: 'all' } });
       assert.notOk(spy.calledOnce);
     });
 
     it('shows loading bars', () => {
-      const wrapper = mount(<Views.CORSController />);
-      Actions.toggleLoadingBarsToEnabled(true);
+      const wrapper = mount(<Views.CORSScreen
+        isLoading={true}
+        corsEnabled={true}
+        isAllOrigins={false}
+        origins={[]}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+        fetchAndLoadCORSOptions={sinon.stub()}
+      />);
+
       assert.ok(wrapper.find('.loading-lines').exists());
     });
 
     it('hides loading bars', () => {
-      const wrapper = mount(<Views.CORSController />);
-      Actions.toggleLoadingBarsToEnabled(false);
+      const wrapper = mount(<Views.CORSScreen
+        isLoading={false}
+        corsEnabled={true}
+        isAllOrigins={false}
+        origins={[]}
+        saveCORS={sinon.stub()}
+        showDeleteDomainConfirmation={sinon.stub()}
+        fetchAndLoadCORSOptions={sinon.stub()}
+      />);
+
       assert.notOk(wrapper.find('.loading-lines').exists());
     });
   });
@@ -98,30 +124,29 @@ describe('CORS Components', () => {
     const newOrigin = 'http://new-site.com';
 
     it('calls validates each domain', () => {
-      const spyValidateCORSDomain = sinon.spy(Resources, 'validateCORSDomain');
-      const addOriginStub = sinon.stub();
-      const wrapper = shallow(<Views.OriginInput isVisible={true} addOrigin={addOriginStub}/>);
+      const spyValidateDomain = sinon.spy(Helpers, 'validateDomain');
+      const wrapper = shallow(<Views.OriginInput isVisible={true} addOrigin={sinon.stub()} />);
 
-      wrapper.find('input').simulate('change', {target: {value: newOrigin}});
-      wrapper.find('.btn').simulate('click', {preventDefault: sinon.stub()});
-      assert.ok(spyValidateCORSDomain.calledWith(newOrigin));
+      wrapper.find('input').simulate('change', { target: { value: newOrigin } });
+      wrapper.find('.btn').simulate('click', { preventDefault: sinon.stub() });
+      assert.ok(spyValidateDomain.called);
     });
 
     it('calls addOrigin on add click with valid domain', () => {
       const addOriginSpy = sinon.spy();
-      const wrapper = shallow(<Views.OriginInput isVisible={true} addOrigin={addOriginSpy}/>);
+      const wrapper = mount(<Views.OriginInput isVisible={true} addOrigin={addOriginSpy} />);
 
-      wrapper.find('input').simulate('change', {target: {value: newOrigin}});
-      wrapper.find('.btn').simulate('click', {preventDefault: sinon.stub()});
+      wrapper.find('input').simulate('change', { target: { value: newOrigin } });
+      wrapper.find('.btn').simulate('click', { preventDefault: sinon.stub() });
       assert.ok(addOriginSpy.calledWith(newOrigin));
     });
 
     it('shows notification if origin is not valid', () => {
       const spyAddNotification = sinon.spy(FauxtonAPI, 'addNotification');
-      const wrapper = shallow(<Views.OriginInput isVisible={true} addOrigin={sinon.stub()}/>);
+      const wrapper = shallow(<Views.OriginInput isVisible={true} addOrigin={sinon.stub()} />);
 
-      wrapper.find('input').simulate('change', {target: {value: 'badOrigin'}});
-      wrapper.find('.btn').simulate('click', {preventDefault: sinon.stub()});
+      wrapper.find('input').simulate('change', { target: { value: 'badOrigin' } });
+      wrapper.find('.btn').simulate('click', { preventDefault: sinon.stub() });
       assert.ok(spyAddNotification.calledOnce);
     });
   });
@@ -134,22 +159,23 @@ describe('CORS Components', () => {
     });
 
     it('calls changeOrigin() when you switch from "Select List of Origins" to "Allow All Origins"', () => {
-      const wrapper = shallow(<Views.Origins corsEnabled={true} isAllOrigins={false} originChange={spyChangeOrigin}/>);
+      const wrapper = shallow(<Views.Origins corsEnabled={true} isAllOrigins={false} originChange={spyChangeOrigin} />);
 
-      wrapper.find('input[value="all"]').simulate('change', {target: {checked: true, value: 'all'}});
+      wrapper.find('input[value="all"]').simulate('change', { target: { checked: true, value: 'all' } });
       assert.ok(spyChangeOrigin.calledWith(true));
     });
 
     it('calls changeOrigin() when you switch from "Allow All Origins" to "Select List of Origins"', () => {
-      const wrapper = shallow(<Views.Origins corsEnabled={true} isAllOrigins={true} originChange={spyChangeOrigin}/>);
+      const wrapper = shallow(<Views.Origins corsEnabled={true} isAllOrigins={true} originChange={spyChangeOrigin} />);
 
-      wrapper.find('input[value="selected"]').simulate('change', {target: {checked: true, value: 'selected'}});
+      wrapper.find('input[value="selected"]').simulate('change', { target: { checked: true, value: 'selected' } });
       assert.ok(spyChangeOrigin.calledWith(false));
     });
   });
 
   describe('OriginRow', () => {
     const spyUpdateOrigin = sinon.spy();
+    const spyDeleteOrigin = sinon.spy();
     let origin;
 
     beforeEach(() => {
@@ -158,21 +184,35 @@ describe('CORS Components', () => {
 
     afterEach(() => {
       spyUpdateOrigin.reset();
+      spyDeleteOrigin.reset();
     });
 
-    it('should show confirm modal on delete', () => {
-      const wrapper = mount(<Views.OriginTable updateOrigin={spyUpdateOrigin} isVisible={true} origins={[origin]}/>);
+    it('should call deleteOrigin on delete', () => {
+      const wrapper = mount(<Views.OriginTable
+        updateOrigin={spyUpdateOrigin}
+        deleteOrigin={spyDeleteOrigin}
+        isVisible={true}
+        origins={[origin]} />);
 
       wrapper.find('.fonticon-trash').simulate('click', { preventDefault: sinon.stub() });
-      assert.ok(corsStore.isDeleteDomainModalVisible());
+      assert.ok(spyDeleteOrigin.calledOnce);
     });
 
     it('does not throw error if origins is undefined', () => {
-      mount(<Views.OriginTable updateOrigin={spyUpdateOrigin} isVisible={true} origins={false}/>);
+      mount(<Views.OriginTable
+        updateOrigin={spyUpdateOrigin}
+        deleteOrigin={spyDeleteOrigin}
+        isVisible={true}
+        origins={undefined} />);
     });
 
     it('should change origin to input on edit click, then hide input on 2nd click', () => {
-      const wrapper = mount(<Views.OriginTable updateOrigin={spyUpdateOrigin} isVisible={true} origins={[origin]}/>);
+      const wrapper = mount(<Views.OriginTable
+        updateOrigin={spyUpdateOrigin}
+        deleteOrigin={spyDeleteOrigin}
+        isVisible={true}
+        origins={[origin]} />);
+
       // Text input appears after clicking Edit
       wrapper.find('.fonticon-pencil').simulate('click', { preventDefault: sinon.stub() });
       assert.ok(wrapper.find('input').exists());
@@ -183,28 +223,32 @@ describe('CORS Components', () => {
     });
 
     it('should update origin on update clicked', () => {
-      let updatedOrigin = 'https://updated-origin.com';
+      const updatedOrigin = 'https://updated-origin.com';
       const wrapper = mount(
         <Views.OriginTable
           updateOrigin={spyUpdateOrigin}
-          isVisible={true} origins={[origin]}/>
+          deleteOrigin={spyDeleteOrigin}
+          isVisible={true}
+          origins={[origin]} />
       );
 
       wrapper.find('.fonticon-pencil').simulate('click', { preventDefault: sinon.stub() });
-      wrapper.find('input').simulate('change', {target: {value: updatedOrigin}});
+      wrapper.find('input').simulate('change', { target: { value: updatedOrigin } });
       wrapper.find('.btn').at(0).simulate('click', { preventDefault: sinon.stub() });
       assert.ok(spyUpdateOrigin.calledWith(updatedOrigin));
     });
 
     it('should not update origin on update clicked with bad origin', () => {
-      let updatedOrigin = 'updated-origin';
+      const updatedOrigin = 'updated-origin';
       const wrapper = mount(
         <Views.OriginTable
           updateOrigin={spyUpdateOrigin}
-          isVisible={true} origins={[origin]}/>
+          deleteOrigin={spyDeleteOrigin}
+          isVisible={true}
+          origins={[origin]} />
       );
       wrapper.find('.fonticon-pencil').simulate('click', { preventDefault: sinon.stub() });
-      wrapper.find('input').simulate('change', {target: {value: updatedOrigin}});
+      wrapper.find('input').simulate('change', { target: { value: updatedOrigin } });
       wrapper.find('.btn').at(0).simulate('click', { preventDefault: sinon.stub() });
       assert.notOk(spyUpdateOrigin.calledWith(updatedOrigin));
     });
diff --git a/app/addons/cors/__tests__/helpers.test.js b/app/addons/cors/__tests__/helpers.test.js
new file mode 100644
index 0000000..d728f4d
--- /dev/null
+++ b/app/addons/cors/__tests__/helpers.test.js
@@ -0,0 +1,53 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import testUtils from "../../../../test/mocha/testUtils";
+import * as Helpers from "../helpers";
+const assert = testUtils.assert;
+
+describe('CORS helper functions', () => {
+
+  it('allows valid domains', () => {
+    const urls = [
+      'http://something.com',
+      'https://a.ca',
+      'https://something.com:8000',
+      'https://www.some-valid-domain.com:80',
+      'http://localhost',
+      'https://localhost',
+      'http://192.168.1.113',
+      'http://192.168.1.113:1337'
+    ];
+
+    urls.forEach((url) => {
+      assert.isTrue(Helpers.validateCORSDomain(url));
+    });
+  });
+
+  it('fails on non http/https domains', () => {
+    const urls = [
+      'whoahnellythisaintright',
+      'ftp://site.com',
+      'https://',
+      'http://'
+    ];
+    _.each(urls, function (url) {
+      assert.isFalse(Helpers.validateCORSDomain(url));
+    });
+  });
+
+  it('normalizes common cases, like accidentally added subfolders', () => {
+    assert.equal('https://foo.com', Helpers.normalizeUrls('https://foo.com/blerg'));
+    assert.equal('https://192.168.1.113', Helpers.normalizeUrls('https://192.168.1.113/blerg'));
+    assert.equal('https://foo.com:1337', Helpers.normalizeUrls('https://foo.com:1337/blerg'));
+    assert.equal('https://foo.com', Helpers.normalizeUrls('https://foo.com'));
+  });
+});
diff --git a/app/addons/cors/__tests__/resources.test.js b/app/addons/cors/__tests__/resources.test.js
deleted file mode 100644
index 322b168..0000000
--- a/app/addons/cors/__tests__/resources.test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import testUtils from "../../../../test/mocha/testUtils";
-import CORS from "../resources";
-const assert = testUtils.assert;
-
-describe('Cors Config Model', () => {
-  let cors;
-
-  beforeEach(() => {
-    cors = new CORS.Config(null, { node: 'node2@127.0.0.1' });
-  });
-
-  it('Splits up origins into array', () => {
-    const origins = ['http://hello.com', 'http://another.co.a'];
-    cors.set(cors.parse({ origins: origins.join(',') }));
-    assert.deepEqual(cors.get('origins'), origins);
-  });
-
-  it('returns empty array for undefined', () => {
-    const origins = { origins: undefined };
-    cors.set(cors.parse(origins));
-    assert.deepEqual(cors.get('origins'), []);
-  });
-
-  it('does not return an empty string (empty origin), when "specific origins" is set, but there are no domains on that list', () => {
-    const emptyOrigins = { origins: '' };
-    cors.set(cors.parse(emptyOrigins));
-    assert.deepEqual(cors.get('origins'), []);
-  });
-
-  it('allows valid domains', () => {
-    const urls = [
-      'http://something.com',
-      'https://a.ca',
-      'https://something.com:8000',
-      'https://www.some-valid-domain.com:80',
-      'http://localhost',
-      'https://localhost',
-      'http://192.168.1.113',
-      'http://192.168.1.113:1337'
-    ];
-    _.each(urls, function (url) {
-      assert.isTrue(CORS.validateCORSDomain(url));
-    });
-  });
-
-  it('fails on non http/https domains', () => {
-    const urls = [
-      'whoahnellythisaintright',
-      'ftp://site.com'
-    ];
-    _.each(urls, function (url) {
-      assert.isFalse(CORS.validateCORSDomain(url));
-    });
-  });
-
-  it('normalizes common cases, like accidentally added subfolders', () => {
-    assert.equal('https://foo.com', CORS.normalizeUrls('https://foo.com/blerg'));
-    assert.equal('https://192.168.1.113', CORS.normalizeUrls('https://192.168.1.113/blerg'));
-    assert.equal('https://foo.com:1337', CORS.normalizeUrls('https://foo.com:1337/blerg'));
-    assert.equal('https://foo.com', CORS.normalizeUrls('https://foo.com'));
-  });
-
-});
diff --git a/app/addons/cors/__tests__/stores.test.js b/app/addons/cors/__tests__/stores.test.js
deleted file mode 100644
index e9be117..0000000
--- a/app/addons/cors/__tests__/stores.test.js
+++ /dev/null
@@ -1,100 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import testUtils from "../../../../test/mocha/testUtils";
-import Stores from "../stores";
-const assert = testUtils.assert;
-const store = Stores.corsStore;
-
-describe('CORS store', () => {
-
-  describe('isAllOrigins', () => {
-
-    it('returns true for all origins', () => {
-      store._origins = ['*'];
-
-      assert.ok(store.isAllOrigins());
-    });
-
-    it('returns false for specific origins', () => {
-      store._origins = ['https://hello.com', 'http://another.com'];
-
-      assert.notOk(store.isAllOrigins());
-    });
-
-    it('returns false for empty array', () => {
-      store._origins = [];
-
-      assert.notOk(store.isAllOrigins());
-    });
-  });
-
-  describe('addOrigin', () => {
-
-    it('adds Origin to list', () => {
-      const origin = 'http://hello.com';
-      store._origins = [];
-      store.addOrigin(origin);
-
-      assert.ok(_.include(store.getOrigins(), origin));
-    });
-
-  });
-
-  describe('originChange', () => {
-
-    it('sets origins to * for true', () => {
-      store.originChange(true);
-
-      assert.deepEqual(store.getOrigins(), ['*']);
-    });
-
-    it('sets origins to [] for true', () => {
-      store.originChange(false);
-
-      assert.deepEqual(store.getOrigins(), []);
-    });
-
-  });
-
-  describe('deleteOrigin', () => {
-
-    it('removes origin', () => {
-      store._origins = ['http://first.com', 'http://hello.com', 'http://second.com'];
-      store.deleteOrigin('http://hello.com');
-
-      assert.deepEqual(store.getOrigins(), ['http://first.com', 'http://second.com']);
-
-    });
-
-  });
-
-  describe('update origin', () => {
-
-    it('removes old origin', () => {
-      store._origins = ['http://first.com', 'http://hello.com', 'http://second.com'];
-      store.updateOrigin('http://hello123.com', 'http://hello.com');
-
-      assert.notOk(_.include(store.getOrigins(), 'http://hello.com'));
-
-    });
-
-    it('adds new origin', () => {
-      store._origins = ['http://first.com', 'http://hello.com', 'http://second.com'];
-      store.updateOrigin('http://hello123.com', 'http://hello.com');
-
-      assert.ok(_.include(store.getOrigins(), 'http://hello123.com'));
-
-    });
-
-  });
-
-});
diff --git a/app/addons/cors/actions.js b/app/addons/cors/actions.js
index 039cb67..3ba2e08 100644
--- a/app/addons/cors/actions.js
+++ b/app/addons/cors/actions.js
@@ -9,186 +9,102 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+
 import FauxtonAPI from "../../core/api";
 import ActionTypes from "./actiontypes";
-import Resources from "./resources";
-
-export default {
-  fetchAndEditCors: function (node) {
-    var cors = new Resources.Config({node: node});
-    var httpd = new Resources.Httpd({node: node});
-
-    FauxtonAPI.when([cors.fetch(), httpd.fetch()]).then(function () {
-      this.editCors({
-        origins: cors.get('origins'),
-        isEnabled: httpd.corsEnabled(),
-        node: node
-      });
-    }.bind(this));
-  },
+import * as CorsAPI from "./api";
 
-  editCors: function (options) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.EDIT_CORS,
-      options: options
-    });
-  },
-
-  toggleEnableCors: function () {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.TOGGLE_ENABLE_CORS
-    });
-  },
-
-  addOrigin: function (origin) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_ADD_ORIGIN,
-      origin: origin
-    });
-  },
-
-  originChange: function (isAllOrigins) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_IS_ALL_ORIGINS,
-      isAllOrigins: isAllOrigins
-    });
-  },
-
-  deleteOrigin: function (origin) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_DELETE_ORIGIN,
-      origin: origin
-    });
-  },
+export const fetchAndLoadCORSOptions = (url, node) => (dispatch) => {
+  const fetchCors = CorsAPI.fetchCORSConfig(url);
+  const fetchHttp = CorsAPI.fetchHttpdConfig(url);
 
-  updateOrigin: function (updatedOrigin, originalOrigin) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_UPDATE_ORIGIN,
-      updatedOrigin: updatedOrigin,
-      originalOrigin: originalOrigin
-    });
-  },
-
-  methodChange: function (httpMethod) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_METHOD_CHANGE,
-      httpMethod: httpMethod
-    });
-  },
-
-  saveEnableCorsToHttpd: function (enableCors, node) {
-    var enableOption = new Resources.ConfigModel({
-      section: 'httpd',
-      attribute: 'enable_cors',
-      value: enableCors.toString(),
-      node: node
-    });
-
-    return enableOption.save();
-  },
-
-  saveCorsOrigins: function (origins, node) {
-    var allowOrigins = new Resources.ConfigModel({
-      section: 'cors',
-      attribute: 'origins',
-      value: origins,
+  FauxtonAPI.Promise.join(fetchCors, fetchHttp, (corsConfig, httpdConfig) => {
+    const loadOptions = loadCORSOptions({
+      origins: corsConfig.origins,
+      corsEnabled: httpdConfig.enable_cors === 'true',
       node: node
     });
-
-    return allowOrigins.save();
-  },
-
-  saveCorsCredentials: function (node) {
-    var allowCredentials = new Resources.ConfigModel({
-      section: 'cors',
-      attribute: 'credentials',
-      value: 'true',
-      node: node
+    dispatch(loadOptions);
+  }).catch((error) => {
+    FauxtonAPI.addNotification({
+      msg: 'Could not load CORS settings.  ' + errorReason(error),
+      type: 'error'
     });
+  });
+};
 
-    return allowCredentials.save();
-  },
+export const showLoadingBars = () => {
+  return {
+    type: ActionTypes.CORS_SET_IS_LOADING,
+    isLoading: true
+  };
+};
 
-  saveCorsHeaders: function (node) {
-    var corsHeaders = new Resources.ConfigModel({
-      section: 'cors',
-      attribute: 'headers',
-      value: 'accept, authorization, content-type, origin, referer',
-      node: node
-    });
+export const hideLoadingBars = () => {
+  return {
+    type: ActionTypes.CORS_SET_IS_LOADING,
+    isLoading: false
+  };
+};
 
-    return corsHeaders.save();
-  },
+export const loadCORSOptions = (options) => {
+  return {
+    type: ActionTypes.EDIT_CORS,
+    options: options,
+    isLoading: false
+  };
+};
 
-  saveCorsMethods: function (node) {
-    var corsMethods = new Resources.ConfigModel({
-      section: 'cors',
-      attribute: 'methods',
-      value: 'GET, PUT, POST, HEAD, DELETE',
-      node: node
-    });
+export const showDomainDeleteConfirmation = (domain) => {
+  return {
+    type: ActionTypes.CORS_SHOW_DELETE_DOMAIN_MODAL,
+    domainToDelete: domain
+  };
+};
 
-    return corsMethods.save();
-  },
+export const hideDomainDeleteConfirmation = () => {
+  return {
+    type: ActionTypes.CORS_HIDE_DELETE_DOMAIN_MODAL
+  };
+};
 
-  sanitizeOrigins: function (origins) {
-    if (_.isEmpty(origins)) {
-      return '';
-    }
+export const saveCors = (url, options) => (dispatch) => {
+  const promises = [];
 
-    return origins.join(',');
-  },
+  promises.push(CorsAPI.updateEnableCorsToHttpd(url, options.node, options.corsEnabled));
+  if (options.corsEnabled) {
+    promises.push(CorsAPI.updateCorsOrigins(url, options.node, sanitizeOrigins(options.origins)));
+    promises.push(CorsAPI.updateCorsCredentials(url, options.node));
+    promises.push(CorsAPI.updateCorsHeaders(url, options.node));
+    promises.push(CorsAPI.updateCorsMethods(url, options.node));
+  }
 
-  toggleLoadingBarsToEnabled: function (state) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_SET_IS_LOADING,
-      isLoading: state
+  return FauxtonAPI.Promise.all(promises).then(() => {
+    FauxtonAPI.addNotification({
+      msg: 'CORS settings updated.',
+      type: 'success',
+      clear: true
     });
-  },
-
-  saveCors: function (options) {
-    this.toggleLoadingBarsToEnabled(true);
-
-    var promises = [];
-    promises.push(this.saveEnableCorsToHttpd(options.enableCors, options.node));
-
-    if (options.enableCors) {
-      promises.push(this.saveCorsOrigins(this.sanitizeOrigins(options.origins), options.node));
-      promises.push(this.saveCorsCredentials(options.node));
-      promises.push(this.saveCorsHeaders(options.node));
-      promises.push(this.saveCorsMethods(options.node));
-    }
-
-    FauxtonAPI.when(promises).then(function () {
-      FauxtonAPI.addNotification({
-        msg: 'Cors settings updated.',
-        type: 'success',
-        clear: true
-      });
-
-      this.hideDeleteDomainModal(); // just in case it was already open
-      this.toggleLoadingBarsToEnabled(false);
-
-    }.bind(this), function () {
+    dispatch(loadCORSOptions(options));
+  }).catch((error) => {
       FauxtonAPI.addNotification({
-        msg: 'Error! Could not save your CORS settings. Please try again.',
+        msg: 'Error! Could not save your CORS settings. Please try again. ' + errorReason(error),
         type: 'error',
         clear: true
       });
-      this.toggleLoadingBarsToEnabled(false);
-    }.bind(this));
-  },
-
-  showDeleteDomainModal: function (domain) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.CORS_SHOW_DELETE_DOMAIN_MODAL,
-      options: {
-        domain: domain
-      }
+      dispatch(hideDomainDeleteConfirmation());
+      dispatch(hideLoadingBars());
     });
-  },
+};
 
-  hideDeleteDomainModal: function () {
-    FauxtonAPI.dispatch({ type: ActionTypes.CORS_HIDE_DELETE_DOMAIN_MODAL });
+const errorReason = (error) => {
+  return 'Reason: ' + ((error && error.message) || 'n/a');
+};
+
+export const sanitizeOrigins = (origins) => {
+  if (_.isEmpty(origins)) {
+    return '';
   }
+
+  return origins.join(',');
 };
diff --git a/app/addons/cors/actiontypes.js b/app/addons/cors/actiontypes.js
index 81ebd7f..63a58e4 100644
--- a/app/addons/cors/actiontypes.js
+++ b/app/addons/cors/actiontypes.js
@@ -9,14 +9,9 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+
 export default {
-  TOGGLE_ENABLE_CORS: 'TOGGLE_ENABLE_CORS',
   EDIT_CORS: 'EDIT_CORS',
-  CORS_ADD_ORIGIN: 'CORS_ADD_ORIGIN',
-  CORS_IS_ALL_ORIGINS: 'CORS_IS_ALL_ORIGINS',
-  CORS_DELETE_ORIGIN: 'CORS_DELETE_ORIGIN',
-  CORS_UPDATE_ORIGIN: 'CORS_UPDATE_ORIGIN',
-  CORS_METHOD_CHANGE: 'CORS_METHOD_CHANGE',
   CORS_SET_IS_LOADING: 'CORS_SET_IS_LOADING',
   CORS_SHOW_DELETE_DOMAIN_MODAL: 'CORS_SHOW_DELETE_DOMAIN_MODAL',
   CORS_HIDE_DELETE_DOMAIN_MODAL: 'CORS_HIDE_DELETE_DOMAIN_MODAL'
diff --git a/app/addons/cors/api.js b/app/addons/cors/api.js
new file mode 100644
index 0000000..82dda38
--- /dev/null
+++ b/app/addons/cors/api.js
@@ -0,0 +1,120 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import 'whatwg-fetch';
+
+export const fetchCORSConfig = (baseURL) => {
+  const configURL = baseURL + '/cors';
+  return fetch(configURL, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    credentials: 'include',
+    method: 'GET'
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error(json.reason);
+      }
+
+      const origins = !json.origins ? [] : json.origins.split(',');
+      return {
+        origins: origins,
+        methods: json.methods,
+        credentials: json.credentials,
+        headers: json.headers
+      };
+    });
+};
+
+export const fetchHttpdConfig = (baseURL) => {
+  const configURL = baseURL + '/httpd';
+  return fetch(configURL, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    credentials: 'include',
+    method: 'GET'
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error(json.reason);
+      }
+      return json;
+    });
+};
+
+export const updateEnableCorsToHttpd = (baseURL, node, enableCors) => {
+  if (!node) {
+    throw new Error('node not set');
+  }
+  const configURL = baseURL + '/httpd/enable_cors';
+  return fetch(configURL, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    credentials: 'include',
+    method: 'PUT',
+    body: JSON.stringify(enableCors.toString())
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error(json.reason);
+      }
+      return json;
+    });
+};
+
+export const updateCorsOrigins = (baseURL, node, origins) => {
+  return updateCorsProperty(baseURL, node, 'origins', origins);
+};
+
+export const updateCorsCredentials = (baseURL, node) => {
+  return updateCorsProperty(baseURL, node, 'credentials', 'true');
+};
+
+export const updateCorsHeaders = (baseURL, node) => {
+  return updateCorsProperty(baseURL, node, 'headers', 'accept, authorization, content-type, origin, referer');
+};
+
+export const updateCorsMethods = (baseURL, node) => {
+  return updateCorsProperty(baseURL, node, 'methods', 'GET, PUT, POST, HEAD, DELETE');
+};
+
+const updateCorsProperty = (baseURL, node, propName, propValue) => {
+  if (!node) {
+    throw new Error('node not set');
+  }
+  const configURL = baseURL + '/cors/' + propName;
+  return fetch(configURL, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    credentials: 'include',
+    method: 'PUT',
+    body: JSON.stringify(propValue)
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error(json.reason);
+      }
+      return json;
+    });
+};
diff --git a/app/addons/cors/base.js b/app/addons/cors/base.js
index 6f8d400..f9c475f 100644
--- a/app/addons/cors/base.js
+++ b/app/addons/cors/base.js
@@ -12,8 +12,14 @@
 
 import FauxtonAPI from "../../core/api";
 import "./assets/less/cors.less";
-var CORS = FauxtonAPI.addon();
+import reducers from "./reducers";
+
+const CORS = FauxtonAPI.addon();
 
 CORS.initialize = function () {};
 
+FauxtonAPI.addReducers({
+  cors: reducers
+});
+
 export default CORS;
diff --git a/app/addons/cors/components.js b/app/addons/cors/components.js
index a986df4..71d0bbc 100644
--- a/app/addons/cors/components.js
+++ b/app/addons/cors/components.js
@@ -10,358 +10,28 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import React from "react";
-import Stores from "./stores";
-import Resources from "./resources";
-import Actions from "./actions";
-import ReactComponents from "../components/react-components";
-import FauxtonComponents from "../fauxton/components";
-var LoadLines = ReactComponents.LoadLines;
-var ConfirmationModal = FauxtonComponents.ConfirmationModal;
+import CORSContainer from "./components/CORSContainer";
+import CORSScreen from "./components/CORSScreen";
+import OriginInput from "./components/OriginInput";
+import Origins from "./components/Origins";
+import OriginTable from "./components/OriginTable";
+import OriginRow from "./components/OriginRow";
 
-var corsStore = Stores.corsStore;
-
-
-var validateOrigin = function (origin) {
-  if (!Resources.validateCORSDomain(origin)) {
-    FauxtonAPI.addNotification({
-      msg: 'Please enter a valid domain, starting with http/https.',
-      type: 'error',
-      clear: true
-    });
-
-    return false;
-  }
-
-  return true;
+export default {
+  CORSContainer,
+  CORSScreen,
+  OriginInput,
+  Origins,
+  OriginTable,
+  OriginRow
 };
 
 
-var OriginRow = React.createClass({
-
-  getInitialState: function () {
-    return {
-      edit: false,
-      updatedOrigin: this.props.origin
-    };
-  },
-
-  editOrigin: function (e) {
-    e.preventDefault();
-    this.setState({ edit: !this.state.edit });
-  },
-
-  updateOrigin: function (e) {
-    e.preventDefault();
-    if (!validateOrigin(this.state.updatedOrigin)) {
-      return;
-    }
-    this.props.updateOrigin(this.state.updatedOrigin, this.props.origin);
-    this.setState({ edit: false });
-  },
-
-  deleteOrigin: function (e) {
-    e.preventDefault();
-    Actions.showDeleteDomainModal(this.props.origin);
-  },
-
-  onInputChange: function (event) {
-    this.setState({ updatedOrigin: event.target.value });
-  },
-
-  onKeyUp: function (e) {
-    if (e.keyCode === 13) {   //enter key
-      return this.updateOrigin(e);
-    }
-  },
-
-  createOriginDisplay: function () {
-    if (this.state.edit) {
-      return (
-        <div className="input-append edit-domain-section">
-          <input type="text" name="update_origin_domain" onChange={this.onInputChange} onKeyUp={this.onKeyUp} value={this.state.updatedOrigin} />
-          <button onClick={this.updateOrigin} className="btn btn-primary update-origin"> Update </button>
-        </div>
-      );
-    }
-    return <div className="js-url url-display">{this.props.origin}</div>;
-  },
-
-  render: function () {
-    var display = this.createOriginDisplay();
-    return (
-      <tr>
-        <td>
-          {display}
-        </td>
-        <td width="30">
-          <span>
-            <a className="fonticon-pencil" onClick={this.editOrigin} title="Edit domain." />
-          </span>
-        </td>
-        <td width="30">
-          <span>
-            <a href="#" data-bypass="true" className="fonticon-trash" onClick={this.deleteOrigin} title="Delete domain." />
-          </span>
-        </td>
-      </tr>
-    );
-  }
-
-});
-
-var OriginTable = React.createClass({
-
-  createRows: function () {
-    return _.map(this.props.origins, function (origin, i) {
-      return <OriginRow
-        updateOrigin={this.props.updateOrigin}
-        key={i} origin={origin} />;
-    }, this);
-  },
-
-  render: function () {
-    if (!this.props.origins) {
-      return null;
-    }
-
-    if (!this.props.isVisible || this.props.origins.length === 0) {
-      return null;
-    }
-
-    var origins = this.createRows();
-
-    return (
-      <table id="origin-domain-table" className="table table-striped">
-        <tbody>
-          {origins}
-        </tbody>
-      </table>
-    );
-  }
-
-});
-
-var OriginInput = React.createClass({
-  getInitialState: function () {
-    return {
-      origin: ''
-    };
-  },
-
-  onInputChange: function (e) {
-    this.setState({origin: e.target.value});
-  },
-
-  addOrigin: function (event) {
-    event.preventDefault();
-    if (!validateOrigin(this.state.origin)) {
-      return;
-    }
-
-    var url = Resources.normalizeUrls(this.state.origin);
-
-    this.props.addOrigin(url);
-    this.setState({origin: ''});
-  },
-
-  onKeyUp: function (e) {
-    if (e.keyCode == 13) {   //enter key
-      return this.addOrigin(e);
-    }
-  },
 
-  render: function () {
-    if (!this.props.isVisible) {
-      return null;
-    }
 
-    return (
-      <div id="origin-domains-container">
-        <div className="origin-domains">
-          <div className="input-append">
-            <input type="text" name="new_origin_domain" onChange={this.onInputChange} onKeyUp={this.onKeyUp} value={this.state.origin} placeholder="https://example.com"/>
-            <button onClick={this.addOrigin} className="btn btn-secondary add-domain"><i className="icon fonticon-ok-circled"></i> Add Domain</button>
-          </div>
-        </div>
-      </div>
-    );
-  }
 
-});
 
-var Origins = React.createClass({
 
-  onOriginChange: function (event) {
-    if (event.target.value === 'all' && this.props.isAllOrigins) {
-      return;   // do nothing if all origins is already selected
-    }
-    if (event.target.value === 'selected' && !this.props.isAllOrigins) {
-      return;   // do nothing if specific origins is already selected
-    }
 
-    this.props.originChange(event.target.value === 'all');
-  },
 
-  render: function () {
 
-    if (!this.props.corsEnabled) {
-      return null;
-    }
-
-    return (
-      <div>
-        <p><strong> Origin Domains </strong> </p>
-        <p>Databases will accept requests from these domains: </p>
-        <label className="radio">
-          <input type="radio" checked={this.props.isAllOrigins} value="all" onChange={this.onOriginChange} name="all-domains"/> All domains ( * )
-        </label>
-        <label className="radio">
-          <input type="radio" checked={!this.props.isAllOrigins} value="selected" onChange={this.onOriginChange} name="selected-domains"/> Restrict to specific domains
-        </label>
-      </div>
-    );
-  }
-});
-
-var CORSController = React.createClass({
-
-  getStoreState: function () {
-    return {
-      corsEnabled: corsStore.isEnabled(),
-      origins: corsStore.getOrigins(),
-      isAllOrigins: corsStore.isAllOrigins(),
-      configChanged: corsStore.hasConfigChanged(),
-      shouldSaveChange: corsStore.shouldSaveChange(),
-      node: corsStore.getNode(),
-      isLoading: corsStore.getIsLoading(),
-      deleteDomainModalVisible: corsStore.isDeleteDomainModalVisible(),
-      domainToDelete: corsStore.getDomainToDelete()
-    };
-  },
-
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  componentDidMount: function () {
-    corsStore.on('change', this.onChange, this);
-  },
-
-  componentWillUnmount: function () {
-    corsStore.off('change', this.onChange);
-  },
-
-  componentDidUpdate: function () {
-    if (this.state.shouldSaveChange) {
-      this.save();
-    }
-  },
-
-  onChange: function () {
-    this.setState(this.getStoreState());
-  },
-
-  enableCorsChange: function () {
-    if (this.state.corsEnabled && !_.isEmpty(this.state.origins)) {
-      var result = window.confirm(app.i18n.en_US['cors-disable-cors-prompt']);
-      if (!result) { return; }
-    }
-
-    Actions.toggleEnableCors();
-  },
-
-  save: function () {
-    Actions.saveCors({
-      enableCors: this.state.corsEnabled,
-      origins: this.state.origins,
-      node: this.state.node
-    });
-  },
-
-  originChange: function (isAllOrigins) {
-    if (isAllOrigins && !_.isEmpty(this.state.origins)) {
-      var result = window.confirm('Are you sure? Switching to all origin domains will overwrite your specific origin domains.');
-      if (!result) { return; }
-    }
-
-    Actions.originChange(isAllOrigins);
-  },
-
-  addOrigin: function (origin) {
-    Actions.addOrigin(origin);
-  },
-
-  updateOrigin: function (updatedOrigin, originalOrigin) {
-    Actions.updateOrigin(updatedOrigin, originalOrigin);
-  },
-
-  methodChange: function (httpMethod) {
-    Actions.methodChange(httpMethod);
-  },
-
-  deleteOrigin: function () {
-    Actions.deleteOrigin(this.state.domainToDelete);
-  },
-
-  render: function () {
-    var isVisible = _.all([this.state.corsEnabled, !this.state.isAllOrigins]);
-
-    var originSettings = (
-      <div id={this.state.corsEnabled ? 'collapsing-container' : ''}>
-        <Origins corsEnabled={this.state.corsEnabled} originChange={this.originChange} isAllOrigins={this.state.isAllOrigins}/>
-        <OriginTable updateOrigin={this.updateOrigin} isVisible={isVisible} origins={this.state.origins} />
-        <OriginInput addOrigin={this.addOrigin} isVisible={isVisible} />
-      </div>
-    );
-
-    if (this.state.isLoading) {
-      originSettings = (<LoadLines />);
-    }
-    var deleteMsg = <span>Are you sure you want to delete <code>{_.escape(this.state.domainToDelete)}</code>?</span>;
-
-    return (
-      <div className="cors-page flex-body">
-        <header id="cors-header">
-          <p>{app.i18n.en_US['cors-notice']}</p>
-        </header>
-
-        <form id="corsForm" onSubmit={this.save}>
-          <div className="cors-enable">
-            {this.state.corsEnabled ? 'Cors is currently enabled.' : 'Cors is currently disabled.'}
-            <br />
-            <button
-              type="button"
-              className="enable-disable btn btn-secondary"
-              onClick={this.enableCorsChange}
-              disabled={this.state.isLoading ? 'disabled' : null}
-            >
-              {this.state.corsEnabled ? 'Disable CORS' : 'Enable CORS'}
-            </button>
-          </div>
-          {originSettings}
-        </form>
-
-        <ConfirmationModal
-          title="Confirm Deletion"
-          visible={this.state.deleteDomainModalVisible}
-          text={deleteMsg}
-          buttonClass="btn-danger"
-          onClose={Actions.hideDeleteDomainModal}
-          onSubmit={this.deleteOrigin}
-          successButtonLabel="Delete Domain" />
-      </div>
-    );
-  }
-});
-
-
-export default {
-  CORSController: CORSController,
-  OriginInput: OriginInput,
-  Origins: Origins,
-  OriginTable: OriginTable,
-  OriginRow: OriginRow
-};
diff --git a/app/addons/cors/components/CORSContainer.js b/app/addons/cors/components/CORSContainer.js
new file mode 100644
index 0000000..eece5ee
--- /dev/null
+++ b/app/addons/cors/components/CORSContainer.js
@@ -0,0 +1,46 @@
+import { connect } from 'react-redux';
+import CORSScreen from './CORSScreen';
+import {
+  saveCors, showLoadingBars, fetchAndLoadCORSOptions,
+  showDomainDeleteConfirmation, hideDomainDeleteConfirmation
+} from '../actions';
+
+const mapStateToProps = ({ cors }) => {
+  return {
+    node: cors.node,
+    corsEnabled: cors.corsEnabled,
+    isAllOrigins: cors.isAllOrigins,
+    isLoading: cors.isLoading,
+    origins: cors.origins,
+    deleteDomainModalVisible: cors.deleteDomainModalVisible,
+    domainToDelete: cors.domainToDelete
+  };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+  return {
+    saveCORS: (options) => {
+      dispatch(showLoadingBars());
+      dispatch(saveCors(ownProps.url, options));
+    },
+
+    fetchAndLoadCORSOptions: () => {
+      dispatch(fetchAndLoadCORSOptions(ownProps.url, ownProps.node));
+    },
+
+    showDeleteDomainConfirmation: (domain) => {
+      dispatch(showDomainDeleteConfirmation(domain));
+    },
+
+    hideDeleteDomainConfirmation: () => {
+      dispatch(hideDomainDeleteConfirmation());
+    }
+  };
+};
+
+const CORSContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(CORSScreen);
+
+export default CORSContainer;
diff --git a/app/addons/cors/components/CORSScreen.js b/app/addons/cors/components/CORSScreen.js
new file mode 100644
index 0000000..e5d76b8
--- /dev/null
+++ b/app/addons/cors/components/CORSScreen.js
@@ -0,0 +1,150 @@
+import React, { Component } from "react";
+import app from "../../../app";
+import ReactComponents from "../../components/react-components";
+import FauxtonComponents from "../../fauxton/components";
+import Origins from "./Origins";
+import OriginInput from "./OriginInput";
+import OriginTable from "./OriginTable";
+
+const LoadLines = ReactComponents.LoadLines;
+const ConfirmationModal = FauxtonComponents.ConfirmationModal;
+
+export default class CORSScreen extends Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    this.props.fetchAndLoadCORSOptions();
+  }
+
+  enableCorsChange() {
+    const { corsEnabled, origins, node } = this.props;
+    if (corsEnabled && !_.isEmpty(origins)) {
+      const result = window.confirm(app.i18n.en_US['cors-disable-cors-prompt']);
+      if (!result) { return; }
+    }
+    this.props.saveCORS({
+      corsEnabled: !corsEnabled,
+      origins: origins,
+      node: node
+    });
+  }
+
+  save() {
+    this.props.saveCORS({
+      corsEnabled: this.props.corsEnabled,
+      origins: this.props.origins,
+      node: this.props.node
+    });
+  }
+
+  originChange(isAllOrigins) {
+    if (isAllOrigins && !_.isEmpty(this.props.origins)) {
+      const result = window.confirm('Are you sure? Switching to all origin domains will overwrite your specific origin domains.');
+      if (!result) { return; }
+    }
+    this.props.saveCORS({
+      corsEnabled: this.props.corsEnabled,
+      origins: isAllOrigins ? ['*'] : [],
+      node: this.props.node
+    });
+  }
+
+  addOrigin(origin) {
+    this.props.saveCORS({
+      corsEnabled: this.props.corsEnabled,
+      origins: this.props.origins.concat(origin),
+      node: this.props.node
+    });
+  }
+
+  updateOrigin(updatedOrigin, originalOrigin) {
+    const newOrigins = this.props.origins.slice();
+    const index = _.indexOf(newOrigins, originalOrigin);
+    if (index === -1) { return; }
+    newOrigins[index] = updatedOrigin;
+
+    this.props.saveCORS({
+      corsEnabled: this.props.corsEnabled,
+      origins: newOrigins,
+      node: this.props.node
+    });
+  }
+
+  deleteOrigin() {
+    const { corsEnabled, origins, node, domainToDelete } = this.props;
+    const index = this.props.origins.indexOf(domainToDelete);
+    if (index === -1) { return; }
+    const newOrigins = [
+      ...origins.slice(0, index),
+      ...origins.slice(index + 1)
+    ];
+
+    this.props.saveCORS({
+      corsEnabled: corsEnabled,
+      origins: newOrigins,
+      node: node
+    });
+  }
+
+  render() {
+    const isVisible = [this.props.corsEnabled, !this.props.isAllOrigins].every((elem) => elem === true);
+
+    let originSettings = (
+      <div id={this.props.corsEnabled ? 'collapsing-container' : ''}>
+        <Origins
+          corsEnabled={this.props.corsEnabled}
+          originChange={this.originChange.bind(this)}
+          isAllOrigins={this.props.isAllOrigins} />
+        <OriginTable
+          updateOrigin={this.updateOrigin.bind(this)}
+          deleteOrigin={this.props.showDeleteDomainConfirmation}
+          isVisible={isVisible}
+          origins={this.props.origins} />
+        <OriginInput
+          addOrigin={this.addOrigin.bind(this)}
+          isVisible={isVisible} />
+      </div>
+    );
+
+    if (this.props.isLoading) {
+      originSettings = (<LoadLines />);
+    }
+    const deleteMsg = <span>Are you sure you want to delete <code>{_.escape(this.props.domainToDelete)}</code>?</span>;
+
+    return (
+      <div className="cors-page flex-body">
+        <header id="cors-header">
+          <p>{app.i18n.en_US['cors-notice']}</p>
+        </header>
+
+        <form id="corsForm" onSubmit={this.save.bind(this)}>
+          <div className="cors-enable">
+            {this.props.corsEnabled ? 'CORS is currently enabled.' : 'CORS is currently disabled.'}
+            <br />
+            <button
+              type="button"
+              className="enable-disable btn btn-secondary"
+              onClick={this.enableCorsChange.bind(this)}
+              disabled={this.props.isLoading ? 'disabled' : null}
+            >
+              {this.props.corsEnabled ? 'Disable CORS' : 'Enable CORS'}
+            </button>
+          </div>
+          {originSettings}
+        </form>
+
+        <ConfirmationModal
+          title="Confirm Deletion"
+          visible={this.props.deleteDomainModalVisible}
+          text={deleteMsg}
+          buttonClass="btn-danger"
+          onClose={this.props.hideDeleteDomainConfirmation}
+          onSubmit={this.deleteOrigin.bind(this)}
+          successButtonLabel="Delete Domain" />
+      </div>
+    );
+  }
+}
diff --git a/app/addons/cors/components/OriginInput.js b/app/addons/cors/components/OriginInput.js
new file mode 100644
index 0000000..bcd8720
--- /dev/null
+++ b/app/addons/cors/components/OriginInput.js
@@ -0,0 +1,60 @@
+import React, { Component } from "react";
+import { validateDomain, normalizeUrls } from "../helpers";
+
+export default class OriginInput extends Component {
+
+  constructor(props) {
+    super(props);
+    this.state = {
+      origin: ''
+    };
+  }
+
+  onInputChange(e) {
+    this.setState({ origin: e.target.value });
+  }
+
+  addOrigin(event) {
+    event.preventDefault();
+    if (!validateDomain(this.state.origin)) {
+      return false;
+    }
+
+    const url = normalizeUrls(this.state.origin);
+
+    this.props.addOrigin(url);
+    this.setState({ origin: '' });
+  }
+
+  onKeyUp(e) {
+    if (e.keyCode == 13) {   //enter key
+      return this.addOrigin(e);
+    }
+  }
+
+  render() {
+    if (!this.props.isVisible) {
+      return null;
+    }
+
+    return (
+      <div id="origin-domains-container">
+        <div className="origin-domains">
+          <div className="input-append">
+            <input type="text" name="new_origin_domain" placeholder="https://example.com"
+              onChange={this.onInputChange.bind(this)} value={this.state.origin} />
+            <button onClick={this.addOrigin.bind(this)} className="btn btn-secondary add-domain">
+              <i className="icon fonticon-ok-circled"></i> Add Domain
+            </button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+};
+
+OriginInput.propTypes = {
+  isVisible: React.PropTypes.bool.isRequired,
+  addOrigin: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/cors/components/OriginRow.js b/app/addons/cors/components/OriginRow.js
new file mode 100644
index 0000000..cbc73eb
--- /dev/null
+++ b/app/addons/cors/components/OriginRow.js
@@ -0,0 +1,86 @@
+import React, { Component } from "react";
+import { validateDomain } from "../helpers";
+
+
+export default class OriginRow extends Component {
+
+  constructor (props) {
+    super(props);
+    this.state = {
+      edit: false,
+      updatedOrigin: this.props.origin
+    };
+  }
+
+  editOrigin(e) {
+    e.preventDefault();
+    this.setState({ edit: !this.state.edit });
+  }
+
+  updateOrigin (e) {
+    e.preventDefault();
+    if (!validateDomain(this.state.updatedOrigin)) {
+      return;
+    }
+
+    if (this.state.updatedOrigin && this.props.origin !== this.state.updatedOrigin) {
+      this.props.updateOrigin(this.state.updatedOrigin, this.props.origin);
+    }
+    this.setState({ edit: false });
+  }
+
+  deleteOrigin (e) {
+    e.preventDefault();
+    this.props.deleteOrigin(this.props.origin);
+  }
+
+  onInputChange (event) {
+    this.setState({ updatedOrigin: event.target.value });
+  }
+
+  onKeyUp (e) {
+    if (e.keyCode === 13) {   //enter key
+      return this.updateOrigin(e);
+    }
+  }
+
+  createOriginDisplay () {
+    if (this.state.edit) {
+      return (
+        <div className="input-append edit-domain-section">
+          <input type="text" name="update_origin_domain" onChange={ this.onInputChange.bind(this) } onKeyUp={ this.onKeyUp.bind(this) } value={this.state.updatedOrigin} />
+          <button onClick={ this.updateOrigin.bind(this) } className="btn btn-primary update-origin"> Update </button>
+        </div>
+      );
+    }
+    return <div className="js-url url-display">{this.props.origin}</div>;
+  }
+
+  render () {
+    const display = this.createOriginDisplay();
+    return (
+      <tr>
+        <td>
+          {display}
+        </td>
+        <td width="30">
+          <span>
+            <a className="fonticon-pencil" onClick={ this.editOrigin.bind(this) } title="Edit domain." />
+          </span>
+        </td>
+        <td width="30">
+          <span>
+            <a href="#" data-bypass="true" className="fonticon-trash" onClick={ this.deleteOrigin.bind(this) } title="Delete domain." />
+          </span>
+        </td>
+      </tr>
+    );
+  }
+
+};
+
+OriginRow.propTypes = {
+  origin: React.PropTypes.string.isRequired,
+  updateOrigin: React.PropTypes.func.isRequired,
+  deleteOrigin: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/cors/components/OriginTable.js b/app/addons/cors/components/OriginTable.js
new file mode 100644
index 0000000..41e14fa
--- /dev/null
+++ b/app/addons/cors/components/OriginTable.js
@@ -0,0 +1,48 @@
+import React, { Component } from "react";
+import OriginRow from "./OriginRow";
+
+export default class OriginTable extends Component {
+
+  constructor (props) {
+    super(props);
+  }
+
+  createRows () {
+    return this.props.origins.map((origin, i) => {
+      return <OriginRow
+        updateOrigin={this.props.updateOrigin}
+        deleteOrigin={this.props.deleteOrigin}
+        key={i} origin={origin} />;
+    });
+  }
+
+  render () {
+    const {origins, isVisible} = this.props;
+
+    if (!origins) {
+      return null;
+    }
+
+    if (!isVisible || origins.length === 0) {
+      return null;
+    }
+
+    const originRows = this.createRows();
+
+    return (
+      <table id="origin-domain-table" className="table table-striped">
+        <tbody>
+          {originRows}
+        </tbody>
+      </table>
+    );
+  }
+
+};
+
+OriginTable.propTypes = {
+  isVisible: React.PropTypes.bool.isRequired,
+  origins: React.PropTypes.arrayOf(React.PropTypes.string),
+  updateOrigin: React.PropTypes.func.isRequired,
+  deleteOrigin: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/cors/components/Origins.js b/app/addons/cors/components/Origins.js
new file mode 100644
index 0000000..103f8b5
--- /dev/null
+++ b/app/addons/cors/components/Origins.js
@@ -0,0 +1,45 @@
+import React, { Component } from "react";
+
+export default class Origins extends Component {
+
+  constructor (props) {
+    super(props);
+  }
+
+  onOriginChange (event) {
+    if (event.target.value === 'all' && this.props.isAllOrigins) {
+      return;   // do nothing if all origins is already selected
+    }
+    if (event.target.value === 'selected' && !this.props.isAllOrigins) {
+      return;   // do nothing if specific origins is already selected
+    }
+
+    this.props.originChange(event.target.value === 'all');
+  }
+
+  render () {
+
+    if (!this.props.corsEnabled) {
+      return null;
+    }
+
+    return (
+      <div>
+        <p><strong> Origin Domains </strong> </p>
+        <p>Databases will accept requests from these domains: </p>
+        <label className="radio">
+          <input type="radio" checked={this.props.isAllOrigins} value="all" onChange={ this.onOriginChange.bind(this) } name="all-domains"/> All domains ( * )
+        </label>
+        <label className="radio">
+          <input type="radio" checked={!this.props.isAllOrigins} value="selected" onChange={ this.onOriginChange.bind(this) } name="selected-domains"/> Restrict to specific domains
+        </label>
+      </div>
+    );
+  }
+};
+
+Origins.propTypes = {
+  corsEnabled: React.PropTypes.bool,
+  isAllOrigins: React.PropTypes.bool,
+  originChange: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/cors/base.js b/app/addons/cors/helpers.js
similarity index 50%
copy from app/addons/cors/base.js
copy to app/addons/cors/helpers.js
index 6f8d400..3ecf4f0 100644
--- a/app/addons/cors/base.js
+++ b/app/addons/cors/helpers.js
@@ -11,9 +11,34 @@
 // the License.
 
 import FauxtonAPI from "../../core/api";
-import "./assets/less/cors.less";
-var CORS = FauxtonAPI.addon();
 
-CORS.initialize = function () {};
+export const validateDomain = (domain) => {
+  if (!validateCORSDomain(domain)) {
+    FauxtonAPI.addNotification({
+      msg: 'Please enter a valid domain, starting with http/https.',
+      type: 'error',
+      clear: true
+    });
+    return false;
+  }
+  return true;
+};
+
+export const validateCORSDomain = (domain) => {
+  return (/^https?:\/\/(.+)(:\d{2,5})?$/).test(domain);
+};
+
+export const normalizeUrls = (url) => {
+  const el = document.createElement('a');
+  el.href = url;
+
+  if (/:/.test(url)) {
+    return el.protocol + '//' + el.host;
+  }
+
+  return el.protocol + '//' + el.hostname;
+};
+
+
+
 
-export default CORS;
diff --git a/app/addons/cors/reducers.js b/app/addons/cors/reducers.js
new file mode 100644
index 0000000..9b8d3f2
--- /dev/null
+++ b/app/addons/cors/reducers.js
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import ActionTypes from "./actiontypes";
+
+const initialState = {
+      corsEnabled: false,
+      origins: [],
+      isAllOrigins: false,
+      configChanged: false,
+      shouldSaveChange: false,
+      node: '',
+      isLoading: true,
+      deleteDomainModalVisible: false,
+      domainToDelete: ''
+};
+
+export default function cors (state = initialState, action) {
+  switch (action.type) {
+
+    case ActionTypes.EDIT_CORS:
+      const corsOptions = action.options;
+      return {
+        ...state,
+        isLoading: false,
+        node: corsOptions.node,
+        corsEnabled: corsOptions.corsEnabled,
+        isAllOrigins: _.include(corsOptions.origins, '*'),
+        origins: corsOptions.origins,
+        deleteDomainModalVisible: false,
+        domainToDelete: ''
+      };
+
+    case ActionTypes.CORS_SHOW_DELETE_DOMAIN_MODAL:
+      return {
+        ...state,
+        deleteDomainModalVisible: true,
+        domainToDelete: action.domainToDelete
+      };
+
+    case ActionTypes.CORS_HIDE_DELETE_DOMAIN_MODAL:
+      return {
+        ...state,
+        deleteDomainModalVisible: false,
+        domainToDelete: ''
+      };
+
+    case ActionTypes.CORS_SET_IS_LOADING:
+      return {
+        ...state,
+        isLoading: action.isLoading
+      };
+
+    default:
+      return state;
+  }
+};
+
diff --git a/app/addons/cors/resources.js b/app/addons/cors/resources.js
deleted file mode 100644
index 6c9a1c3..0000000
--- a/app/addons/cors/resources.js
+++ /dev/null
@@ -1,110 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the 'License'); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-var CORS = FauxtonAPI.addon();
-
-
-CORS.Config = FauxtonAPI.Model.extend({
-  url: function () {
-    if (!this.get('node')) {
-      throw new Error('node not set');
-    }
-
-    return window.location.origin + '/_node/' + this.get('node') + '/_config/cors';
-  },
-
-  parse: function (resp) {
-    var origins = !resp.origins ? [] : resp.origins.split(',');
-
-    return {
-      origins: origins,
-      methods: resp.methods,
-      credentials: resp.credentials,
-      headers: resp.headers
-   };
-  }
-});
-
-CORS.Httpd = FauxtonAPI.Model.extend({
-  url: function () {
-    if (!this.get('node')) {
-      throw new Error('node not set');
-    }
-
-    return window.location.origin + '/_node/' + this.get('node') + '/_config/httpd';
-  },
-
-  corsEnabled: function () {
-    var enabledCors = this.get('enable_cors');
-
-    if (_.isUndefined(enabledCors)) {
-      return false;
-    }
-
-    return enabledCors === 'true';
-  }
-
-});
-
-CORS.ConfigModel = Backbone.Model.extend({
-  documentation: 'cors',
-
-  url: function () {
-    if (!this.get('node')) {
-      throw new Error('node not set');
-    }
-
-    return app.host + '/_node/' + this.get('node') + '/_config/' +
-      encodeURIComponent(this.get('section')) + '/' + encodeURIComponent(this.get('attribute'));
-  },
-
-  isNew: function () { return false; },
-
-  sync: function (method, model) {
-
-    var params = {
-      url: model.url(),
-      contentType: 'application/json',
-      dataType: 'json',
-      data: JSON.stringify(model.get('value'))
-    };
-
-    if (method === 'delete') {
-      params.type = 'DELETE';
-    } else {
-      params.type = 'PUT';
-    }
-
-    return $.ajax(params);
-  }
-
-});
-
-// simple helper function to validate the user entered a valid domain starting with http(s)
-CORS.validateCORSDomain = function (str) {
-  return (/^https?:\/\/(.*)(:\d{2,5})?$/).test(str);
-};
-
-CORS.normalizeUrls = function (url) {
-  var el = document.createElement('a');
-  el.href = url;
-
-  if (/:/.test(url)) {
-    return el.protocol + '//' + el.host;
-  }
-
-  return el.protocol + '//' + el.hostname;
-};
-
-export default CORS;
diff --git a/app/addons/cors/stores.js b/app/addons/cors/stores.js
deleted file mode 100644
index e8fdd62..0000000
--- a/app/addons/cors/stores.js
+++ /dev/null
@@ -1,190 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../core/api";
-import ActionTypes from "./actiontypes";
-
-var CorsStore = FauxtonAPI.Store.extend({
-
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._deleteDomainModalVisible = false;
-    this._domainToDelete = '';
-  },
-
-  editCors: function (options) {
-    this._isEnabled = options.isEnabled;
-    this._origins = options.origins;
-    this._configChanged = false;
-    this._shouldSaveChange = false;
-    this._node = options.node;
-    this._isLoading = false;
-  },
-
-  shouldSaveChange: function () {
-    return this._shouldSaveChange;
-  },
-
-  hasConfigChanged: function () {
-    return this._configChanged;
-  },
-
-  setConfigChanged: function () {
-    this._configChanged = true;
-  },
-
-  setConfigSaved: function () {
-    this._configChanged = false;
-  },
-
-  setIsLoading: function (state) {
-    this._isLoading = state;
-    this._shouldSaveChange = false;
-  },
-
-  getIsLoading: function () {
-    return this._isLoading;
-  },
-
-  isEnabled: function () {
-    return this._isEnabled;
-  },
-
-  addOrigin: function (origin) {
-    this._origins.push(origin);
-  },
-
-  deleteOrigin: function (origin) {
-    var index = _.indexOf(this._origins, origin);
-
-    if (index === -1) { return; }
-
-    this._origins.splice(index, 1);
-  },
-
-  originChange: function (isAllOrigins) {
-    if (isAllOrigins) {
-      this._origins = ['*'];
-      return;
-    }
-
-    this._origins = [];
-  },
-
-  getOrigins: function () {
-    return this._origins;
-  },
-
-  getNode: function () {
-    return this._node;
-  },
-
-  isAllOrigins: function () {
-    var origins = this.getOrigins();
-    return _.include(origins, '*');
-  },
-
-  toggleEnableCors: function () {
-    this._isEnabled = !this._isEnabled;
-  },
-
-  updateOrigin: function (updatedOrigin, originalOrigin) {
-    this.deleteOrigin(originalOrigin);
-    this.addOrigin(updatedOrigin);
-  },
-
-  showDeleteDomainModal: function (domain) {
-    this._domainToDelete = domain;
-    this._deleteDomainModalVisible = true;
-    this._shouldSaveChange = false;
-  },
-
-  hideDeleteDomainModal: function () {
-    this._deleteDomainModalVisible = false;
-    this._shouldSaveChange = false;
-  },
-
-  isDeleteDomainModalVisible: function () {
-    return this._deleteDomainModalVisible;
-  },
-
-  getDomainToDelete: function () {
-    return this._domainToDelete;
-  },
-
-  dispatch: function (action) {
-    // it should save after any change is triggered except for EDIT_CORS which is just to update
-    // cors after the first change
-    this._shouldSaveChange = true;
-
-    switch (action.type) {
-      case ActionTypes.EDIT_CORS:
-        this.editCors(action.options);
-      break;
-
-      case ActionTypes.TOGGLE_ENABLE_CORS:
-        this.toggleEnableCors();
-        this.setConfigChanged();
-      break;
-
-      case ActionTypes.CORS_ADD_ORIGIN:
-        this.addOrigin(action.origin);
-        this.setConfigChanged();
-      break;
-
-      case ActionTypes.CORS_IS_ALL_ORIGINS:
-        this.originChange(action.isAllOrigins);
-        this.setConfigChanged();
-      break;
-
-      case ActionTypes.CORS_DELETE_ORIGIN:
-        this.deleteOrigin(action.origin);
-        this.setConfigChanged();
-      break;
-
-      case ActionTypes.CORS_UPDATE_ORIGIN:
-        this.updateOrigin(action.updatedOrigin, action.originalOrigin);
-        this.setConfigChanged();
-      break;
-
-      case ActionTypes.CORS_SET_IS_LOADING:
-        this.setIsLoading(action.isLoading);
-      break;
-
-      case ActionTypes.CORS_SHOW_DELETE_DOMAIN_MODAL:
-        this.showDeleteDomainModal(action.options.domain);
-      break;
-
-      case ActionTypes.CORS_HIDE_DELETE_DOMAIN_MODAL:
-        this.hideDeleteDomainModal();
-      break;
-
-      default:
-      return;
-    }
-
-    this.triggerChange();
-  }
-
-});
-
-
-var corsStore = new CorsStore();
-
-corsStore.dispatchToken = FauxtonAPI.dispatcher.register(corsStore.dispatch.bind(corsStore));
-
-export default {
-  corsStore: corsStore
-};

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].