diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index 3724ccc9e80c2cfefc2d07c9ce6184ee43b498a1..8117452b170ec0ee00577716d23f8e2d9ea15ea1 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -491,12 +491,8 @@ passwords:
             name: passwordReuseRestriction
             label: Password Reuse Restriction
             options:
-                None:
-                    label: None
-                    value: None
-                Historical:
-                    label: Historical
-                    value: Historical
+                None: None
+                Historical: Historical
         passwordHistoryDuration:
             kind: panel.input.text
             id: passwordHistoryDuration
@@ -789,7 +785,7 @@ pluginTable:
         dataModelBeans:
             label: Contents
             cells:
-                apply: JSON.stringify  # function that accepts value as sole argument
+                apply: formatJSON  # function that accepts value as sole argument
                 html: >
                     <a href="#!" class="view-plugin-info link">View Plugin Info</a>
                     <div class="hidden plugin-json-string">__VALUE__</div>
@@ -810,14 +806,14 @@ pluginTableScript:
             var _url = XNAT.url.restUrl('/xapi/plugins/' + _id);
             XNAT.xhr.getJSON(_url).done(function(data){
                 var _source = spawn('textarea', JSON.stringify(data, null, 4));
-                XNAT.app.codeEditor.init(_source, {
-                    language: 'json'
-                }).openEditor({
-                    title: 'Plugin Info',
-                    classes: 'plugin-json',
-                    footerContent: '(read-only)',
-                    buttons: { close: { label: 'Close' } }
-                });
+            XNAT.app.codeEditor.init(_source, {
+                language: 'json'
+            }).openEditor({
+                title: 'Plugin Info',
+                classes: 'plugin-json',
+                footerContent: '(read-only)',
+                buttons: { close: { label: 'Close' } }
+            });
             });
         });
         $body.on('focus', '.plugin-json textarea.ace_text-input', function(){
@@ -1195,6 +1191,8 @@ dicomScpReceivers:
                     action: /xapi/dicomscp
                     contentType: json
                     contents:
+                        pad:
+                            tag: div.pad20top
                         scpId:
                             kind: panel.input.hidden
                             id: scp-id
diff --git a/src/main/webapp/scripts/globals.js b/src/main/webapp/scripts/globals.js
index 86e852b64185759ad2b90f43d3195f6a59758d9b..5ca2b6b8a8f91e41fe8938106d11f1258d269dc4 100644
--- a/src/main/webapp/scripts/globals.js
+++ b/src/main/webapp/scripts/globals.js
@@ -127,6 +127,9 @@ function isEqual( a, b ){
 function isEqualLower(a, b){
     return isEqual(stringLower(a||''), stringLower(b||''));
 }
+function isFunction( func ){
+    return typeof func == 'function';
+}
 function isObject( obj ){
     // returns true for objects, arrays, and null
     return typeof obj == 'object';
@@ -142,7 +145,7 @@ function isEmptyObject( obj ){
     return true;
 }
 function getObject( obj ){
-    return isPlainObject(obj) ? obj : {};
+    return (isPlainObject(obj) || isFunction(obj)) ? obj : {};
 }
 function isArray( arr ){
     if ( Array.isArray ) {
@@ -177,9 +180,6 @@ function isEmpty( x, args ){
     }
     return (x === null || isUndefined(x) || !isFunction(x));
 }
-function isFunction( func ){
-    return typeof func == 'function';
-}
 function isNumber( num ){
     return (typeof num == 'number' && !isNaN(num));
 }
@@ -1203,6 +1203,11 @@ function loadCSS( url, parent ){
     parent.appendChild(scriptElement(url, min, name));
 }
 
+// basic indentation formatting only
+function formatJSON(json, indent){
+    return JSON.stringify(json, null, indent || 4);
+}
+
 function prettifyJSON(data, indent) {
     var json;
     if (typeof data != 'string') {
diff --git a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
index 695feb59d6630ab76166583fcf0d675da765d810..10885b22a6c53e560f0ee0eee9e1c517188ab1c5 100644
--- a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
+++ b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
@@ -209,23 +209,27 @@ var XNAT = getObject(XNAT || {});
         // TODO: move event listeners to parent elements - events will bubble up
         // ^-- this will reduce the number of event listeners
         function enabledCheckbox(item){
+            var ckbox = spawn('input.enabled', {
+                type: 'checkbox',
+                checked: !!item.enabled,
+                onchange: function(){
+                    // save the status when clicked
+                    var enabled = this.checked;
+                    XNAT.xhr.put({
+                        url: scpUrl(item.id + '/enabled/' + enabled),
+                        success: function(){
+                            var status = (enabled ? ' enabled' : ' disabled');
+                            XNAT.ui.banner.top(1000, '<b>' + item.aeTitle + '</b> ' + status, 'success');
+                            console.log(item.id + (enabled ? ' enabled' : ' disabled'))
+                        }
+                    });
+                }
+            });
             return spawn('div.center', [
-                ['input.enabled', {
-                    type: 'checkbox',
-                    checked: !!item.enabled,
-                    onclick: function(){
-                        // save the status when clicked
-                        var enabled = this.checked;
-                        XNAT.xhr.put({
-                            url: scpUrl(item.id + '/enabled/' + enabled),
-                            success: function(){
-                                var status = (enabled ? ' enabled' : ' disabled');
-                                XNAT.ui.banner.top(1000, '<b>' + item.aeTitle + '</b> ' + status, 'success');
-                                console.log(item.id + (enabled ? ' enabled' : ' disabled'))
-                            }
-                        });
-                    }
-                }]
+                spawn('label.switchbox', [
+                    ckbox,
+                    ['span.switchbox-outer', [['span.switchbox-inner']]]
+                ])
             ]);
         }
 
diff --git a/src/main/webapp/scripts/xnat/event.js b/src/main/webapp/scripts/xnat/event.js
index 804b29969e411fbb9d039adb4daa8e9ebc9add25..6534550b872f269b5b37395b512e00f41f6df04f 100644
--- a/src/main/webapp/scripts/xnat/event.js
+++ b/src/main/webapp/scripts/xnat/event.js
@@ -147,10 +147,3 @@ var XNAT = getObject(XNAT||{});
     ////////////////////////////////////////////////////////////
 
 })(XNAT);
-
-function isPlainObject( obj ){
-    return Object.prototype.toString.call(obj) === '[object Object]';
-}
-function getObject( obj ){
-    return isPlainObject(obj) ? obj : {};
-}
diff --git a/src/main/webapp/scripts/xnat/validate.js b/src/main/webapp/scripts/xnat/validate.js
index 7e4030c8831c3e5ba1996036c01d445678499fb5..b5dab661e86717e709ade2f2f6ccbc5ea325d154 100644
--- a/src/main/webapp/scripts/xnat/validate.js
+++ b/src/main/webapp/scripts/xnat/validate.js
@@ -111,10 +111,10 @@ var XNAT = getObject(XNAT);
         cronDay: /^((\*|\?|([0-9]|[1-2][0-9]|3[0-1]))(\/\d+)?)$/,
         cronMonth: /^((\*|\?|([0-9]|1[0-2]))(\/\d+)?)$/,
         cronMonths: /^(((\*|\?|([0-9]|1[0-2]))(\/\d+)?)|(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|DEC))$/i,
-        cronMonthNames: /^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|DEC)$/i,
+        cronMonthNames: /^(\*|\?|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|DEC)$/i,
         cronWeekday: /^((\*|\?|([0-7]))(\/\d+)?)$/,
         cronWeekdays: /^(((\*|\?|([0-7]))(\/\d+)?)|(MON|TUE|WED|THU|FRI|SAT|SUN))$/i,
-        cronWeekdayNames: /^(MON|TUE|WED|THU|FRI|SAT|SUN)$/i,
+        cronWeekdayNames: /^(\*|\?|MON|TUE|WED|THU|FRI|SAT|SUN)$/i,
         // cronAlt: /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$/,
         // cron regex lifted from this post: http://stackoverflow.com/questions/235504/validating-crontab-entries-w-php
         // cron: /^\s*($|#|\w+\s*=|(\*(?:\/\d+)?|(?:[0-5]?\d)(?:-(?:[0-5]?\d)(?:\/\d+)?)?(?:,(?:[0-5]?\d)(?:-(?:[0-5]?\d)(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:[01]?\d|2[0-3])(?:-(?:[01]?\d|2[0-3])(?:\/\d+)?)?(?:,(?:[01]?\d|2[0-3])(?:-(?:[01]?\d|2[0-3])(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:0?[1-9]|[12]\d|3[01])(?:-(?:0?[1-9]|[12]\d|3[01])(?:\/\d+)?)?(?:,(?:0?[1-9]|[12]\d|3[01])(?:-(?:0?[1-9]|[12]\d|3[01])(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:\/\d+)?)?(?:,(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:\/\d+)?)?)*|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+(\*(?:\/\d+)?|(?:[0-6])(?:-(?:[0-6])(?:\/\d+)?)?(?:,(?:[0-6])(?:-(?:[0-6])(?:\/\d+)?)?)*|mon|tue|wed|thu|fri|sat|sun)\s+|(@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly)\s+)([^\s]+)\s+(.*)$/,
@@ -122,7 +122,7 @@ var XNAT = getObject(XNAT);
     };
     // aliases
     regex.int = regex.integer;
-    regex.number = regex.numeric;
+    //regex.number = regex.numeric;
     regex.float = regex.decimal;
     regex.hex = regex.hexadecimal;
     regex.alphaNumeric = regex.alphaNum;
@@ -147,7 +147,7 @@ var XNAT = getObject(XNAT);
     // define custom test methods for more complex validations
     var test = {};
 
-    test.numeric = function(value){
+    test.numeric = test.number = function(value){
         console.log('numeric');
         return isNumeric(value);
     };
@@ -176,98 +176,35 @@ var XNAT = getObject(XNAT);
 
     // match to 6-field cron syntax:
     // 0 0 * * * *
-    test.cronFn = function(value){
+    test.cron = test.cronSyntax = function(value){
 
-        // TODO: replace all this with regex tests
+        value = (value+'').trim();
 
-        /*
-        var WORDS = ('reboot yearly annually monthly ' +
-            'weekly daily midnight hourly').split(/\s+/).map(function(word){
-            return '@' + word
-        });
-        var MONTHS = ('JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC').split(/\s+/);
-        var DAYS = ('MON TUE WED THU FRI SAT SUN').split(/\s+/);
-
-        // is it a special cron keyword?
-        if (WORDS.indexOf(value) > -1) {
-            return true
+        // easiest test - use words
+        if (regex.cronWords.test(value)) {
+            return true;
         }
 
-        // split passed value into separate fields
-        var FIELDS = (value+'').trim().split(/\s+/);
-
-        // check for 6 fields
-        if (FIELDS.length < 6) {
-            return false
-        }
+        // split value to test parts
+        var parts = value.split(/\s+/);
 
-        var SECONDS = FIELDS[0].split('/');
-        var MINUTES = FIELDS[1].split('/');
-        var HOURS   = FIELDS[2].split('/');
-        var DAY     = FIELDS[3];
-        var MONTH   = FIELDS[4];
-        var WEEKDAY = FIELDS[5];
+        // array of regexes to match 'parts' array
+        var tests = [
+            regex.cronSeconds,
+            regex.cronMinutes,
+            regex.cronHours,
+            regex.cronDay,
+            regex.cronMonths,
+            regex.cronWeekdays
+        ];
 
         var errors = 0;
 
-        function isWild(val){
-            return /[*?/]/.test((val+'').trim());
-        }
-
-        function isRange(val){
-            return /[a-z0-9]-[a-z0-9]/i.test(val)
-        }
-
-        function isError(val, regex){
-            var notWild = !isWild(val);
-            var notMatch = regex ? !regex.test(val) : false;
-            if (notWild && notMatch) {
-                errors++
-            }
-        }
-
-        function checkTime(val, limit, regex){
-
-            val = [].concat(val);
-
-            var notWild = !isWild(val[0]);
-
-            if (notWild){
-                if (+val[0] < 0 || +val[0] > limit) {
-                    isError(val, regex)
-                }
-            }
-
-            // seconds interval must be a number
-            if (val[1] && !/[0-9]/.test(val[1])) {
-                errors++
-            }
-
-        }
-
-        // seconds
-        checkTime(SECONDS, 59);
-
-        // minutes
-        checkTime(MINUTES, 59);
-
-        // hours
-        checkTime(HOURS, 23);
-
-        // day
-        checkTime(DAY, 31);
-
-        // month
-        var monthRegex = /JAN|FEB|MAR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC/i;
-        checkTime(MONTH, 12, monthRegex);
-
-        // day of the week
-        var weekdayRegex = /MON|TUE|WED|THU|FRI|SAT|SUN/i;
-        checkTime(WEEKDAY, 7, weekdayRegex);
-
-        return (errors === 0);
+        parts.forEach(function(part, i){
+            errors = tests[i].test(part) ? 0 : errors + 1;
+        });
 
-        */
+        return errors === 0;
 
     };
 
@@ -347,6 +284,47 @@ var XNAT = getObject(XNAT);
     };
     test.equals = test.equalTo;
 
+    // date checks
+    test.greaterThanDate = function(field, date){
+        var enteredDate = getValidDate(field.value),
+            validDate   = getValidDate(date);
+        if (!validDate || !enteredDate) {
+            return false;
+        }
+        return enteredDate > validDate;
+    };
+    test.gtDate = test.greaterThanDate;
+    test.greaterThanOrEqualDate = function(field, date){
+        var enteredDate = getValidDate(field.value),
+            validDate   = getValidDate(date);
+        if (!validDate || !enteredDate) {
+            return false;
+        }
+        return enteredDate >= validDate;
+    };
+    test.greaterThanOrEqualToDate = test.greaterThanOrEqualDate;
+    test.gteDate = test.greaterThanOrEqualDate;
+    test.lessThanDate = function(field, date){
+        var enteredDate = getValidDate(field.value),
+            validDate   = getValidDate(date);
+        if (!validDate || !enteredDate) {
+            return false;
+        }
+        return enteredDate < validDate;
+    };
+    test.ltDate = test.lessThanDate;
+    test.lessThanOrEqualDate = function(field, date){
+        var enteredDate = getValidDate(field.value),
+            validDate   = getValidDate(date);
+        if (!validDate || !enteredDate) {
+            return false;
+        }
+        return enteredDate <= validDate;
+    };
+    test.lessThanOrEqualToDate = test.lessThanOrEqualDate;
+    test.lteDate = test.lessThanOrEqualDate;
+
+
     // XNAT.validate('input.credit-card').is('creditCard').check();
     test.creditCard = function(value){
         // Luhn Check Code from https://gist.github.com/4075533
@@ -429,9 +407,16 @@ var XNAT = getObject(XNAT);
         return obj;
     }
 
+    // TODO: get this working
     function getValidDate(date){
 
-        if (!date.match('today') && !date.match(regex[date])) {
+        var regexDateMatch = (
+                date.match(regex['dateISO']) ||
+                date.match(regex['dateUS']) ||
+                date.match(regex['dateEU'])
+        );
+
+        if (!date.match('today') && !regexDateMatch) {
             return false;
         }
 
@@ -439,7 +424,7 @@ var XNAT = getObject(XNAT);
             validDateArray;
 
         if (!date.match('today')) {
-            validDateArray = date.split(/[\s.-]+/);
+            validDateArray = date.split(/[\s.-/]+/);
             validDate.setFullYear(validDateArray[0]);
             validDate.setMonth(validDateArray[1] - 1);
             validDate.setDate(validDateArray[2]);
diff --git a/src/main/webapp/style/app.css b/src/main/webapp/style/app.css
index 67b911efcee3d86b9a5445617ca7f111c0885126..557c85704bc51a023aa03eedb45417961e17b387 100644
--- a/src/main/webapp/style/app.css
+++ b/src/main/webapp/style/app.css
@@ -3725,5 +3725,13 @@ textarea.xml-formatted {
 .pad5h { padding-left: 5px; padding-right: 5px; }
 .pad10h { padding-left: 10px; padding-right: 10px; }
 .pad20h { padding-left: 20px; padding-right: 20px; }
+.pad1v { padding-top: 1px; padding-bottom: 1px; }
+.pad5v { padding-top: 5px; padding-bottom: 5px; }
+.pad10v { padding-top: 10px; padding-bottom: 10px; }
+.pad20v { padding-top: 20px; padding-bottom: 20px; }
+.pad1top, .pad-top-1 { padding-top: 5px; }
+.pad5top, .pad-top-5 { padding-top: 5px; }
+.pad10top, .pad-top-10 { padding-top: 10px; }
+.pad20top, .pad-top-20 { padding-top: 20px; }
 
 .inline { display: inline }