You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zabbix/ui/js/class.notifications.js

1043 lines
27 KiB

/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
/**
* Default value in seconds, for poller interval.
*/
ZBX_Notifications.POLL_INTERVAL = 30;
ZBX_Notifications.ALARM_SEVERITY_RESOLVED = -1;
ZBX_Notifications.ALARM_INFINITE_SERVER = -1;
ZBX_Notifications.ALARM_ONCE_PLAYER = -1;
ZBX_Notifications.ALARM_ONCE_SERVER = 1;
/**
* Fetches and renders notifications. Server always returns full list of actual notifications that this class will
* render into DOM. Last focused ZBX_BrowserTab instance is the active one. Active ZBX_BrowserTab instance is the only
* one that polls server, meanwhile other instances are inactive. This is achieved by synchronizing state of active tab
* via ZBX_LocalStorage and responding to it's change event.
*
* Only methods prefixed with <push> are the "action dispatchers", methods prefixed with <handlePushed> responds to
* these "actions" by passing the new received state value through a method prefixed with <consume> that will adjust
* instance's internal state, that in turn can be dispatched as "action". Other methods prefixed with <handle> responds
* to other events than localStorage change event - (poll, focus, timeout..) and still they would reuse <consume>
* domain methods and issue an action via <push> if needed and call to render method explicitly. The <handlePushed> is
* not reused on the instance that produces the action. This is so to reduce complexity and increase maintainability,
* because when an action produces an action, logic diverges deep, instead <consume> various domain within logic
* and call `render` once, then <push> into localStorage once.
*
* Methods prefixed with <render> uses only consumed internal state and should not <push> any changes.
*
* @param {ZBX_LocalStorage} store
* @param {ZBX_BrowserTab} tab
*/
function ZBX_Notifications(store, tab) {
if (!(store instanceof ZBX_LocalStorage) || !(tab instanceof ZBX_BrowserTab)) {
throw 'Unmatched signature!';
}
this.active = false;
this.poll_interval = ZBX_Notifications.POLL_INTERVAL;
this.store = store;
this.tab = tab;
this.collection = new ZBX_NotificationCollection();
this.alarm = new ZBX_NotificationsAlarm(new ZBX_NotificationsAudio());
this.fetchUpdates();
this.consumeList(this._cached_list);
this.consumeUserSettings(this._cached_user_settings);
this.consumeAlarmState(this._cached_alarm_state);
// Latest data page is being reloaded in background.
var all_tabids = this.tab.getAllTabIds(),
any_active_tab = (all_tabids.indexOf(this._cached_active_tabid) !== -1);
// If pages are opened in background, and has never yet received focusIn event.
if (!any_active_tab || document.hasFocus()) {
this.becomeActive();
}
else {
this.becomeInactive();
}
/*
* Fetched store is immediately rendered if this is not the only session, data as can be trusted then.
* Then if this is active instance it will always poll server once at construction, and then rerender if needed.
*/
if (all_tabids.length > 1) {
this.render();
}
if (this.active) {
this.pushUpdates();
}
this.restartMainLoop();
this.bindEventHandlers();
}
/**
* Binds to click events, LS update events and tab events.
*/
ZBX_Notifications.prototype.bindEventHandlers = function() {
this.tab.onBeforeUnload(this.handleTabBeforeUnload.bind(this));
this.tab.onFocus(this.handleTabFocusIn.bind(this));
this.tab.onCrashed(this.handleTabFocusIn.bind(this));
this.collection.btn_snooze.onclick = this.handleSnoozeClicked.bind(this);
this.collection.btn_close.onclick = this.handleCloseClicked.bind(this);
this.collection.btn_mute.onclick = this.handleMuteClicked.bind(this);
this.store.onKeySync('notifications.active_tabid', this.handlePushedActiveTabid.bind(this));
this.store.onKeySync('notifications.list', this.handlePushedList.bind(this));
this.store.onKeySync('notifications.user_settings', this.handlePushedUserSettings.bind(this));
this.store.onKeySync('notifications.alarm_state', this.handlePushedAlarmState.bind(this));
this.alarm.onChange(this.handleAlarmStateChanged.bind(this));
};
/**
* Reads all from store.
*/
ZBX_Notifications.prototype.fetchUpdates = function() {
this._cached_list = this.store.readKey('notifications.list', []);
this._cached_user_settings = this.store.readKey('notifications.user_settings', {
msg_timeout: ZBX_Notifications.POLL_INTERVAL * 2
});
this._cached_active_tabid = this.store.readKey('notifications.active_tabid', '');
this._cached_alarm_state = this.store.readKey('notifications.alarm_state', this.alarm.produce());
};
/**
* @param {string} id
*/
ZBX_Notifications.prototype.removeById = function(id) {
if (id.constructor != String) {
id += '';
}
this.collection.removeById(id);
};
/**
* @param {string} id
*
* @return {ZBX_Notification}
*/
ZBX_Notifications.prototype.getById = function(id) {
return this.collection.getById(id);
};
/**
* @param {object} alarm_state
*/
ZBX_Notifications.prototype.consumeAlarmState = function(alarm_state) {
this.alarm.consume(alarm_state, this.getById(alarm_state.start));
};
/**
* Used to speed up poll interval, in case if user has set message timeout to be short enough it is possible
* to miss a recovered event for notification that is long gone, because of how Problems API is implemented.
*
* @param {objects} user_settings
*
* @return {integer}
*/
ZBX_Notifications.prototype.calcPollInterval = function(user_settings) {
var min_timeout = Math.floor(user_settings.msg_timeout / 2);
if (min_timeout < 1) {
min_timeout = 1;
}
else if (min_timeout > ZBX_Notifications.POLL_INTERVAL) {
min_timeout = ZBX_Notifications.POLL_INTERVAL;
}
return min_timeout;
};
/**
* @param {objects} user_settings
*/
ZBX_Notifications.prototype.consumeUserSettings = function(user_settings) {
var poll_interval = this.calcPollInterval(user_settings);
if (this.poll_interval != poll_interval) {
this.poll_interval = poll_interval;
this._main_loop_id && this.restartMainLoop();
}
this._cached_user_settings = user_settings;
if (user_settings.muted) {
this.alarm.mute();
}
else {
this.alarm.unmute();
}
if (this._cached_user_settings.disabled) {
this.alarm.stop();
this.pushAlarmState(this.alarm.produce());
this.dropStore();
}
};
/**
* Consumes list into virtual DOM (collection). Computes and resets display timeouts for notification objects.
* After display timeout collection is mutated and rendered. This loop is reused (acceptNotification) to choose
* a notification to be played - most recent, most severe. Then it is written into alarm_state that once consumed,
* will know if this notification has been played or not.
*
* @param {array} list Ordered list of raw notification objects.
*/
ZBX_Notifications.prototype.consumeList = function(list) {
this.collection.consumeList(list);
this._cached_list = this.collection.getRawList();
this.alarm.reset();
this.collection.map(function(notif) {
this.alarm.acceptNotification(notif);
notif.display_timeoutid && clearTimeout(notif.display_timeoutid);
notif.display_timeoutid = setTimeout(function() {
this.removeById(notif.getId());
this.debounceRender();
this.pushUpdates();
}.bind(this), notif.calcDisplayTimeout(this._cached_user_settings));
}.bind(this));
};
/**
* Stops ticking.
*/
ZBX_Notifications.prototype.stopMainLoop = function() {
if (this._main_loop_id) {
clearInterval(this._main_loop_id);
}
};
/**
* Sets interval for main loop. Tick is immediately executed.
*/
ZBX_Notifications.prototype.restartMainLoop = function() {
this.stopMainLoop();
this._main_loop_id = setInterval(this.mainLoop.bind(this), this.poll_interval * 1000);
this.mainLoop();
};
/**
* Invokes render once after some timeout if not called again during last timeout.
*
* @param {integer} ms Optional milliseconds for debounce.
*/
ZBX_Notifications.prototype.debounceRender = function(ms) {
ms = ms || 50;
if (this._render_timeoutid) {
clearTimeout(this._render_timeoutid);
}
this._render_timeoutid = setTimeout(this.render.bind(this), ms);
};
/**
* Write ZBX_LocalStorage only values that were updated by <consume> methods. For example, during a new user_settings
* consumption it came clear that alarm has to be updated, only if that happened, alarm will be pushed.
*/
ZBX_Notifications.prototype.pushUpdates = function() {
if (this.active) {
this.pushActiveTabid(this.tab.uid);
}
this.pushUserSettings(this._cached_user_settings);
this.pushList(this.collection.getRawList());
this.pushAlarmState(this.alarm.produce());
};
/**
* @param {array} list
*/
ZBX_Notifications.prototype.pushList = function(list) {
this.store.writeKey('notifications.list', list);
};
/**
* @param {object} user_settings
*/
ZBX_Notifications.prototype.pushUserSettings = function(user_settings) {
this.store.writeKey('notifications.user_settings', user_settings);
};
/**
* @param {object} alarm
*/
ZBX_Notifications.prototype.pushAlarmState = function(alarm_state) {
this.store.writeKey('notifications.alarm_state', alarm_state);
};
/**
* @param {string} tabid
*/
ZBX_Notifications.prototype.pushActiveTabid = function(tabid) {
this.store.writeKey('notifications.active_tabid', tabid);
};
/**
* This logic is a response - if other instance writes this tabid into LS, when current tab receives focusIn event
* or at new instance creation depending on context (for example single tab scenario without receiving focusIn event).
*/
ZBX_Notifications.prototype.becomeActive = function() {
if (this.active) {
return;
}
this._cached_active_tabid = this.tab.uid;
this.active = true;
this.pushActiveTabid(this.tab.uid);
this.fetchUpdates();
this.consumeAlarmState(this._cached_alarm_state);
this.renderAudio();
};
/**
* Notification instance may only ever become inactive when another instance becomes active. At single tab unload case
* various artifacts like seek position are transferred explicitly.
*/
ZBX_Notifications.prototype.becomeInactive = function() {
if (this.active) {
// No need to push everything.
this.pushAlarmState(this.alarm.produce());
}
this._cached_active_tabid = '';
this.active = false;
this.renderAudio();
};
/**
* Backup store still remains, this is used mainly for single instance session case on tab unload event.
*/
ZBX_Notifications.prototype.dropStore = function() {
this.store.eachKeyRegex('^notifications\\.', function(key) {
key.truncatePrimary();
});
};
/**
* @param {object} user_settings
*/
ZBX_Notifications.prototype.handlePushedUserSettings = function(user_settings) {
this.consumeUserSettings(user_settings);
this.consumeList(this.collection.getRawList());
this.render();
};
/**
* @param {array} list
*/
ZBX_Notifications.prototype.handlePushedList = function(list) {
this.consumeList(list);
this.render();
};
/**
* @param {object} alarm_state
*/
ZBX_Notifications.prototype.handlePushedAlarmState = function(alarm_state) {
this.alarm.refresh();
this.consumeAlarmState(alarm_state);
this.render();
};
/**
* @param {string} tabid
*/
ZBX_Notifications.prototype.handlePushedActiveTabid = function(tabid) {
(tabid === this.tab.uid) ? this.becomeActive() : this.becomeInactive();
};
/**
* When active tab is unloaded, any sibling tab is set to become active. If single session, then we drop LS (privacy).
* We cannot know if this unload will happen because of navigation, scripted reload or a tab was just closed.
* Latter is always assumed, so when navigating active tab, focus is deligated onto to any tab if possible,
* then this tab might reclaim focus again at construction if during that time document has focus.
* At slow connection during page navigation there will be another active tab polling for notifications (if multitab).
* Here `tab` is referred as ZBX_Notifications instance and `focus` - whether instance is `active` (not focused).
*
* @param {ZBX_BrowseTab} removed_tab Current tab instance.
* @param {array} other_tabids List of alive tab ids (without current tabid).
*/
ZBX_Notifications.prototype.handleTabBeforeUnload = function(removed_tab, other_tabids) {
if (this.active && other_tabids.length) {
this.pushActiveTabid(other_tabids[0]);
this.becomeInactive();
/*
* Solves problem happening in case when navigating to another top level domain. Chrome dispatches 'focusin'
* event right after beforeunload event. It is crucial to not to respond to that, otherwise nonexisting tab
* becomes active.
*/
this.becomeActive = function() {};
}
else if (this.active) {
this.pushAlarmState(this.alarm.produce());
this.dropStore();
}
};
/**
* Responds when this instance tab receives focus event.
*/
ZBX_Notifications.prototype.handleTabFocusIn = function() {
this.becomeActive();
};
/**
* @param {MouseEvent} e
*/
ZBX_Notifications.prototype.handleCloseClicked = function(e) {
this
.fetch('notifications.read', {ids: this.getEventIds()})
.then((resp) => {
if ('error' in resp) {
throw {error: resp.error};
}
resp.ids.forEach(function(id) {
this.removeById(id);
this.debounceRender();
}.bind(this));
this.alarm.reset();
this.pushUpdates();
})
.catch((exception) => {
if (typeof exception === 'object' && 'error' in exception) {
clearMessages();
const message_box = makeMessageBox('bad', exception.error.messages, exception.error.title);
addMessage(message_box);
}
else {
console.log('Could not read notifications:', exception);
}
});
};
/**
* @param {MouseEvent} e
*/
ZBX_Notifications.prototype.handleSnoozeClicked = function(e) {
if (this.alarm.isSnoozed(this._cached_list)) {
return;
}
this.collection.map(function(notif) {
notif.updateRaw({snoozed: true});
});
this.consumeList(this.collection.getRawList());
this.pushUpdates();
this.render();
};
/**
* @param {MouseEvent} e
*/
ZBX_Notifications.prototype.handleMuteClicked = function(e) {
this
.fetch('notifications.mute', {muted: this.alarm.muted ? 0 : 1})
.then((resp) => {
if ('error' in resp) {
throw {error: resp.error};
}
this._cached_user_settings.muted = (resp.muted == 1);
this.alarm.consume({muted: this._cached_user_settings.muted});
this.pushUpdates();
this.render();
})
.catch((exception) => {
clearMessages();
let title, messages;
if (typeof exception === 'object' && 'error' in exception) {
title = exception.error.title;
messages = exception.error.messages;
}
else {
messages = [t('Unexpected server error.')];
}
const message_box = makeMessageBox('bad', messages, title);
addMessage(message_box);
});
};
/**
* Handles server response.
*
* @param {object} resp Server response object. Contains settings and list of notifications.
*/
ZBX_Notifications.prototype.handleMainLoopResp = function(resp) {
if (resp.error) {
this.stopMainLoop();
this.store.truncateBackup();
this.dropStore();
return;
}
this.consumeUserSettings(resp.settings);
this.consumeList(resp.notifications);
this.render();
this.pushUpdates();
};
/**
* @param {ZBX_NotificationsAlarm} alarm_state
*/
ZBX_Notifications.prototype.handleAlarmStateChanged = function(alarm_state) {
this.pushAlarmState(alarm_state.produce());
};
/**
* Collection renders whole list of notifications and snooze, and mute buttons, not all state is passed down here, just
* user configuration, the list state to be rendered, has been consumed by collection before.
*/
ZBX_Notifications.prototype.renderCollection = function() {
this.collection.render(this._cached_user_settings.severity_styles, this.alarm);
};
/**
* Render everything. Any painting optimization may be considered levels deeper.
*/
ZBX_Notifications.prototype.render = function() {
this.renderCollection();
this.renderAudio();
};
/**
* Alarm is stopped for inactive instance.
*/
ZBX_Notifications.prototype.renderAudio = function() {
if (this.active) {
this.alarm.render(this._cached_user_settings, this._cached_list);
}
else {
this.alarm.stop();
}
};
/**
* @param {string} resource A value for 'action' parameter.
* @param {object} params Form data to be sent.
*
* @return {Promise}
*/
ZBX_Notifications.prototype.fetch = function(resource, params) {
return new Promise(function(resolve, reject) {
sendAjaxData('zabbix.php?action=' + resource, {
data: params || {},
success: resolve,
error: reject
});
});
};
/**
* @return {array}
*/
ZBX_Notifications.prototype.getEventIds = function() {
return this.collection.getIds();
};
/**
* Main loop periodically executes at some interval. Only if this instance is 'active' notifications are fetched
* and rendered.
*/
ZBX_Notifications.prototype.mainLoop = function() {
if (!this.active) {
return;
}
this
.fetch('notifications.get', {known_eventids: this.getEventIds()})
.then((resp) => {
if ('error' in resp) {
throw {error: resp.error};
}
this.handleMainLoopResp(resp);
})
.catch((exception) => {
if (typeof exception === 'object' && 'error' in exception) {
clearMessages();
const message_box = makeMessageBox('bad', exception.error.messages, exception.error.title);
addMessage(message_box);
}
else {
console.log('Could not get notifications:', exception);
}
});
};
/**
* Utilities.
*/
ZBX_Notifications.util = {};
/**
* @param {Node} node Display none node.
*
* @return {Promise}
*/
ZBX_Notifications.util.getNodeHeight = function(node) {
node.style.display = 'block';
node.style.position = 'absolute';
node.style.visibility = 'hidden';
node.style.overflow = 'hidden';
return new Promise(function(resolve, failed) {
function readHeight() {
if (!node.offsetHeight) {
requestAnimationFrame(readHeight);
}
else {
node.removeAttribute('style');
resolve(node.offsetHeight);
}
}
readHeight();
});
};
/**
* Fully IE11 compatible slideUp animation using CSS.
*
* @param {Node} node
* @param {integer} duration Animation duration in milliseconds.
* @param {integer} delay Milliseconds to wait before animating.
*
* @return {Promise} Resolved once animation should have finished.
*/
ZBX_Notifications.util.slideDown = function(node, duration, delay) {
delay = delay || 0;
duration = duration || 200;
return new Promise(function(resolved, failed) {
ZBX_Notifications.util.getNodeHeight(node).then(function(height) {
var padding = window.getComputedStyle(node).padding;
node.style.height = '0px';
node.style.padding = '0px';
node.style.overflow = 'hidden';
node.style.boxSizing = 'border-box';
node.style.transitionDuration = duration + 'ms';
node.style.transitionProperty = 'opacity, height, margin, padding';
setTimeout(function() {
node.style.height = height + 'px';
node.style.padding = padding;
setTimeout(function() {
node.removeAttribute('style');
}, duration);
resolved(node);
}, delay);
});
});
};
/**
* @param {Node} node
*/
ZBX_Notifications.util.fadeIn = function(node) {
node.style.opacity = 0;
node.style.display = 'inherit';
var op = 0;
var id = setInterval(function() {
op += 0.1;
if (op > 1) {
return clearInterval(id);
}
node.style.opacity = op;
}, 50);
};
/**
* @param {Node} node
*
* @return {Promise} Resolved once animation should have finished.
*/
ZBX_Notifications.util.fadeOut = function(node) {
var opacity = 1,
intervalid;
return new Promise(function(resolved, failed) {
if (node.style.display === 'none') {
return resolved(node);
}
node.style.opacity = opacity;
intervalid = setInterval(function() {
opacity -= 0.1;
if (opacity < 0) {
node.style.display = 'none';
resolved(node);
return clearInterval(intervalid);
}
node.style.opacity = opacity;
}, 50);
});
};
/**
* Fully IE11 compatible slideUp animation using CSS.
*
* @param {Node} node
* @param {integer} duration Animation duration in milliseconds.
* @param {integer} delay Milliseconds to wait before animating.
*
* @return {Promise} Resolved once animation should have finished.
*/
ZBX_Notifications.util.slideUp = function(node, duration, delay) {
delay = delay || 0;
node.style.overflow = 'hidden';
node.style.boxSizing = 'border-box';
node.style.transitionDuration = duration + 'ms';
node.style.transitionProperty = 'height, margin, padding';
node.style.height = node.offsetHeight + 'px';
setTimeout(function() {
node.style.height = '0px';
node.style.padding = '0px';
node.style.margin = '0px';
}, delay);
return new Promise(function(resolved, failed) {
setTimeout(resolved.bind(null, node), delay + duration);
});
};
/**
* @param {ZBX_NotificationsAudio} player
*/
function ZBX_NotificationsAlarm(player) {
this.player = player;
this.severity = -2;
this.start = '';
this.end = '';
this.timeout = 0;
this.muted = true;
this.notif = null;
this.on_changed_cbs = [];
this.old_id = this.getId();
}
/**
* An alarm is identified by notification and it's current severity.
*
* @return {string}
*/
ZBX_NotificationsAlarm.prototype.getId = function() {
if (this.notif) {
return this.notif.getId() + '_' + this.severity;
}
return '';
};
/**
* Invokes callbacks.
*/
ZBX_NotificationsAlarm.prototype.dispatchChanged = function() {
this.on_changed_cbs.forEach(function(callback) {
callback(this);
}.bind(this));
};
/**
* This mechanism exists to prevent or explicitly allow seek position to be applied at render.
*/
ZBX_NotificationsAlarm.prototype.refresh = function() {
this.old_id = '';
};
/**
* Subscribes a callback.
*/
ZBX_NotificationsAlarm.prototype.onChange = function(callback) {
this.on_changed_cbs.push(callback);
};
/**
* Calculated property.
*/
ZBX_NotificationsAlarm.prototype.markAsPlayed = function() {
this.end = this.getId();
};
/**
* @return {bool}
*/
ZBX_NotificationsAlarm.prototype.isPlayed = function() {
return (this.getId() === this.end);
};
/**
* @param {array} list List of raw notifications.
*
* @return {bool}
*/
ZBX_NotificationsAlarm.prototype.isSnoozed = function(list) {
for (var i = 0; i < list.length; i++) {
if (!list[i].snoozed) {
return false;
}
}
return (list.length == 0) ? false : true;
};
/**
* @return {bool}
*/
ZBX_NotificationsAlarm.prototype.isStopped = function() {
return !this.getId();
};
/**
* @param {object} alarm_state
* @param {ZBX_Notification} notif
*/
ZBX_NotificationsAlarm.prototype.consume = function(alarm_state, notif) {
if (notif) {
this.notif = notif;
}
for (var field in alarm_state) {
this[field] = alarm_state[field];
}
};
/**
* Does not update state, just renders player stopped.
*/
ZBX_NotificationsAlarm.prototype.stop = function() {
this.notif = null;
this.player.stop();
};
ZBX_NotificationsAlarm.prototype.mute = function() {
this.muted = true;
this.player.mute();
};
ZBX_NotificationsAlarm.prototype.unmute = function() {
this.muted = false;
this.player.unmute();
};
/**
* @param {object} user_settings
* @param {array} list List of raw notification objects.
*/
ZBX_NotificationsAlarm.prototype.render = function(user_settings, list) {
user_settings.muted ? this.mute() : this.unmute();
if (this.isStopped() || this.isPlayed() || this.isSnoozed(list)) {
return this.player.stop();
}
this.player.file(user_settings.files[this.severity]);
if (this.old_id !== this.getId()) {
this.player.seek(0);
this.player.stop();
}
this.player.tune({
playOnce: (this.calcTimeout(user_settings) == ZBX_Notifications.ALARM_ONCE_PLAYER),
messageTimeout: (this.notif.calcDisplayTimeout(user_settings) / 1000) >> 0,
callback: function() {
this.markAsPlayed();
this.dispatchChanged();
}.bind(this)
});
this.player.timeout(this.calcTimeout(user_settings));
this.old_id = this.getId();
};
/**
* @param {object} user_settings
*
* @return {integer}
*/
ZBX_NotificationsAlarm.prototype.calcTimeout = function(user_settings) {
if (user_settings.alarm_timeout == ZBX_Notifications.ALARM_INFINITE_SERVER) {
return (this.notif.calcDisplayTimeout(user_settings) / 1000) >> 0;
}
if (user_settings.alarm_timeout == ZBX_Notifications.ALARM_ONCE_SERVER) {
return ZBX_Notifications.ALARM_ONCE_PLAYER;
}
if (this.timeout == 0) {
return user_settings.alarm_timeout;
}
return this.timeout;
};
/**
* @return {object}
*/
ZBX_NotificationsAlarm.prototype.produce = function() {
return {
start: this.start,
end: this.end,
muted: this.muted,
severity: this.severity,
seek: this.player.getSeek(),
timeout: this.player.getTimeout(),
supported: !! this.player.audio
};
};
/*
* Resets crucial fields to accept notifications.
*/
ZBX_NotificationsAlarm.prototype.reset = function() {
this.old_id = this.getId();
this.start = '';
this.severity = -2;
this.notif = null;
};
/**
* Appends notification to state in context.
*
* @param {ZBX_Notification} notif
*/
ZBX_NotificationsAlarm.prototype.acceptNotification = function(notif) {
var raw = notif.getRaw(),
severity = raw.resolved ? ZBX_Notifications.ALARM_SEVERITY_RESOLVED : raw.severity;
if (raw.snoozed) {
return;
}
if (this.severity < severity) {
this.severity = severity;
this.notif = notif;
this.start = notif.getId();
}
};
/**
* Registering instance.
*/
ZABBIX.namespace('instances.notifications', new ZBX_Notifications(
ZABBIX.namespace('instances.localStorage'),
ZABBIX.namespace('instances.browserTab')
));
/**
* Appends list node to DOM when document is ready, then make it draggable.
*/
$(function() {
let wrapper = document.querySelector(".wrapper"),
main = document.querySelector("main"),
ntf_node = ZABBIX.namespace('instances.notifications.collection.node'),
store = ZABBIX.namespace('instances.localStorage'),
ntf_pos = store.readKey('web.notifications.pos', null),
pos_top = 10,
pos_side = 10,
side = 'right';
if (main !== null) {
main.appendChild(ntf_node);
}
if (ntf_pos !== null && 'top' in ntf_pos) {
side = ('right' in ntf_pos ? 'right' : ('left' in ntf_pos ? 'left' : null));
if (side !== null) {
pos_top = Math.max(-main.offsetTop, Math.min(ntf_pos.top, wrapper.scrollHeight - ntf_node.offsetHeight));
pos_side = Math.max(0, Math.min(ntf_pos[side], Math.floor(wrapper.scrollWidth - ntf_node.offsetWidth) / 2));
}
}
ntf_node.style.top = pos_top + 'px';
ntf_node.style[side] = pos_side + 'px';
$(ntf_node).draggable({handle: '>.dashboard-widget-head',
start: function(event, ui) {
ui.helper.data('containment', {
min_top: -main.offsetTop,
max_top: wrapper.scrollHeight - this.offsetHeight - main.offsetTop,
min_left: 0,
max_left: wrapper.scrollWidth - this.offsetWidth
});
},
drag: function(event, ui) {
let containment = ui.helper.data('containment');
ui.position.top = Math.max(Math.min(ui.position.top, containment.max_top), containment.min_top);
ui.position.left = Math.max(Math.min(ui.position.left, containment.max_left), containment.min_left);
},
stop: function(event, ui) {
ntf_pos = {top: ui.position.top};
if (ui.position.left < (wrapper.scrollWidth - this.offsetWidth) / 2) {
ntf_pos.left = ui.position.left;
this.style.right = null;
}
else {
ntf_pos.right = wrapper.scrollWidth - this.offsetWidth - ui.position.left;
this.style.left = null;
this.style.right = ntf_pos.right + 'px';
}
store.writeKey('web.notifications.pos', ntf_pos);
}
});
});