diff --git a/.gitignore b/.gitignore
index 698862ddd855d7fa6edfa761553fa31520c61a1b..914440ea3720e6714467d6df97cf8503247e20e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ profiles.clj
 /resources/frontend_client/app/bower_components
 /resources/frontend_client/app/dist/
 /node_modules/
+/.js_hint_output/
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000000000000000000000000000000000000..f39320a72c961c3de2174de74d3933a7a9e15529
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,7 @@
+{
+  "globalstrict": true,
+  "globals": {
+    "angular": false,
+    "console": false
+  }
+}
diff --git a/bower.json b/bower.json
index 7af767547b6f67e905718c772fcc1fca5eda9018..c78fffe3006d97a75d4ec8b34b324c1d3f8b4f9e 100644
--- a/bower.json
+++ b/bower.json
@@ -1,43 +1,43 @@
 {
-  "name": "corvus",
-  "description": "Expa Analytics",
-  "version": "0.0.0",
-  "homepage": "https://github.com/expa/data",
-  "license": "private",
-  "private": true,
-  "dependencies": {
-    "angular": "1.2.26",
-    "angular-route": "1.2.26",
-    "angular-loader": "1.2.26",
-    "angular-mocks": "1.2.26",
-    "angular-resource": "1.2.26",
-    "angular-cookies": "1.2.26",
-    "angular-sanitize": "1.2.26",
-    "angular-xeditable": "0.1.8",
-    "angular-bootstrap": "0.11.0",
-    "angular-cookie": "4.0.6",
-    "jquery": "2.1.1",
-    "angular-animate": "1.2.26",
-    "underscore": "1.8.2",
-    "angularytics": "0.3.0",
-    "angular-gridster": "0.11.7",
-    "angular-http-auth": "1.2.1",
-    "ace-builds": "1.1.7",
-    "angular-ui-ace": "0.1.1",
-    "fastclick": "~1.0.3",
-    "ng-sortable": "1.1.6",
-    "angular-readable-time": "~0.1.1",
-    "d3": "3.5.3",
-    "crossfilter": "1.3.11",
-    "dc.js": "2.0.0-beta.1",
-    "ngReact": "~0.1.3",
-    "react": "~0.12.2",
-    "react-onclickoutside": "~0.2.2",
-    "react-date-picker": "~0.5.1",
-    "moment": "~2.9.0"
-  },
-  "resolutions": {
-    "react-onclickoutside": "~0.2.2",
-    "moment": "2.8.2"
-  }
+    "name": "corvus",
+    "description": "Expa Analytics",
+    "version": "0.0.0",
+    "homepage": "https://github.com/expa/data",
+    "license": "private",
+    "private": true,
+    "dependencies": {
+        "ace-builds": "1.1.7",
+        "angular": "1.2.26",
+        "angular-animate": "1.2.26",
+        "angular-bootstrap": "0.11.0",
+        "angular-cookie": "4.0.6",
+        "angular-cookies": "1.2.26",
+        "angular-gridster": "0.11.7",
+        "angular-http-auth": "1.2.1",
+        "angular-loader": "1.2.26",
+        "angular-mocks": "1.2.26",
+        "angular-readable-time": "~0.1.1",
+        "angular-resource": "1.2.26",
+        "angular-route": "1.2.26",
+        "angular-sanitize": "1.2.26",
+        "angular-ui-ace": "0.1.1",
+        "angular-xeditable": "0.1.8",
+        "angularytics": "0.3.0",
+        "crossfilter": "1.3.11",
+        "d3": "3.5.3",
+        "dc.js": "2.0.0-beta.1",
+        "fastclick": "~1.0.3",
+        "jquery": "2.1.1",
+        "moment": "~2.9.0",
+        "ng-sortable": "1.1.6",
+        "ngReact": "~0.1.3",
+        "react": "~0.12.2",
+        "react-date-picker": "~0.5.1",
+        "react-onclickoutside": "~0.2.2",
+        "underscore": "1.8.2"
+    },
+    "resolutions": {
+        "react-onclickoutside": "~0.2.2",
+        "moment": "2.8.2"
+    }
 }
diff --git a/circle.yml b/circle.yml
index 0711a63d7cb26730f3afd3ca7e4d21541137ae4e..2ece3029d06d2b84c00bd4709cccb47092024360 100644
--- a/circle.yml
+++ b/circle.yml
@@ -4,5 +4,5 @@ machine:
       oraclejdk8
 test:
   override:
-    - case $CIRCLE_NODE_INDEX in 0) lein eastwood ;; 1) lein test ;; 2) lein bikeshed --max-line-length 240 && lein uberjar ;; esac:
+    - case $CIRCLE_NODE_INDEX in 0) lein eastwood ;; 1) lein test ;; 2) lein uberjar ;; 3) ./util/lint_js.sh && lein bikeshed --max-line-length 240 ;; esac:
         parallel: true
diff --git a/package.json b/package.json
index 12d7053f6a4b0ffe4641fc6a62d2466eacb3e129..b2b133457e1ecc31ffc7cc65f4f8eb18651800a5 100644
--- a/package.json
+++ b/package.json
@@ -1,30 +1,30 @@
 {
-  "name": "corvus",
-  "private": true,
-  "version": "0.0.0",
-  "description": "Expa Analytics",
-  "repository": "https://github.com/expa/data",
-  "license": "private",
-  "engines": {
-    "node": "0.10.25"
-  },
-  "dependencies": {
-    "bower": "1.3.12",
-    "gulp": "^3.8.8",
-    "gulp-concat": "^2.4.0",
-    "gulp-myth": "^1.0.1",
-    "gulp-jsx": "^0.7.0",
-    "gulp-react": "^2.0.0"
-  },
-  "devDependencies": {
-    "http-server": "^0.6.1",
-    "jshint": "2.4.4",
-    "karma": "~0.10",
-    "karma-junit-reporter": "^0.2.2",
-    "protractor": "~0.20.1",
-    "shelljs": "^0.2.6"
-  },
-  "scripts": {
-    "postinstall": "./node_modules/bower/bin/bower install --allow-root --config.interactive=false"
-  }
+    "name": "corvus",
+    "private": true,
+    "version": "0.0.0",
+    "description": "Expa Analytics",
+    "repository": "https://github.com/expa/data",
+    "license": "private",
+    "engines": {
+        "node": "0.10.25"
+    },
+    "dependencies": {
+        "bower": "1.3.12",
+        "gulp": "^3.8.8",
+        "gulp-concat": "^2.4.0",
+        "gulp-myth": "^1.0.1",
+        "gulp-jsx": "^0.7.0",
+        "gulp-react": "^2.0.0"
+    },
+    "devDependencies": {
+        "http-server": "^0.6.1",
+        "jshint": "2.6.3",
+        "karma": "~0.10",
+        "karma-junit-reporter": "^0.2.2",
+        "protractor": "~0.20.1",
+        "shelljs": "^0.2.6"
+    },
+    "scripts": {
+        "postinstall": "./node_modules/bower/bin/bower install --allow-root --config.interactive=false"
+    }
 }
diff --git a/resources/frontend_client/app/js/google_maps.js b/resources/frontend_client/app/js/google_maps.js
index 7b230e7155025f5014abe689e98296d566a08eab..6ab79307fa5a1431cddddc8ba346a385bec1da2e 100644
--- a/resources/frontend_client/app/js/google_maps.js
+++ b/resources/frontend_client/app/js/google_maps.js
@@ -1,23 +1,137 @@
-
+'use strict';
+/*global google, window, document*/
 
 window.google = window.google || {};
 google.maps = google.maps || {};
 (function() {
-  
-  function getScript(src) {
-    document.write('<' + 'script src="' + src + '"' +
-                   ' type="text/javascript"><' + '/script>');
-  }
-  
-  var modules = google.maps.modules = {};
-  google.maps.__gjsload__ = function(name, text) {
-    modules[name] = text;
-  };
-  
-  google.maps.Load = function(apiLoad) {
-    delete google.maps.Load;
-    apiLoad([0.009999999776482582,[[["https://mts0.googleapis.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.googleapis.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"m@267000000",["https://mts0.google.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.google.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026"]],[["https://khms0.googleapis.com/kh?v=152\u0026hl=en-US\u0026","https://khms1.googleapis.com/kh?v=152\u0026hl=en-US\u0026"],null,null,null,1,"152",["https://khms0.google.com/kh?v=152\u0026hl=en-US\u0026","https://khms1.google.com/kh?v=152\u0026hl=en-US\u0026"]],[["https://mts0.googleapis.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.googleapis.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"h@267000000",["https://mts0.google.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.google.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026"]],[["https://mts0.googleapis.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.googleapis.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"t@132,r@267000000",["https://mts0.google.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026","https://mts1.google.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026"]],null,null,[["https://cbks0.googleapis.com/cbk?","https://cbks1.googleapis.com/cbk?"]],[["https://khms0.googleapis.com/kh?v=84\u0026hl=en-US\u0026","https://khms1.googleapis.com/kh?v=84\u0026hl=en-US\u0026"],null,null,null,null,"84",["https://khms0.google.com/kh?v=84\u0026hl=en-US\u0026","https://khms1.google.com/kh?v=84\u0026hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt?hl=en-US\u0026","https://mts1.googleapis.com/mapslt?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt/ft?hl=en-US\u0026","https://mts1.googleapis.com/mapslt/ft?hl=en-US\u0026"]],[["https://mts0.googleapis.com/vt?hl=en-US\u0026","https://mts1.googleapis.com/vt?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt/loom?hl=en-US\u0026","https://mts1.googleapis.com/mapslt/loom?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt?hl=en-US\u0026","https://mts1.googleapis.com/mapslt?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt/ft?hl=en-US\u0026","https://mts1.googleapis.com/mapslt/ft?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt/loom?hl=en-US\u0026","https://mts1.googleapis.com/mapslt/loom?hl=en-US\u0026"]]],["en-US","US",null,0,null,null,"https://maps.gstatic.com/mapfiles/","https://csi.gstatic.com","https://maps.googleapis.com","https://maps.googleapis.com"],["https://maps.gstatic.com/intl/en_us/mapfiles/api-3/17/7","3.17.7"],[487737768],1,null,null,null,null,null,"",["visualization"],null,1,"https://khms.googleapis.com/mz?v=152\u0026",null,"https://earthbuilder.googleapis.com","https://earthbuilder.googleapis.com",null,"https://mts.googleapis.com/vt/icon",[["https://mts0.googleapis.com/vt","https://mts1.googleapis.com/vt"],["https://mts0.googleapis.com/vt","https://mts1.googleapis.com/vt"],[null,[[0,"m",267000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[47],[37,[["smartmaps"]]]]],0],[null,[[0,"m",267000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[47],[37,[["smartmaps"]]]]],3],[null,[[0,"m",267000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[50],[37,[["smartmaps"]]]]],0],[null,[[0,"m",267000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[50],[37,[["smartmaps"]]]]],3],[null,[[4,"t",132],[0,"r",132000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[5],[37,[["smartmaps"]]]]],0],[null,[[4,"t",132],[0,"r",132000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[5],[37,[["smartmaps"]]]]],3],[null,null,[null,"en-US","US",null,18],0],[null,null,[null,"en-US","US",null,18],3],[null,null,[null,"en-US","US",null,18],6],[null,null,[null,"en-US","US",null,18],0],["https://mts0.google.com/vt","https://mts1.google.com/vt"],"/maps/vt"],2,500,["https://geo0.ggpht.com/cbk?cb_client=maps_sv.uv_api_demo","https://www.gstatic.com/landmark/tour","https://www.gstatic.com/landmark/config","/maps/preview/reveal?authuser=0","/maps/preview/log204","/gen204?tbm=map","https://static.panoramio.com.storage.googleapis.com/photos/"],["https://www.google.com/maps/api/js/widget","https://www.google.com/maps/api/js/slave_widget"]], loadScriptTime);
-  };
-  var loadScriptTime = (new Date).getTime();
-  getScript("https://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/17/7/%7Bmain,visualization%7D.js");
+
+    function getScript(src) {
+        document.write('<' + 'script src="' + src + '" type="text/javascript"></script>'); // jshint ignore:line
+    }
+
+    var modules = google.maps.modules = {};
+    google.maps.__gjsload__ = function(name, text) {
+        modules[name] = text;
+    };
+
+    google.maps.Load = function(apiLoad) {
+        delete google.maps.Load;
+        apiLoad([0.009999999776482582, [
+                [
+                    ["https://mts0.googleapis.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.googleapis.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026"], null, null, null, null, "m@267000000", ["https://mts0.google.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.google.com/vt?lyrs=m@267000000\u0026src=api\u0026hl=en-US\u0026"]
+                ],
+                [
+                    ["https://khms0.googleapis.com/kh?v=152\u0026hl=en-US\u0026", "https://khms1.googleapis.com/kh?v=152\u0026hl=en-US\u0026"], null, null, null, 1, "152", ["https://khms0.google.com/kh?v=152\u0026hl=en-US\u0026", "https://khms1.google.com/kh?v=152\u0026hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.googleapis.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026"], null, null, null, null, "h@267000000", ["https://mts0.google.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.google.com/vt?lyrs=h@267000000\u0026src=api\u0026hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.googleapis.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026"], null, null, null, null, "t@132,r@267000000", ["https://mts0.google.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026", "https://mts1.google.com/vt?lyrs=t@132,r@267000000\u0026src=api\u0026hl=en-US\u0026"]
+                ], null, null, [
+                    ["https://cbks0.googleapis.com/cbk?", "https://cbks1.googleapis.com/cbk?"]
+                ],
+                [
+                    ["https://khms0.googleapis.com/kh?v=84\u0026hl=en-US\u0026", "https://khms1.googleapis.com/kh?v=84\u0026hl=en-US\u0026"], null, null, null, null, "84", ["https://khms0.google.com/kh?v=84\u0026hl=en-US\u0026", "https://khms1.google.com/kh?v=84\u0026hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt/ft?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt/ft?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/vt?hl=en-US\u0026", "https://mts1.googleapis.com/vt?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt/loom?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt/loom?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt/ft?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt/ft?hl=en-US\u0026"]
+                ],
+                [
+                    ["https://mts0.googleapis.com/mapslt/loom?hl=en-US\u0026", "https://mts1.googleapis.com/mapslt/loom?hl=en-US\u0026"]
+                ]
+            ],
+            ["en-US", "US", null, 0, null, null, "https://maps.gstatic.com/mapfiles/", "https://csi.gstatic.com", "https://maps.googleapis.com", "https://maps.googleapis.com"],
+            ["https://maps.gstatic.com/intl/en_us/mapfiles/api-3/17/7", "3.17.7"],
+            [487737768], 1, null, null, null, null, null, "", ["visualization"], null, 1, "https://khms.googleapis.com/mz?v=152\u0026", null, "https://earthbuilder.googleapis.com", "https://earthbuilder.googleapis.com", null, "https://mts.googleapis.com/vt/icon", [
+                ["https://mts0.googleapis.com/vt", "https://mts1.googleapis.com/vt"],
+                ["https://mts0.googleapis.com/vt", "https://mts1.googleapis.com/vt"],
+                [null, [
+                        [0, "m", 267000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [47],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 0
+                ],
+                [null, [
+                        [0, "m", 267000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [47],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 3
+                ],
+                [null, [
+                        [0, "m", 267000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [50],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 0
+                ],
+                [null, [
+                        [0, "m", 267000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [50],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 3
+                ],
+                [null, [
+                        [4, "t", 132],
+                        [0, "r", 132000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [5],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 0
+                ],
+                [null, [
+                        [4, "t", 132],
+                        [0, "r", 132000000]
+                    ],
+                    [null, "en-US", "US", null, 18, null, null, null, null, null, null, [
+                        [5],
+                        [37, [
+                            ["smartmaps"]
+                        ]]
+                    ]], 3
+                ],
+                [null, null, [null, "en-US", "US", null, 18], 0],
+                [null, null, [null, "en-US", "US", null, 18], 3],
+                [null, null, [null, "en-US", "US", null, 18], 6],
+                [null, null, [null, "en-US", "US", null, 18], 0],
+                ["https://mts0.google.com/vt", "https://mts1.google.com/vt"], "/maps/vt"
+            ], 2, 500, ["https://geo0.ggpht.com/cbk?cb_client=maps_sv.uv_api_demo", "https://www.gstatic.com/landmark/tour", "https://www.gstatic.com/landmark/config", "/maps/preview/reveal?authuser=0", "/maps/preview/log204", "/gen204?tbm=map", "https://static.panoramio.com.storage.googleapis.com/photos/"],
+            ["https://www.google.com/maps/api/js/widget", "https://www.google.com/maps/api/js/slave_widget"]
+        ], loadScriptTime);
+    };
+    var loadScriptTime = (new Date()).getTime();
+    getScript("https://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/17/7/%7Bmain,visualization%7D.js");
 })();
\ No newline at end of file
diff --git a/resources/frontend_client/app/services.js b/resources/frontend_client/app/services.js
index d48ab89f9c1a25f0ebfc97d8876f661298f65f92..6f575bc7b071fd42f41bc6ad0a2e830bbab2bd28 100644
--- a/resources/frontend_client/app/services.js
+++ b/resources/frontend_client/app/services.js
@@ -97,15 +97,15 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
                 return deferred.promise;
             },
 
-            switchOrg: function (org_slug) {
+            switchOrg: function(org_slug) {
                 console.log('changing org to ...', org_slug);
                 Organization.get_by_slug({
-                    'slug':  org_slug
-                }, function (org) {
+                    'slug': org_slug
+                }, function(org) {
                     service.model.currentOrgSlug = org.slug;
                     service.model.currentOrg = org;
                     $rootScope.$broadcast('appstate:organization', service.model.currentOrg);
-                }, function (error) {
+                }, function(error) {
                     console.log('error getting current org', error);
                 });
             },
@@ -137,7 +137,7 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
                     routeContext = 'site-admin';
                 } else if ($routeParams.orgSlug) {
                     // couple of options when within an org
-                    if ($location.path().indexOf('/'+$routeParams.orgSlug+'/admin/') === 0) {
+                    if ($location.path().indexOf('/' + $routeParams.orgSlug + '/admin/') === 0) {
                         routeContext = 'org-admin';
                     } else {
                         routeContext = 'org';
@@ -224,7 +224,7 @@ CorvusServices.factory('AppState', ['$rootScope', '$routeParams', '$q', '$locati
 
                     if (service.model.currentOrgSlug != $routeParams.orgSlug) {
                         // we just navigated to a new organization
-                        this.switchOrg($routeParams.orgSlug)
+                        this.switchOrg($routeParams.orgSlug);
                         service.model.currentOrgSlug = $routeParams.orgSlug;
                         service.setCurrentOrgCookie(service.model.currentOrgSlug);
                     }
@@ -877,4 +877,4 @@ CoreServices.factory('PermissionViolation', ['$resource', '$cookies', function($
         },
 
     });
-}]);
+}]);
\ No newline at end of file
diff --git a/resources/frontend_client/app/setup/setup.controllers.js b/resources/frontend_client/app/setup/setup.controllers.js
index 853cc16549ead0cc830956e17cad83b552f7da5c..84aa0c02f895fcb8fc34a4c5ac51e303b2d6448c 100644
--- a/resources/frontend_client/app/setup/setup.controllers.js
+++ b/resources/frontend_client/app/setup/setup.controllers.js
@@ -1,7 +1,9 @@
-var SetupControllers = angular.module('corvus.setup.controllers', ['corvus.metabase.services', 'corvus.setup.services'])
+'use strict';
+
+var SetupControllers = angular.module('corvus.setup.controllers', ['corvus.metabase.services', 'corvus.setup.services']);
 
 SetupControllers.controller('SetupInit', ['$scope', '$location', '$routeParams', 'AppState',
-    function ($scope, $location, $routeParams, AppState) {
+    function($scope, $location, $routeParams, AppState) {
 
         // The only thing this controller does is grab the setup token from the url and store it in our AppState
         // then we begin the actual setup workflow by sending the user to /setup/
@@ -13,9 +15,9 @@ SetupControllers.controller('SetupInit', ['$scope', '$location', '$routeParams',
 ]);
 
 SetupControllers.controller('SetupIntro', ['$scope', '$location', '$timeout', 'ipCookie', 'Organization', 'AppState', 'Setup',
-    function ($scope, $location, $timeout, ipCookie, Organization, AppState, Setup) {
+    function($scope, $location, $timeout, ipCookie, Organization, AppState, Setup) {
 
-        $scope.createOrgAndUser = function () {
+        $scope.createOrgAndUser = function() {
 
             // start off by creating the first user of the system
             // NOTE: this should both create the user AND log us in and return a session id
@@ -25,7 +27,7 @@ SetupControllers.controller('SetupIntro', ['$scope', '$location', '$timeout', 'i
                 'first_name': $scope.newUser.firstName,
                 'last_name': $scope.newUser.lastName,
                 'password': $scope.newUser.password
-            }, function (result) {
+            }, function(result) {
                 // result should have a single :id value which is our new session id
                 var sessionId = result.id;
 
@@ -34,7 +36,11 @@ SetupControllers.controller('SetupIntro', ['$scope', '$location', '$timeout', 'i
 
                 // TODO - this session cookie code needs to be somewhere easily reusable
                 var isSecure = ($location.protocol() === "https") ? true : false;
-                ipCookie('metabase.SESSION_ID', sessionId, {path: '/', expires: 14, secure: isSecure});
+                ipCookie('metabase.SESSION_ID', sessionId, {
+                    path: '/',
+                    expires: 14,
+                    secure: isSecure
+                });
 
                 // send a login notification event
                 $scope.$emit('appstate:login', sessionId);
@@ -48,151 +54,163 @@ SetupControllers.controller('SetupIntro', ['$scope', '$location', '$timeout', 'i
                     Organization.create({
                         'name': $scope.userOrgName,
                         'slug': $scope.userOrgName
-                    }, function (org) {
+                    }, function(org) {
                         console.log('first org created', org);
 
                         // switch the org
                         // TODO - make sure this is up to snuff from a security standpoint
-                        AppState.switchOrg(org.slug)
+                        AppState.switchOrg(org.slug);
 
                         // we should be good to carry on with setting up data at this point
                         $location.path('/setup/data/');
 
-                    }, function(error){
+                    }, function(error) {
                         $scope.error = error.data;
                         console.log('error creating organization', error);
                     });
                 }, 300);
-            }, function (error) {
+            }, function(error) {
                 $scope.error = error.data;
                 console.log('error with initial user creation', error);
             });
-        }
+        };
     }
 ]);
 
 SetupControllers.controller('SetupConnection', ['$scope', '$routeParams', '$location', 'Metabase', function($scope, $routeParams, $location, Metabase) {
 
-        // TODO - we should be getting all this info from the api
-
-        var defaultPorts = {'MySql': 3306, 'Postgres': 5432, 'Mongo': 27017, "RedShift": 5439, 'Druid': 8083}
-
-        $scope.engines = [
-            {'id': 'postgres', 'name':'Postgres'},
-            {'id': 'h2', 'name':'H2'},
-            {'id': 'mysql', 'name':'MySQL'}
-        ];
-
-        $scope.connection = {};
-
-        // assume we have a new connection since this is the setup process
-        var newConnection = true
-        $scope.breadcrumb = 'Add connection'
-
-        if($routeParams.dbId) {
-            newConnection = false
-            Metabase.db_get({
-                'dbId': $routeParams.dbId
-            }, function (result) {
-                $scope.database = result;
-                $scope.breadcrumb = result.name
-                $scope.connection = parseConnectionString(result.details.conn_str)
-                $scope.connection.engine = result.engine
-            }, function (error) {
-                console.log('error', error)
-            })
-        } else {
-            var connectionType = 'Postgres'
-            $scope.connection = {
-                host: "localhost",
-                port: defaultPorts[connectionType],
-                engine: 'postgres'
-            }
+    // TODO - we should be getting all this info from the api
+
+    var defaultPorts = {
+        'MySql': 3306,
+        'Postgres': 5432,
+        'Mongo': 27017,
+        "RedShift": 5439,
+        'Druid': 8083
+    };
+
+    $scope.engines = [{
+        'id': 'postgres',
+        'name': 'Postgres'
+    }, {
+        'id': 'h2',
+        'name': 'H2'
+    }, {
+        'id': 'mysql',
+        'name': 'MySQL'
+    }];
+
+    $scope.connection = {};
+
+    // assume we have a new connection since this is the setup process
+    var newConnection = true;
+    $scope.breadcrumb = 'Add connection';
+
+    if ($routeParams.dbId) {
+        newConnection = false;
+        Metabase.db_get({
+            'dbId': $routeParams.dbId
+        }, function(result) {
+            $scope.database = result;
+            $scope.breadcrumb = result.name;
+            $scope.connection = parseConnectionString(result.details.conn_str);
+            $scope.connection.engine = result.engine;
+        }, function(error) {
+            console.log('error', error);
+        });
+    } else {
+        var connectionType = 'Postgres';
+        $scope.connection = {
+            host: "localhost",
+            port: defaultPorts[connectionType],
+            engine: 'postgres'
+        };
+    }
+
+    function parseConnectionString(connectionString) {
+        // connection strings take the form of
+        // 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
+
+        var parsedConnection = {};
+        var string = connectionString.split(" ");
+
+        for (var s in string) {
+            var connectionDetail = string[s].split("=");
+            parsedConnection[connectionDetail[0]] = connectionDetail[1];
         }
 
-        function parseConnectionString (connectionString) {
-            // connection strings take the form of
-            // 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
+        return parsedConnection;
+    }
 
-            var parsedConnection = {};
-            var string = connectionString.split(" ");
+    function buildConnectionString(values) {
+        // connection strings take the form of
+        // 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
 
-            for(var s in string) {
-                var connectionDetail = string[s].split("=");
-                parsedConnection[connectionDetail[0]] = connectionDetail[1];
-            }
+        var connectionStringElements = ['host', 'port', 'dbname', 'user', 'password'],
+            conn_str = '';
 
-            return parsedConnection;
+        for (var element in connectionStringElements) {
+            conn_str = conn_str + ' ' + connectionStringElements[element] + '=' + values[connectionStringElements[element]];
         }
 
-        function buildConnectionString (values) {
-            // connection strings take the form of
-            // 'host="<value>" post="<value" dbname="<value>" user="<value>" password="<value>"'
-
-            var connectionStringElements = ['host', 'port', 'dbname', 'user', 'password'],
-                conn_str = '';
+        return conn_str;
+    }
 
-            for(var element in connectionStringElements) {
-                conn_str = conn_str + ' ' + connectionStringElements[element] + '=' + values[connectionStringElements[element]];
+    $scope.setConnectionEngine = function(engine) {
+        $scope.connection.engine = engine;
+    };
+
+    $scope.submit = function() {
+        var database = {
+            org: $scope.currentOrg.id,
+            name: $scope.connection.dbname,
+            engine: $scope.connection.engine,
+            details: {
+                conn_str: buildConnectionString($scope.connection)
             }
+        };
 
-            return conn_str;
+        function success(result) {
+            $location.path('/setup/data');
         }
 
-        $scope.setConnectionEngine = function (engine) {
-            $scope.connection.engine = engine;
+        function error(err) {
+            $scope.error = err;
+            console.log('error', err);
         }
 
-        $scope.submit = function () {
-            var database = {
-                org: $scope.currentOrg.id,
-                name: $scope.connection.dbname,
-                engine: $scope.connection.engine,
-                details: {
-                    conn_str: buildConnectionString($scope.connection)
-                }
-            };
-            function success (result) {
-                $location.path('/setup/data');
-            }
-
-            function error (error) {
-                $scope.error = error;
-                console.log('error', error);
+        // api needs a int
+        $scope.connection.port = parseInt($scope.connection.port);
+        // Validate the connection string
+        Metabase.validate_connection($scope.connection, function(result) {
+            if (newConnection) {
+                Metabase.db_create(database, success, error);
+            } else {
+                // add the id since we're updating
+                database.id = $scope.database.id;
+                Metabase.db_update(database, success, error);
             }
 
-            // api needs a int
-            $scope.connection.port = parseInt($scope.connection.port);
-            // Validate the connection string
-            Metabase.validate_connection($scope.connection, function (result) {
-                if(newConnection) {
-                    Metabase.db_create(database, success, error);
-                } else {
-                    // add the id since we're updating
-                    database.id = $scope.database.id
-                    Metabase.db_update(database, success, error);
-                }
-
-            }, function (error) {
-                console.log(error);
-                $scope.error = "Invalid Connection String - " + error.data.message;
-            });
-        };
-}])
+        }, function(error) {
+            console.log(error);
+            $scope.error = "Invalid Connection String - " + error.data.message;
+        });
+    };
+}]);
 
-SetupControllers.controller('SetupData', ['$scope', 'Metabase', function ($scope, Metabase) {
-    $scope.$watch('currentOrg', function (org) {
-        if(!org) return;
+SetupControllers.controller('SetupData', ['$scope', 'Metabase', function($scope, Metabase) {
+    $scope.$watch('currentOrg', function(org) {
+        if (!org) return;
 
         Metabase.db_list({
-            'orgId': org.id
+                'orgId': org.id
             },
-            function (result) {
-                $scope.databases = result
+            function(result) {
+                $scope.databases = result;
             },
-            function (error) {
-                console.log('error', error)
+            function(error) {
+                console.log('error', error);
             }
         );
-    })
-}])
+    });
+}]);
\ No newline at end of file
diff --git a/resources/frontend_client/app/setup/setup.directives.js b/resources/frontend_client/app/setup/setup.directives.js
index b861761f171b5bd4094a83fde2bcea23b3a57235..66c7eeea4ba61ffa0c6fd22c63cd87b3b3d1c9dc 100644
--- a/resources/frontend_client/app/setup/setup.directives.js
+++ b/resources/frontend_client/app/setup/setup.directives.js
@@ -1,20 +1,22 @@
-var SetupDirectives = angular.module('corvus.setup.directives', [])
+'use strict';
 
-var setupPartialsDir = '/app/setup/partials'
+var SetupDirectives = angular.module('corvus.setup.directives', []);
+
+var setupPartialsDir = '/app/setup/partials';
 
 SetupDirectives
-    .directive('cvSetupHeader', function () {
+    .directive('cvSetupHeader', function() {
         return {
             restrict: 'E',
             templateUrl: setupPartialsDir + '/_header.html',
             scope: {
                 text: '@'
             }
-        }
-    })
+        };
+    });
 
 SetupDirectives
-    .directive('cvConnectionList', function () {
+    .directive('cvConnectionList', function() {
         return {
             restrict: 'E',
             templateUrl: setupPartialsDir + '/_connection_list.html',
@@ -22,11 +24,11 @@ SetupDirectives
                 connections: '=',
                 loading: '='
             }
-        }
-    })
+        };
+    });
 
 SetupDirectives
-    .directive('cvStepButton', function () {
+    .directive('cvStepButton', function() {
         return {
             restrict: 'E',
             templateUrl: setupPartialsDir + '/_step_button.html',
@@ -35,7 +37,7 @@ SetupDirectives
                 text: '@'
             },
             compile: function(element, attrs) {
-                attrs.text = attrs.text || "Next"
+                attrs.text = attrs.text || "Next";
             }
-        }
-    })
+        };
+    });
\ No newline at end of file
diff --git a/resources/frontend_client/app/setup/setup.module.js b/resources/frontend_client/app/setup/setup.module.js
index 5dbf97f79985fdbdd36e37a9e0b5bb8f16a52e09..88cc30be216ed7b78e0408bd06a0bda6b5482009 100644
--- a/resources/frontend_client/app/setup/setup.module.js
+++ b/resources/frontend_client/app/setup/setup.module.js
@@ -1,9 +1,11 @@
+'use strict';
+
 var Setup = angular.module('corvus.setup', [
     'corvus.setup.controllers',
     'corvus.setup.directives'
 ]);
 
-Setup.config(['$routeProvider', function ($routeProvider) {
+Setup.config(['$routeProvider', function($routeProvider) {
     $routeProvider.when('/setup/data', {
         templateUrl: '/app/setup/partials/setup_data.html',
         controller: 'SetupData'
@@ -18,4 +20,4 @@ Setup.config(['$routeProvider', function ($routeProvider) {
         templateUrl: '/app/setup/partials/setup_connection.html',
         controller: 'SetupConnection'
     });
-}]);
+}]);
\ No newline at end of file
diff --git a/resources/frontend_client/app/superadmin/organization/organization.controllers.js b/resources/frontend_client/app/superadmin/organization/organization.controllers.js
index 997a8c2e8422ee7f970ea697468d5de7570a9dd5..fb9631701e36c730782435051b1ad2732aec750f 100644
--- a/resources/frontend_client/app/superadmin/organization/organization.controllers.js
+++ b/resources/frontend_client/app/superadmin/organization/organization.controllers.js
@@ -1,10 +1,13 @@
 'use strict';
 /*global _*/
 
-var OrganizationControllers = angular.module('superadmin.organization.controllers', ['corvus.services']);
+var OrganizationControllers = angular.module('superadmin.organization.controllers', ['corvus.services',
+    'superadmin.index.services'
+]);
+
+OrganizationControllers.controller('OrganizationListController', ['$scope', 'Organization', 'SettingsAdminServices',
 
-OrganizationControllers.controller('OrganizationListController', ['$scope', 'Organization',
-    function($scope, Organization) {
+    function($scope, Organization, SettingsAdminServices) {
         $scope.organizations = [];
 
         $scope.saveSetting = function(setting) {
@@ -29,9 +32,9 @@ OrganizationControllers.controller('OrganizationListController', ['$scope', 'Org
         };
 
         // initialize on load
-        Organization.list(function (orgs) {
+        Organization.list(function(orgs) {
             $scope.organizations = orgs;
-        }, function (error) {
+        }, function(error) {
             console.log("Error getting organizations: ", error);
         });
     }
@@ -42,26 +45,28 @@ OrganizationControllers.controller('OrganizationDetailController', ['$scope', '$
         $scope.organization = undefined;
 
         // initialize on load
-        if($routeParams.orgId) {
+        if ($routeParams.orgId) {
             // editing an existing organization
             Organization.get({
-                'orgId': $routeParams.orgId
-            },
-            function (org) {
-                $scope.organization = org;
-            }, function (error) {
-                console.log("Error getting organization: ", error);
-                // TODO - should be a 404 response
-            });
+                    'orgId': $routeParams.orgId
+                },
+                function(org) {
+                    $scope.organization = org;
+                },
+                function(error) {
+                    console.log("Error getting organization: ", error);
+                    // TODO - should be a 404 response
+                });
 
             // provide a relevant save() function
             $scope.save = function(organization) {
                 Organization.update(organization,
-                function (org) {
-                    $scope.organization = org;
-                }, function (error) {
-                    console.log(error);
-                });
+                    function(org) {
+                        $scope.organization = org;
+                    },
+                    function(error) {
+                        console.log(error);
+                    });
             };
         } else {
             // assume we are creating a new org
@@ -72,12 +77,13 @@ OrganizationControllers.controller('OrganizationDetailController', ['$scope', '$
                 // TODO - some simple validation checks
 
                 Organization.create(organization,
-                function (org) {
-                    $location.path('/superadmin/organization/'+org.id);
-                }, function (error) {
-                    console.log(error);
-                });
+                    function(org) {
+                        $location.path('/superadmin/organization/' + org.id);
+                    },
+                    function(error) {
+                        console.log(error);
+                    });
             };
         }
     }
-]);
+]);
\ No newline at end of file
diff --git a/util/lint_js.sh b/util/lint_js.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7e2572ce00dd00bc4473c215ea33504fd53dd419
--- /dev/null
+++ b/util/lint_js.sh
@@ -0,0 +1,81 @@
+#! /bin/bash
+
+# Simple shell script for running jshint and nicely formatting the output
+
+JS_HINT=./node_modules/jshint/bin/jshint
+JS_HINT_OPTS='--config .jshintrc'
+JS_FILES=`find resources/frontend_client/app -name "*.js" | grep -v bower_components | grep -v 'app/test/' | grep -v '\#' | grep -v 'app/dist/' | grep -v react`
+
+BOLD='\033[1;30m'
+RED='\033[0;31m' # \e doesn't work on OS X but \033 works on either
+NC='\033[0m'
+
+HAS_ERROR=0
+
+# Make the .js_hint_output dir if needed
+if [ ! -d .js_hint_output ]; then
+    mkdir .js_hint_output
+fi
+
+# cache file names are are just filename with '/' replaced by '_' and stuck in the .js_hint_output dir
+cache_file_name () {
+    echo ".js_hint_output/"`echo $1 | sed 's:/:_:g'`
+}
+
+# return unix timestamp for file or 0 if it doesn't exist
+
+# find out if stat supports --format (Linux or Mac w/ updated coreutils via homebrew) or
+# if we need to use -c (default OS X)
+# OS X version doesn't support --version so see if doing that returns non-zero exit code
+if (( `stat --version >/dev/null 2>/dev/null; echo "$?"` )); then
+    STAT_FORMAT_FLAG='-f%m';
+else
+    STAT_FORMAT_FLAG='--format=%Y';
+fi
+file_modified () {
+    file=$1
+    if [ -f "$file" ] && [ -n "$file" ]; then # make sure filename is non-empty, -f will return true otherwise
+         echo `stat $STAT_FORMAT_FLAG $file`
+    else
+        echo "0";
+    fi
+}
+
+# Default output repeats file name on every offending line;
+# Instead, we'll print the filename once in bold for all bad files
+# and then print just the errors in red
+run_js_lint () {
+    file=$1
+    output_file=$2
+    $JS_HINT $JS_HINT_OPTS $file | perl -pe 's/^.*(line.*)$/$1/' | sort > $output_file
+}
+
+for file in $JS_FILES; do
+    # Find matching cached output if file hasn't changed since last run
+    cache_file=$(cache_file_name $file)
+
+    # get file modified dates
+    file_modified_date=$(file_modified $file)
+    cache_file_modified_date=$(file_modified $cache_file)
+
+    # run js lint to (re-)generate cache file if it's older than $file
+    if [ $cache_file_modified_date -lt $file_modified_date ]; then
+        run_js_lint $file $cache_file
+    fi
+
+    # ok, output the cached file either way
+    errors=$(cat $cache_file)
+    if [ -n "$errors" ]; then
+        echo -e "\n\n"${BOLD}"$file"
+        echo -e ${RED}
+        echo "$errors"
+        echo -e ${NC}
+        HAS_ERROR=1
+    fi
+done
+
+if [[ $HAS_ERROR -eq 1 ]]; then
+    exit 1
+else
+    exit 0
+fi