44fa844e61
Replace 20sec polling of the whole notification collection by polling of notification statistics (GET /api/notifications/stats) which includes number of unread notifications. This number is rendered in notifications badge in Fuel UI. Also, notifications are marked as read by a new handler PUT /api/notifications/change_status with {status: 'read'} payload. Partial-Bug: #1657348 Depends-On: I2e6a0daaf8712ab3064df728a8fb463ef805aa06 Change-Id: I6a7eae7abf2b43143039db7ca262ae40ce5a30b4
314 lines
9.0 KiB
JavaScript
314 lines
9.0 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.
|
|
**/
|
|
|
|
import $ from 'jquery';
|
|
import _ from 'underscore';
|
|
import i18n from 'i18n';
|
|
import Backbone from 'backbone';
|
|
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import models from 'models';
|
|
import dispatcher from 'dispatcher';
|
|
import {NailgunUnavailabilityDialog} from 'views/dialogs';
|
|
import KeystoneClient from 'keystone_client';
|
|
import RootComponent from 'views/root';
|
|
import LoginPage from 'views/login_page.js';
|
|
import WelcomePage from 'views/welcome_page';
|
|
import ClusterPage from 'views/cluster_page';
|
|
import ClustersPage from 'views/clusters_page';
|
|
import EquipmentPage from 'views/equipment_page';
|
|
import ReleasesPage from 'views/releases_page';
|
|
import PluginsPage from 'views/plugins_page';
|
|
import NotificationsPage from 'views/notifications_page';
|
|
import SupportPage from 'views/support_page';
|
|
import CapacityPage from 'views/capacity_page';
|
|
import 'backbone.routefilter';
|
|
import 'bootstrap';
|
|
import './styles/main.less';
|
|
|
|
class Router extends Backbone.Router {
|
|
routes() {
|
|
return {
|
|
login: 'login',
|
|
logout: 'logout',
|
|
welcome: 'welcome',
|
|
clusters: 'listClusters',
|
|
'cluster/:id(/:tab)(/:opt1)(/:opt2)': 'showCluster',
|
|
equipment: 'showEquipmentPage',
|
|
releases: 'listReleases',
|
|
plugins: 'listPlugins',
|
|
notifications: 'showNotifications',
|
|
support: 'showSupportPage',
|
|
capacity: 'showCapacityPage',
|
|
'*default': 'default'
|
|
};
|
|
}
|
|
|
|
// pre-route hook
|
|
before(currentRouteName) {
|
|
var currentUrl = Backbone.history.getHash();
|
|
var preventRouting = false;
|
|
// remove trailing slash
|
|
if (_.endsWith(currentUrl, '/')) {
|
|
this.navigate(currentUrl.substr(0, currentUrl.length - 1), {trigger: true, replace: true});
|
|
preventRouting = true;
|
|
}
|
|
// handle special routes
|
|
if (!preventRouting) {
|
|
var specialRoutes = [
|
|
{name: 'login', condition: () => {
|
|
var result = app.version.get('auth_required') && !app.user.get('authenticated');
|
|
if (result && currentUrl !== 'login' && currentUrl !== 'logout') {
|
|
this.returnUrl = currentUrl;
|
|
}
|
|
return result;
|
|
}},
|
|
{name: 'welcome', condition: (previousUrl) => {
|
|
return previousUrl !== 'logout' &&
|
|
_.find(app.user.get('roles'), {name: 'admin'}) &&
|
|
!app.fuelSettings.get('statistics.user_choice_saved.value');
|
|
}}
|
|
];
|
|
_.each(specialRoutes, (route) => {
|
|
if (route.condition(currentRouteName)) {
|
|
if (currentRouteName !== route.name) {
|
|
preventRouting = true;
|
|
this.navigate(route.name, {trigger: true, replace: true});
|
|
}
|
|
return false;
|
|
} else if (currentRouteName === route.name) {
|
|
preventRouting = true;
|
|
this.navigate('', {trigger: true});
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
return !preventRouting;
|
|
}
|
|
|
|
// routes
|
|
default() {
|
|
this.navigate('clusters', {trigger: true, replace: true});
|
|
}
|
|
|
|
login() {
|
|
app.loadPage(LoginPage);
|
|
}
|
|
|
|
logout() {
|
|
app.logout();
|
|
}
|
|
|
|
welcome() {
|
|
app.loadPage(WelcomePage);
|
|
}
|
|
|
|
showCluster(clusterId, tab) {
|
|
var tabs = _.map(ClusterPage.getTabs(), 'url');
|
|
if (!tab || !_.includes(tabs, tab)) {
|
|
this.navigate('cluster/' + clusterId + '/' + tabs[0], {trigger: true, replace: true});
|
|
} else {
|
|
app.loadPage(ClusterPage, arguments).catch(() => this.default());
|
|
}
|
|
}
|
|
|
|
listClusters() {
|
|
app.loadPage(ClustersPage);
|
|
}
|
|
|
|
showEquipmentPage() {
|
|
app.loadPage(EquipmentPage);
|
|
}
|
|
|
|
listReleases() {
|
|
app.loadPage(ReleasesPage);
|
|
}
|
|
|
|
listPlugins() {
|
|
app.loadPage(PluginsPage);
|
|
}
|
|
|
|
showNotifications() {
|
|
app.loadPage(NotificationsPage);
|
|
}
|
|
|
|
showSupportPage() {
|
|
app.loadPage(SupportPage);
|
|
}
|
|
|
|
showCapacityPage() {
|
|
app.loadPage(CapacityPage);
|
|
}
|
|
}
|
|
|
|
class App {
|
|
constructor() {
|
|
this.initialized = false;
|
|
|
|
// this is needed for IE,
|
|
// which caches requests resulting in wrong results (e.g /ostf/testruns/last/1)
|
|
$.ajaxSetup({cache: false});
|
|
|
|
this.overrideBackboneSyncMethod();
|
|
this.overrideBackboneAjax();
|
|
|
|
this.router = new Router();
|
|
this.version = new models.FuelVersion();
|
|
this.fuelSettings = new models.FuelSettings();
|
|
this.user = new models.User();
|
|
this.nodeStatistics = new models.NodeStatistics();
|
|
this.notificationStatistics = new models.NotificationStatistics();
|
|
this.releases = new models.Releases();
|
|
this.keystoneClient = new KeystoneClient('/keystone');
|
|
}
|
|
|
|
initialize() {
|
|
this.initialized = true;
|
|
this.mountNode = $('#main-container');
|
|
|
|
document.title = i18n('common.title');
|
|
|
|
this.version.set({auth_required: true});
|
|
this.user.set({authenticated: false});
|
|
|
|
var isNailgunAvailable = true;
|
|
|
|
return this.version.fetch()
|
|
.catch((response) => {
|
|
if (response.status !== 401) {
|
|
isNailgunAvailable = false;
|
|
}
|
|
return Promise.reject(response);
|
|
})
|
|
.then(() => {
|
|
this.user.set({authenticated: true});
|
|
if (this.version.get('auth_required')) {
|
|
return this.keystoneClient.getTokenInfo(this.user.get('token'))
|
|
.then((tokenInfo) => {
|
|
this.user.set({
|
|
id: tokenInfo.token.user.id,
|
|
roles: tokenInfo.token.roles
|
|
});
|
|
})
|
|
.catch(() => {
|
|
this.user.set({authenticated: false});
|
|
this.user.unset('token');
|
|
this.user.unset('username');
|
|
return Promise.reject();
|
|
});
|
|
} else {
|
|
return Promise.resolve();
|
|
}
|
|
})
|
|
.then(() => this.fuelSettings.fetch())
|
|
.catch(() => {
|
|
if (isNailgunAvailable) {
|
|
return Promise.resolve();
|
|
} else {
|
|
this.mountNode.empty();
|
|
NailgunUnavailabilityDialog.show({}, {preventDuplicate: true});
|
|
return Promise.reject();
|
|
}
|
|
})
|
|
.then(() => Backbone.history.start());
|
|
}
|
|
|
|
renderLayout() {
|
|
var wrappedRootComponent = ReactDOM.render(
|
|
React.createElement(
|
|
RootComponent,
|
|
_.pick(this,
|
|
'version', 'user', 'fuelSettings', 'nodeStatistics', 'notificationStatistics'
|
|
)
|
|
),
|
|
this.mountNode[0]
|
|
);
|
|
// RootComponent is wrapped with React-DnD, extracting link to it using ref
|
|
this.rootComponent = wrappedRootComponent.refs.child;
|
|
}
|
|
|
|
loadPage(Page, options = []) {
|
|
dispatcher.trigger('pageLoadStarted');
|
|
return (Page.fetchData ? Page.fetchData(...options) : Promise.resolve())
|
|
.then((pageOptions) => {
|
|
if (!this.rootComponent) this.renderLayout();
|
|
this.setPage(Page, pageOptions);
|
|
})
|
|
.catch(() => true)
|
|
.then(() => dispatcher.trigger('pageLoadFinished'));
|
|
}
|
|
|
|
setPage(Page, options) {
|
|
this.page = this.rootComponent.setPage(Page, options);
|
|
}
|
|
|
|
navigate(url, {replace} = {replace: false}) {
|
|
return this.router.navigate(url.replace(/^\//, '#'), {trigger: true, replace: !!replace});
|
|
}
|
|
|
|
logout() {
|
|
if (this.user.get('authenticated') && this.version.get('auth_required')) {
|
|
var token = this.user.get('token');
|
|
|
|
this.user.set('authenticated', false);
|
|
this.user.unset('token');
|
|
this.user.unset('username');
|
|
this.user.unset('id');
|
|
this.user.unset('roles');
|
|
|
|
this.keystoneClient.deauthenticate(token);
|
|
}
|
|
|
|
_.defer(() => this.navigate('login', {trigger: true, replace: true}));
|
|
}
|
|
|
|
overrideBackboneSyncMethod() {
|
|
var originalSyncMethod = Backbone.sync;
|
|
if (originalSyncMethod.patched) return;
|
|
Backbone.sync = function(method, model, options = {}) {
|
|
// our server doesn't support PATCH, so use PUT instead
|
|
if (method === 'patch') {
|
|
method = 'update';
|
|
}
|
|
// add auth token to header if auth is enabled
|
|
if (app.version.get('auth_required')) {
|
|
options.headers = options.headers || {};
|
|
options.headers['X-Auth-Token'] = app.user.get('token');
|
|
return originalSyncMethod.call(this, method, model, options)
|
|
.catch((response) => {
|
|
if (response && response.status === 401) {
|
|
app.logout();
|
|
}
|
|
return Promise.reject(response);
|
|
});
|
|
}
|
|
return originalSyncMethod.call(this, method, model, options);
|
|
};
|
|
Backbone.sync.patched = true;
|
|
}
|
|
|
|
overrideBackboneAjax() {
|
|
Backbone.ajax = (...args) => Promise.resolve(Backbone.$.ajax(...args));
|
|
}
|
|
}
|
|
|
|
window.app = new App();
|
|
|
|
$(() => app.initialize());
|
|
|
|
export default app;
|