blob: 4ccff6314c684ae4e917345fe41a95ccc0eb5803 [file] [log] [blame]
(function() {
'use strict';
angular.module('mesos', ['ngRoute', 'mesos.services', 'ui.bootstrap', 'ui.bootstrap.dialog']).
config(['paginationConfig', '$routeProvider', function(paginationConfig, $routeProvider) {
$routeProvider
.when('/',
{templateUrl: 'static/home.html', controller: 'HomeCtrl'})
.when('/frameworks',
{templateUrl: 'static/frameworks.html', controller: 'FrameworksCtrl'})
.when('/frameworks/:id',
{templateUrl: 'static/framework.html', controller: 'FrameworkCtrl'})
.when('/offers',
{templateUrl: 'static/offers.html', controller: 'OffersCtrl'})
.when('/slaves',
{templateUrl: 'static/slaves.html', controller: 'SlavesCtrl'})
.when('/slaves/:slave_id',
{templateUrl: 'static/slave.html', controller: 'SlaveCtrl'})
.when('/slaves/:slave_id/frameworks/:framework_id',
{templateUrl: 'static/slave_framework.html', controller: 'SlaveFrameworkCtrl'})
.when('/slaves/:slave_id/frameworks/:framework_id/executors/:executor_id',
{templateUrl: 'static/slave_executor.html', controller: 'SlaveExecutorCtrl'})
// Use a non-falsy template so the controller will still be executed.
// Since the controller is intended only to redirect, the blank template
// is fine.
//
// By design, controllers currently will not handle routes if the
// template is falsy. There is an issue open in Angular to add that
// feature:
//
// https://github.com/angular/angular.js/issues/1838
.when('/slaves/:slave_id/frameworks/:framework_id/executors/:executor_id/browse',
{template: ' ', controller: 'SlaveExecutorRerouterCtrl'})
.when('/slaves/:slave_id/browse',
{templateUrl: 'static/browse.html', controller: 'BrowseCtrl'})
.otherwise({redirectTo: '/'});
// Configure [Angular UI Pagination][1]:
// * Show first/last buttons
// * Show 50 items per page
// * Show "..." when there are pages beyond the max shown
//
// [1] http://angular-ui.github.io/bootstrap/#/pagination
paginationConfig.boundaryLinks = true;
paginationConfig.rotate = false;
ZeroClipboard.setDefaults({
moviePath: '/static/obj/zeroclipboard-1.1.7.swf'
});
}])
.filter('truncateMesosID', function() {
return function(id) {
if (id) {
var truncatedIdParts = id.split('-');
if (truncatedIdParts.length > 3) {
return '…' + truncatedIdParts.splice(3, 3).join('-');
} else {
return id;
}
} else {
return '';
}
};
})
.filter('truncateMesosState', function() {
return function(state) {
// Remove the "TASK_" prefix.
return state.substring(5);
};
})
.filter('isoDate', function($filter) {
return function(date) {
var i = parseInt(date, 10);
if (_.isNaN(i)) { return '' };
return $filter('date')(i, 'yyyy-MM-ddTH:mm:ssZ');
};
})
.filter('relativeDate', function() {
return function(date, refDate) {
var i = parseInt(date, 10);
if (_.isNaN(i)) { return '' };
return relativeDate(i, refDate);
};
})
.filter('slice', function() {
return function(array, begin, end) {
if (_.isArray(array)) {
return array.slice(begin, end);
}
};
})
.filter('unixDate', function($filter) {
return function(date) {
if ((new Date(date)).getFullYear() == (new Date()).getFullYear()) {
return $filter('date')(date, 'MMM dd HH:mm');
} else {
return $filter('date')(date, 'MMM dd YYYY');
}
};
})
.filter('dataSize', function() {
var BYTES_PER_KB = Math.pow(2, 10);
var BYTES_PER_MB = Math.pow(2, 20);
var BYTES_PER_GB = Math.pow(2, 30);
return function(bytes) {
if (bytes == null || isNaN(bytes)) {
return '';
} else if (bytes < BYTES_PER_KB) {
return bytes.toFixed() + ' B';
} else if (bytes < BYTES_PER_MB) {
return (bytes / BYTES_PER_KB).toFixed() + ' KB';
} else if (bytes < BYTES_PER_GB) {
return (bytes / BYTES_PER_MB).toFixed() + ' MB';
} else {
return (bytes / BYTES_PER_GB).toFixed(1) + ' GB';
}
};
})
// Defines the 'clipboard' directive, which integrates copying to the user's
// clipboard with an Adobe Flash object via the ZeroClipboard library.
//
// Text to be copied on click is specified with the 'data-clipboard-text'
// attribute.
//
// The 'mouseenter' and 'mouseleave' events from the Flash object are exposed
// to the directive's element via the 'clipboardhover' event. There is no
// differentiation between enter/leave; they are both called 'clipboardhover'.
//
// Example:
//
// <button class="btn btn-mini" clipboard
// data-clipboard-text="I'm in your clipboard!">
// </button>
//
// See: http://zeroclipboard.github.io/ZeroClipboard/
.directive('clipboard', [function() {
return {
restrict: 'A',
scope: true,
template: '<i class="glyphicon glyphicon-file"></i>',
link: function(scope, element, attrs) {
var clip = new ZeroClipboard(element[0]);
clip.on('mouseover', function() {
angular.element(this).triggerHandler('clipboardhover');
});
clip.on('mouseout', function() {
// TODO(ssorallen): Why is 'scope' incorrect here? It has to be
// retrieved from the element explicitly to be correct.
var elScope = angular.element(this).scope();
// Restore tooltip content to its original value if it was changed by
// this Clipboard instance.
if (elScope && elScope.tt_content_orig) {
elScope.tt_content = elScope.tt_content_orig;
delete elScope.tt_content_orig;
}
angular.element(this).triggerHandler('clipboardhover');
});
clip.on('complete', function() {
// TODO(ssorallen): Why is 'scope' incorrect here? It has to be
// retrieved from the element explicitly to be correct.
var elScope = angular.element(this).scope();
if (elScope) {
// Store the tooltip's original content so it can be restored when
// the tooltip is hidden.
elScope.tt_content_orig = elScope.tt_content;
// Angular UI's Tooltip sets content on the element's scope in a
// variable named 'tt_content'. The Tooltip has no public interface,
// so set the value directly here to change the value of the tooltip
// when content is successfully copied.
elScope.tt_content = 'copied!';
elScope.$apply();
}
});
clip.on('load', function() {
// The 'load' event fires only if the Flash file loads successfully.
// The copy buttons will only display if the class 'flash' exists
// on an ancestor.
//
// Browsers with no flash support will not append the 'flash' class
// and will therefore not see the copy buttons.
angular.element('html').addClass('flash');
});
}
};
}])
.directive('mTimestamp', [ '$rootScope', function($rootScope) {
return {
restrict: 'E',
transclude: true,
scope: {
value: '@'
},
link: function($scope, element, attrs) {
$scope.longDate = JSON.parse(
localStorage.getItem('longDate') || false);
$scope.$on('mTimestamp.toggle', function() {
$scope.longDate = !$scope.longDate;
});
$scope.toggle = function() {
localStorage.setItem('longDate', !$scope.longDate);
$rootScope.$broadcast('mTimestamp.toggle');
};
},
templateUrl: 'static/directives/timestamp.html'
}
}])
.directive('mPagination', function() {
return { templateUrl: 'static/directives/pagination.html' }
})
.directive('mTableHeader', function() {
return { templateUrl: 'static/directives/tableHeader.html' }
})
.directive('mTable', ['$compile', '$filter', function($compile, $filter) {
/* This directive does not have a template. The DOM doesn't like
* having partially defined tables and so they don't work well with
* directives and templates. Because of this, the sub-elements that this
* includes are their own directive/templates and it adds them via. DOM
* manipulation here.
*/
return {
scope: true,
link: function(scope, element, attrs) {
var defaultOrder = true;
_.extend(scope, {
originalData: [],
columnKey: '',
sortOrder: defaultOrder,
pgNum: 1,
pageLength: 50,
filterTerm: '',
headerTitle: attrs.title
})
// ---
// --- Allow sorting by column based on the <th> data-key attr
var th = element.find('th');
th.attr('ng-click', 'sortColumn($event)');
$compile(th)(scope);
var setSorting = function(el) {
var key = el.attr('data-key');
if (scope.columnKey === key) {
scope.sortOrder = !scope.sortOrder;
}
else { scope.sortOrder = defaultOrder }
scope.columnKey = key;
th.removeClass('descending ascending');
el.addClass(scope.sortOrder ? 'descending' : 'ascending');
};
var defaultSortColumn = function() {
var el = element.find('[data-sort]');
if (el.length === 0) {
el = element.find('th:first');
}
return el;
};
scope.sortColumn = function(ev) {
setSorting(angular.element(ev.target));
};
setSorting(defaultSortColumn());
// ---
scope.$watch(attrs.tableContent, function(data) {
if (!data) { scope.originalData = []; return };
if (angular.isObject(data)) { data = _.values(data) }
scope.originalData = data;
});
var setTableData = function() {
scope.filteredData = $filter('filter')(scope.originalData, scope.filterTerm)
scope.$data = $filter('orderBy')(
scope.filteredData,
scope.columnKey,
scope.sortOrder).slice(
(scope.pgNum - 1) * scope.pageLength,
scope.pgNum * scope.pageLength);
};
// Reset the page number for each new filtering.
scope.$watch('filterTerm', function() { scope.pgNum = 1; });
_.each(['originalData', 'columnKey', 'sortOrder', 'pgNum', 'filterTerm'],
function(k) { scope.$watch(k, setTableData); });
// --- Pagination controls
var el = angular.element('<div m-pagination></div>');
$compile(el)(scope);
element.after(el);
// ---
// --- Filtering
var el = angular.element('<div m-table-header></div>');
$compile(el)(scope);
element.before(el);
// ---
}
};
}]);
})();