diff --git a/app/js/controllers/console.js b/app/js/controllers/console.js
new file mode 100644
index 0000000..71370cb
--- /dev/null
+++ b/app/js/controllers/console.js
@@ -0,0 +1,20 @@
+'use strict';
+
+var controllersModule = require('./_index');
+
+var codemirror = require('codemirror');
+
+/**
+ * @ngInject
+ */
+function ConsoleController($scope, $location, $stateParams, datasetService) {
+ var vm = this;
+ vm.artifactName = $stateParams.artifactName;
+ vm.show = $location.search().show;
+
+ datasetService.artifact(vm.artifactName, 'console').then(function(response) {
+ vm.data = response.data;
+ });
+}
+
+controllersModule.controller('ConsoleController', ConsoleController);
diff --git a/app/js/directives/codemirror-console.js b/app/js/directives/codemirror-console.js
new file mode 100644
index 0000000..a46fdc4
--- /dev/null
+++ b/app/js/directives/codemirror-console.js
@@ -0,0 +1,130 @@
+'use strict';
+
+var CodeMirror = require('codemirror');
+require('codemirror/addon/mode/simple');
+require('codemirror/addon/fold/foldcode');
+require('codemirror/addon/fold/foldgutter');
+
+var directivesModule = require('./_index.js');
+
+CodeMirror.defineSimpleMode('console', {
+ start: [
+ {
+ token: 'comment',
+ regex: /[\d\-]+ [\d\:\.]+ \|/,
+ sol: true
+ }, {
+ token: 'keyword',
+ regex: /\[[a-z\-]+\] \$ .*/
+ }
+ ]
+});
+
+/**
+ * @ngInject
+ */
+function codemirrorConsole($compile, $window, datasetService, summaryService) {
+ var instance = null;
+ var element = null;
+ var headers = new Map();
+
+ var rangeFinder = function(cm, pos) {
+ if (!headers.has(pos.line)) {
+ return null;
+ }
+
+ var foundSelf = false;
+ var foldEnd = null;
+ headers.forEach(function(name, lineNo) {
+ if (!foundSelf && lineNo === pos.line) {
+ foundSelf = true;
+ return;
+ }
+
+ if (foundSelf && foldEnd === null) {
+ foldEnd = lineNo - 1;
+ }
+ });
+
+ if (foldEnd === null) {
+ foldEnd = cm.lastLine();
+ }
+
+ return {
+ from: CodeMirror.Pos(pos.line, cm.getLine(pos.line).length),
+ to: CodeMirror.Pos(foldEnd, cm.getLine(foldEnd).length)
+ };
+ };
+
+ var link = function(scope, el, attrs, ctrl) {
+ instance = CodeMirror(el[0], {
+ lineNumbers: true,
+ readOnly: true,
+ value: 'test test test',
+ mode: 'console',
+ theme: 'neat',
+ foldGutter: {
+ rangeFinder: rangeFinder
+ },
+ gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
+ });
+
+ element = el.find('div');
+
+ var updateHeight = function() {
+ var rect = element[0].getBoundingClientRect();
+ element[0].style.height = ($window.innerHeight - rect.top) + 'px';
+ };
+
+ scope.$on('windowResize', updateHeight);
+ updateHeight();
+ };
+
+ /**
+ * @ngInject
+ */
+ var controller = function($scope) {
+ $scope.$watch('data', function(data) {
+ if (!data) {
+ return;
+ }
+
+ var lines = [];
+ var currentLine = 0;
+ headers.clear();
+ data.scripts.forEach(function(script) {
+ headers.set(currentLine, script.name);
+
+ script.lines.forEach(function(line) {
+ lines.push(line.date + ' | ' + line.line);
+ currentLine++;
+ });
+ });
+
+ instance.setValue(lines.join('\n'));
+
+ var doc = instance.getDoc();
+ headers.forEach(function(text, lineNumber) {
+ var element = angular.element('
' + text + '
');
+ element.addClass('console-script-header');
+
+ doc.addLineWidget(lineNumber, element[0], {
+ above: true,
+ noHScroll: true
+ });
+ });
+ });
+ };
+
+ return {
+ restrict: 'EA',
+ scope: {
+ 'data': '=',
+ 'show': '='
+ },
+ link: link,
+ controller: controller
+ };
+}
+
+directivesModule.directive('codemirrorConsole', codemirrorConsole);
diff --git a/app/js/directives/console-summary.js b/app/js/directives/console-summary.js
new file mode 100644
index 0000000..ae74750
--- /dev/null
+++ b/app/js/directives/console-summary.js
@@ -0,0 +1,31 @@
+'use strict';
+
+var directivesModule = require('./_index.js');
+
+/**
+ * @ngInject
+ */
+function consoleSummary() {
+
+ /**
+ * @ngInject
+ */
+ var controller = function($scope, $attrs, datasetService) {
+ $scope.$watch('artifactName', function(artifactName) {
+ datasetService.artifact(artifactName, 'console').then(function(response) {
+ $scope.console = response.data;
+ });
+ });
+ };
+
+ return {
+ restrict: 'EA',
+ scope: {
+ 'artifactName': '='
+ },
+ controller: controller,
+ templateUrl: 'directives/console-summary.html'
+ };
+}
+
+directivesModule.directive('consoleSummary', consoleSummary);
diff --git a/app/js/filters/bootstrap-filters.js b/app/js/filters/bootstrap-filters.js
index b72b3fe..5fcf18b 100644
--- a/app/js/filters/bootstrap-filters.js
+++ b/app/js/filters/bootstrap-filters.js
@@ -2,16 +2,22 @@
var filtersModule = require('./_index.js');
-var contextClass = function(test, type) {
+var statusClass = function(status, type) {
var clazz;
- if (test.status === 'success') {
- clazz = 'success';
- } else if (test.status === 'skip') {
- clazz = 'info';
- } else if (test.status === 'fail') {
- clazz = 'danger';
- } else {
+ if (!status) {
clazz = 'default';
+ } else {
+ status = status.toLowerCase();
+
+ if (status === 'success') {
+ clazz = 'success';
+ } else if (status === 'skip') {
+ clazz = 'info';
+ } else if (status === 'fail' || status === 'failure') {
+ clazz = 'danger';
+ } else {
+ clazz = 'default';
+ }
}
if (type) {
@@ -21,4 +27,9 @@ var contextClass = function(test, type) {
}
};
+var contextClass = function(test, type) {
+ return statusClass(test.status, type);
+};
+
filtersModule.filter('contextClass', function() { return contextClass; });
+filtersModule.filter('statusClass', function() { return statusClass; });
diff --git a/app/js/on_config.js b/app/js/on_config.js
index 28405a1..b2b42a2 100644
--- a/app/js/on_config.js
+++ b/app/js/on_config.js
@@ -29,6 +29,14 @@ function OnConfig($stateProvider, $locationProvider, $urlRouterProvider) {
title: 'Test Details'
});
+ $stateProvider.state('console', {
+ url: '/{artifactName}/console?show',
+ controller: 'ConsoleController',
+ controllerAs: 'console',
+ templateUrl: 'console.html',
+ title: 'Console'
+ });
+
$urlRouterProvider.otherwise('/');
}
diff --git a/app/styles/_codemirror.scss b/app/styles/_codemirror.scss
new file mode 100644
index 0000000..1cf66a9
--- /dev/null
+++ b/app/styles/_codemirror.scss
@@ -0,0 +1,338 @@
+/* BASICS */
+
+.CodeMirror {
+ /* Set height, width, borders, and global font properties here */
+ font-family: monospace;
+ height: 300px;
+ color: black;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+ padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+@-moz-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@-webkit-keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+@keyframes blink {
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+ background: white;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px; margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+.CodeMirror-vscrollbar {
+ right: 0; top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+ bottom: 0; left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+ right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+ left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute; left: 0; top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+ /* Hack to make IE7 behave */
+ *zoom:1;
+ *display:inline;
+}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0; bottom: 0;
+ z-index: 4;
+}
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+.CodeMirror-gutter-wrapper {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
+}
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0; right: 0; top: 0; bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ overflow: auto;
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor { position: absolute; }
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* IE7 hack to prevent it from returning funny offsetTops on the spans */
+.CodeMirror span { *vertical-align: text-bottom; }
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }
diff --git a/app/styles/_foldgutter.scss b/app/styles/_foldgutter.scss
new file mode 100644
index 0000000..ad19ae2
--- /dev/null
+++ b/app/styles/_foldgutter.scss
@@ -0,0 +1,20 @@
+.CodeMirror-foldmarker {
+ color: blue;
+ text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+ font-family: arial;
+ line-height: .3;
+ cursor: pointer;
+}
+.CodeMirror-foldgutter {
+ width: .7em;
+}
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+ cursor: pointer;
+}
+.CodeMirror-foldgutter-open:after {
+ content: "\25BE";
+}
+.CodeMirror-foldgutter-folded:after {
+ content: "\25B8";
+}
diff --git a/app/styles/_neat.scss b/app/styles/_neat.scss
new file mode 100644
index 0000000..4267b1a
--- /dev/null
+++ b/app/styles/_neat.scss
@@ -0,0 +1,12 @@
+.cm-s-neat span.cm-comment { color: #a86; }
+.cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; }
+.cm-s-neat span.cm-string { color: #a22; }
+.cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; }
+.cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; }
+.cm-s-neat span.cm-variable { color: black; }
+.cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
+.cm-s-neat span.cm-meta { color: #555; }
+.cm-s-neat span.cm-link { color: #3a3; }
+
+.cm-s-neat .CodeMirror-activeline-background { background: #e8f2ff; }
+.cm-s-neat .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
diff --git a/app/styles/directives/_codemirror-console.scss b/app/styles/directives/_codemirror-console.scss
new file mode 100644
index 0000000..f74fb91
--- /dev/null
+++ b/app/styles/directives/_codemirror-console.scss
@@ -0,0 +1,9 @@
+codemirror-console {
+ font-size: 12px;
+
+ .CodeMirror-linewidget .console-script-header {
+ font-weight: bold;
+ text-align: center;
+ background-color: lightgray;
+ }
+}
diff --git a/app/styles/main.scss b/app/styles/main.scss
index 07e7d42..90cab6d 100644
--- a/app/styles/main.scss
+++ b/app/styles/main.scss
@@ -5,8 +5,13 @@
@import 'sb-admin-2';
@import 'nprogress';
+@import 'codemirror';
+@import 'foldgutter';
+@import 'neat';
+
@import 'directives/_test-details-search.scss';
@import 'directives/_timeline-details.scss';
@import 'directives/_timeline-search.scss';
@import 'directives/_timeline-viewport.scss';
@import 'directives/_timeline-overview.scss';
+@import 'directives/_codemirror-console.scss';
diff --git a/app/views/console.html b/app/views/console.html
new file mode 100644
index 0000000..66c69da
--- /dev/null
+++ b/app/views/console.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/app/views/directives/console-summary.html b/app/views/directives/console-summary.html
new file mode 100644
index 0000000..58b2408
--- /dev/null
+++ b/app/views/directives/console-summary.html
@@ -0,0 +1,45 @@
+
+
+
{{artifactName}}
+
+
+
+
+
+ {{console.status}}
+
+
+ status
+
+
+
+ {{console.scripts.length}} of
+ {{console.scripts.length + console.remaining.length}}
+
+
+ {{console.scripts.length}} of
+ {{console.scripts.length + console.remaining.length}}
+
+
+ scripts completed
+
+
+
+
+
+
+ {{console.scripts[console.scripts.length - 2].name}}
+
+
+ last script run
+
+
+
+
+
diff --git a/package.json b/package.json
index ee306c5..f166ee9 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"browserify-ngannotate": "2.0.0",
"bulk-require": "^0.2.1",
"bulkify": "1.1.1",
+ "codemirror": "5.14.2",
"d3": "^3.5.6",
"del": "2.2.0",
"eslint": "~1.10.3",