fuel-ui/static/js/views/cluster_page_tabs/logs_tab.js

334 lines
14 KiB
JavaScript

/*
* Copyright 2013 Mirantis, Inc.
*
* 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.
**/
define(
[
'utils',
'models',
'views/common',
'text!templates/cluster/logs_tab.html',
'text!templates/cluster/log_entry.html'
],
function(utils, models, commonViews, logsTabTemplate, logEntryTemplate) {
'use strict';
var LogsTab = commonViews.Tab.extend({
updateInterval: 5000,
reversed: true,
template: _.template(logsTabTemplate),
logEntryTemplate: _.template(logEntryTemplate),
lastLogLevel: 'INFO',
lastSource: '',
events: {
'click .show-logs-btn:not(.disabled)': 'onShowButtonClick',
'click .show-more-entries': 'onShowMoreClick',
'click .show-all-entries': 'onShowAllClick',
'change select': 'updateShowButtonState',
'change select[name=type]': 'onTypeChange',
'change select[name=node]': 'onNodeChange',
'change select[name=source]': 'updateLevels',
'change select[name=level]': 'saveLogLevel'
},
scheduleUpdate: function() {
this.registerDeferred($.timeout(this.updateInterval).done(_.bind(this.update, this)));
},
update: function() {
this.fetchLogs({to: this.to})
.done(_.bind(function(data) {
this.appendLogEntries(data, false);
}, this))
.always(_.bind(this.scheduleUpdate, this));
},
onTypeChange: function() {
var chosenType = this.$('select[name=type]').val();
this.$('.log-node-filter').toggle(chosenType == 'remote');
this.fetchSources(chosenType);
},
onNodeChange: function() {
this.fetchSources('remote');
},
fetchSources: function(type) {
var input = this.$('select[name=source]');
this.$('select[name=source], select[name=level]').html('').attr('disabled', true);
this.updateShowButtonState();
this.sources = new models.LogSources();
if (type == 'remote') {
this.sources.deferred = this.sources.fetch({url: '/api/logs/sources/nodes/' + this.$('select[name=node]').val()});
} else if (!this.model.get('log_sources')) {
this.sources.deferred = this.sources.fetch();
this.sources.deferred.done(_.bind(function() {
this.model.set('log_sources', this.sources.toJSON());
}, this));
} else {
this.sources.reset(this.model.get('log_sources'));
this.sources.deferred = $.Deferred();
this.sources.deferred.resolve();
}
this.sources.deferred.done(_.bind(type == 'local' ? this.updateLocalSources : this.updateRemoteSources, this));
this.sources.deferred.done(_.bind(function() {
if (this.sources.length) {
input.attr('disabled', false);
this.updateShowButtonState();
this.updateLevels();
}
}, this));
this.sources.deferred.fail(_.bind(function() {
this.$('.node-sources-error').show();
}, this));
return this.sources.deferred;
},
updateSources: function() {
var chosenType = this.$('select[name=type]').val();
if (chosenType == 'local') {
this.updateLocalSources();
} else {
this.updateRemoteSources();
}
},
updateLocalSources: function() {
var input = this.$('select[name=source]');
this.sources.each(function(source) {
if (!source.get('remote')) {
var option = $('<option/>', {value: source.id, text: source.get('name')});
if (source.get('name') == this.lastSource){
option.attr('selected', 'selected');
}
input.append(option);
}
}, this);
},
updateRemoteSources: function() {
var input = this.$('select[name=source]');
var groups = [''], sourcesByGroup = {'': []};
this.sources.each(function(source) {
var group = source.get('group') || '';
if (!_.has(sourcesByGroup, group)) {
sourcesByGroup[group] = [];
groups.push(group);
}
sourcesByGroup[group].push(source);
});
_.each(groups, function(group) {
if (sourcesByGroup[group].length) {
var el = group ? $('<optgroup/>', {label: group}).appendTo(input) : input;
_.each(sourcesByGroup[group], function(source) {
var option = $('<option/>', {value: source.id, text: source.get('name')});
if (source.get('name') == this.lastSource){
option.attr('selected', 'selected');
}
el.append(option);
}, this);
}
}, this);
},
updateLevels: function(e) {
var input = this.$('select[name=level]');
if (e) {
this.lastSource = this.$('select[name=source]').val();
} else {
this.$('select[name=source]').val(this.lastSource);
}
var chosenSourceId = this.$('select[name=source]').val();
if (chosenSourceId) {
input.html('').attr('disabled', false);
var source = this.sources.get(chosenSourceId);
_.each(source.get('levels'), function(level) {
var option = $('<option/>').text(level);
if (level == this.lastLogLevel){
option.attr('selected', 'selected');
}
input.append(option);
}, this);
}
},
saveLogLevel: function() {
this.lastLogLevel = this.$('select[name=level]').val();
},
updateShowButtonState: function() {
this.$('.show-logs-btn').toggleClass('disabled', !this.$('select[name=source]').val());
},
onShowButtonClick: function() {
this.showLogs({truncate_log: true});
},
onShowMoreClick: function(e) {
var count = parseInt($(e.currentTarget).text(), 10) + this.$('.table-logs .log-entries tr').length;
this.showLogs({truncate_log: true, max_entries: count});
},
onShowAllClick: function() {
this.showLogs({});
},
fetchLogs: function(data, callbacks) {
var options = {
url: '/api/logs',
dataType: 'json',
data: {
node: this.chosenNodeId,
source: this.chosenSourceId,
level: this.chosenLevel
},
headers: {
'X-Auth-Token': app.keystoneClient.token
}
};
_.extend(options, callbacks);
_.extend(options.data, data);
return this.registerDeferred($.ajax(options));
},
showLogs: function(params) {
this.rejectRegisteredDeferreds();
this.to = 0;
this.chosenType = this.$('select[name=type]').val();
this.chosenNodeId = this.$('select[name=node]').val();
this.chosenSourceId = this.$('select[name=source]').val();
this.chosenLevel = this.$('select[name=level]').val();
var options = this.getOptions();
this.model.set({'log_options': options}, {silent: true});
app.navigate('#cluster/' + this.model.id + '/logs/' + utils.serializeTabOptions(options), {trigger: false, replace: true});
this.$('.logs-fetch-error, .node-sources-error').hide();
if (!this.reversed) {
this.$('.table-logs').hide();
} else {
this.$('.table-logs .entries-skipped-msg').hide();
}
this.$('.logs-loading').show();
this.$('select').attr('disabled', true);
this.$('.show-logs-btn').addClass('disabled');
this.fetchLogs(params)
.done(_.bind(function(data) {
this.$('.table-logs .log-entries').html('');
if (data.entries.length) {
if (data.has_more) {
this.showEntriesSkippedMsg();
} else {
this.$('.table-logs .entries-skipped-msg').hide();
}
this.appendLogEntries(data, true);
} else {
this.$('.table-logs .no-logs-msg').show();
this.$('.table-logs .entries-skipped-msg').hide();
}
this.$('.table-logs').show();
this.scheduleUpdate();
}, this))
.fail(_.bind(function() {
this.$('.table-logs').hide();
this.$('.logs-fetch-error').show();
this.$('.show-logs-btn').removeClass('disabled');
}, this))
.always(_.bind(function() {
this.$('.logs-loading').hide();
this.$('select').attr('disabled', false);
}, this));
},
showEntriesSkippedMsg: function() {
var el = this.$('.table-logs .entries-skipped-msg');
el.show();
el.find('.show-more-entries').remove();
_.each([100, 500, 1000, 5000], function(count) {
el.find('.show-all-entries').before($('<span/>', {'class': 'show-more-entries', text: count}));
}, this);
},
appendLogEntries: function(data, doNotScroll) {
this.to = data.to;
if (data.entries.length) {
if (!this.reversed) {
data.entries.reverse();
}
// autoscroll only if window is already scrolled to bottom
var scrollToBottom = !this.reversed && !doNotScroll && $(document).height() == $(window).scrollTop() + $(window).height();
this.$('.table-logs .no-logs-msg').hide();
this.$('.table-logs .log-entries')[this.reversed ? 'prepend' : 'append'](_.map(data.entries, function(entry) {
return this.logEntryTemplate({entry: entry});
}, this).join(''));
if (scrollToBottom) {
$(window).scrollTop($(document).height());
}
}
},
getOptions: function() {
var options = {};
options.type = this.chosenType;
if (options.type == 'remote') {
options.node = this.chosenNodeId;
}
options.source = this.chosenSourceId;
options.level = this.chosenLevel.toLowerCase();
return options;
},
initialize: function(params) {
_.defaults(this, params);
this.to = 0;
this.sources = new models.LogSources();
this.types = [['local', 'Fuel Master']];
if (this.model.get('nodes').length) {
this.types.push(['remote', 'Other servers']);
}
},
render: function() {
this.$el.html(this.template({
cluster: this.model,
types: this.types,
chosenType: this.chosenType,
chosenNodeId: this.chosenNodeId,
chosenSourceId: this.chosenSourceId
})).i18n();
if (this.reversed) {
this.$('.table-logs').append(this.$('.table-logs .entries-skipped-msg').detach());
}
if (!this.sourcesFetched) {
this.sourcesFetched = true;
// this part is run on first rendering only
var options = {};
if (this.tabOptions[0]) {
options = utils.deserializeTabOptions(this.tabOptions[0]);
} else if (this.model.get('log_options')) {
options = this.model.get('log_options');
}
_.each(['type', 'node'], function(option) {
if (options[option]) {
this.$('select[name=' + option + ']').val(options[option]);
}
}, this);
this.$('select[name=type]').trigger('change'); // starts to fetch sources
if (options.source) {
this.sources.deferred.done(_.bind(function() {
this.$('select[name=source]').val(options.source).trigger('change');
if (options.level) {
this.$('select[name=level]').val(options.level.toUpperCase());
}
}, this));
}
if (_.keys(options).length) {
this.sources.deferred.done(_.bind(function() {
this.$('.show-logs-btn:not(.disabled)').click();
}, this));
}
} else {
this.updateSources();
this.updateShowButtonState();
}
return this;
}
});
return LogsTab;
});