2020-12-09 19:26:34 +01:00
|
|
|
function MessagePanel(el) {
|
|
|
|
|
this.el = el;
|
|
|
|
|
this.render();
|
2020-12-09 20:08:50 +01:00
|
|
|
this.initClearButton();
|
2020-12-09 19:26:34 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 15:05:33 +02:00
|
|
|
MessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-09 19:26:34 +01:00
|
|
|
MessagePanel.prototype.render = function() {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MessagePanel.prototype.pushMessage = function(message) {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// automatic clearing is not enabled by default. call this method from the constructor to enable
|
|
|
|
|
MessagePanel.prototype.initClearTimer = function() {
|
|
|
|
|
var me = this;
|
|
|
|
|
if (me.removalInterval) clearInterval(me.removalInterval);
|
|
|
|
|
me.removalInterval = setInterval(function () {
|
2020-12-09 20:08:50 +01:00
|
|
|
me.clearMessages(1000);
|
2020-12-09 19:26:34 +01:00
|
|
|
}, 15000);
|
2020-12-09 20:08:50 +01:00
|
|
|
};
|
2020-12-09 19:26:34 +01:00
|
|
|
|
2020-12-09 20:08:50 +01:00
|
|
|
MessagePanel.prototype.clearMessages = function(toRemain) {
|
2020-12-09 19:42:46 +01:00
|
|
|
var $elements = $(this.el).find('tbody tr');
|
|
|
|
|
// limit to 1000 entries in the list since browsers get laggy at some point
|
2020-12-09 20:08:50 +01:00
|
|
|
var toRemove = $elements.length - toRemain;
|
2020-12-09 19:42:46 +01:00
|
|
|
if (toRemove <= 0) return;
|
|
|
|
|
$elements.slice(0, toRemove).remove();
|
2020-12-09 20:08:50 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MessagePanel.prototype.initClearButton = function() {
|
|
|
|
|
var me = this;
|
|
|
|
|
me.clearButton = $(
|
|
|
|
|
'<div class="openwebrx-button">Clear</div>'
|
|
|
|
|
);
|
|
|
|
|
me.clearButton.css({
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
top: '10px',
|
|
|
|
|
right: '10px'
|
|
|
|
|
});
|
|
|
|
|
me.clearButton.on('click', function() {
|
|
|
|
|
me.clearMessages(0);
|
|
|
|
|
});
|
|
|
|
|
$(me.el).append(me.clearButton);
|
|
|
|
|
};
|
2020-12-09 19:42:46 +01:00
|
|
|
|
2023-08-22 01:53:47 +02:00
|
|
|
MessagePanel.prototype.htmlEscape = function(input) {
|
|
|
|
|
return $('<div/>').text(input).html()
|
2023-09-02 00:23:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MessagePanel.prototype.scrollToBottom = function() {
|
|
|
|
|
var $t = $(this.el).find('table');
|
|
|
|
|
$t.scrollTop($t[0].scrollHeight);
|
|
|
|
|
};
|
2023-08-22 01:53:47 +02:00
|
|
|
|
2020-12-09 19:26:34 +01:00
|
|
|
function WsjtMessagePanel(el) {
|
|
|
|
|
MessagePanel.call(this, el);
|
|
|
|
|
this.initClearTimer();
|
2023-02-14 18:45:51 +01:00
|
|
|
this.qsoModes = ['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65', 'MSK144'];
|
2021-09-06 15:05:33 +02:00
|
|
|
this.beaconModes = ['WSPR', 'FST4W'];
|
|
|
|
|
this.modes = [].concat(this.qsoModes, this.beaconModes);
|
2020-12-09 19:26:34 +01:00
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
WsjtMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
2020-12-09 19:26:34 +01:00
|
|
|
|
2021-09-06 15:05:33 +02:00
|
|
|
WsjtMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return this.modes.indexOf(message['mode']) >= 0;
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-09 19:26:34 +01:00
|
|
|
WsjtMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
|
|
|
|
'<th>UTC</th>' +
|
|
|
|
|
'<th class="decimal">dB</th>' +
|
|
|
|
|
'<th class="decimal">DT</th>' +
|
|
|
|
|
'<th class="decimal freq">Freq</th>' +
|
|
|
|
|
'<th class="message">Message</th>' +
|
|
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
WsjtMessagePanel.prototype.pushMessage = function(msg) {
|
|
|
|
|
var $b = $(this.el).find('tbody');
|
|
|
|
|
var t = new Date(msg['timestamp']);
|
|
|
|
|
var pad = function (i) {
|
|
|
|
|
return ('' + i).padStart(2, "0");
|
|
|
|
|
};
|
|
|
|
|
var linkedmsg = msg['msg'];
|
|
|
|
|
var matches;
|
|
|
|
|
|
2021-09-06 15:05:33 +02:00
|
|
|
if (this.qsoModes.indexOf(msg['mode']) >= 0) {
|
2020-12-09 19:26:34 +01:00
|
|
|
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
|
|
|
|
|
if (matches && matches[2] !== 'RR73') {
|
2023-08-22 01:53:47 +02:00
|
|
|
linkedmsg = this.htmlEscape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
|
2020-12-09 19:26:34 +01:00
|
|
|
} else {
|
2023-08-22 01:53:47 +02:00
|
|
|
linkedmsg = this.htmlEscape(linkedmsg);
|
2020-12-09 19:26:34 +01:00
|
|
|
}
|
2021-09-06 15:05:33 +02:00
|
|
|
} else if (this.beaconModes.indexOf(msg['mode']) >= 0) {
|
2020-12-09 19:26:34 +01:00
|
|
|
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
|
|
|
|
|
if (matches) {
|
2023-08-22 01:53:47 +02:00
|
|
|
linkedmsg = this.htmlEscape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + this.htmlEscape(matches[3]);
|
2020-12-09 19:26:34 +01:00
|
|
|
} else {
|
2023-08-22 01:53:47 +02:00
|
|
|
linkedmsg = this.htmlEscape(linkedmsg);
|
2020-12-09 19:26:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$b.append($(
|
|
|
|
|
'<tr data-timestamp="' + msg['timestamp'] + '">' +
|
|
|
|
|
'<td>' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '</td>' +
|
|
|
|
|
'<td class="decimal">' + msg['db'] + '</td>' +
|
|
|
|
|
'<td class="decimal">' + msg['dt'] + '</td>' +
|
|
|
|
|
'<td class="decimal freq">' + msg['freq'] + '</td>' +
|
|
|
|
|
'<td class="message">' + linkedmsg + '</td>' +
|
|
|
|
|
'</tr>'
|
|
|
|
|
));
|
2023-09-02 00:23:25 +02:00
|
|
|
this.scrollToBottom();
|
2020-12-09 19:26:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$.fn.wsjtMessagePanel = function(){
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new WsjtMessagePanel(this));
|
2021-09-06 15:05:33 +02:00
|
|
|
}
|
2020-12-09 19:26:34 +01:00
|
|
|
return this.data('panel');
|
2020-12-09 19:42:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function PacketMessagePanel(el) {
|
|
|
|
|
MessagePanel.call(this, el);
|
|
|
|
|
this.initClearTimer();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
PacketMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
2020-12-09 19:42:46 +01:00
|
|
|
|
2021-09-06 15:05:33 +02:00
|
|
|
PacketMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message['mode'] === 'APRS';
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-09 19:42:46 +01:00
|
|
|
PacketMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
|
|
|
|
'<th>UTC</th>' +
|
|
|
|
|
'<th class="callsign">Callsign</th>' +
|
|
|
|
|
'<th class="coord">Coord</th>' +
|
|
|
|
|
'<th class="message">Comment</th>' +
|
|
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PacketMessagePanel.prototype.pushMessage = function(msg) {
|
|
|
|
|
var $b = $(this.el).find('tbody');
|
|
|
|
|
var pad = function (i) {
|
|
|
|
|
return ('' + i).padStart(2, "0");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (msg.type && msg.type === 'thirdparty' && msg.data) {
|
|
|
|
|
msg = msg.data;
|
|
|
|
|
}
|
2022-11-30 01:07:16 +01:00
|
|
|
|
2020-12-09 19:42:46 +01:00
|
|
|
var source = msg.source;
|
2022-11-30 01:07:16 +01:00
|
|
|
var callsign;
|
|
|
|
|
if ('object' in source) {
|
|
|
|
|
callsign = source.object;
|
|
|
|
|
} else if ('item' in source) {
|
|
|
|
|
callsign = source.item;
|
|
|
|
|
} else {
|
|
|
|
|
callsign = source.callsign;
|
|
|
|
|
if ('ssid' in source) {
|
|
|
|
|
callsign += '-' + source.ssid;
|
2020-12-09 19:42:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var timestamp = '';
|
|
|
|
|
if (msg.timestamp) {
|
|
|
|
|
var t = new Date(msg.timestamp);
|
|
|
|
|
timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var link = '';
|
|
|
|
|
var classes = [];
|
|
|
|
|
var styles = {};
|
|
|
|
|
var overlay = '';
|
|
|
|
|
var stylesToString = function (s) {
|
|
|
|
|
return $.map(s, function (value, key) {
|
|
|
|
|
return key + ':' + value + ';'
|
|
|
|
|
}).join('')
|
|
|
|
|
};
|
|
|
|
|
if (msg.symbol) {
|
|
|
|
|
classes.push('aprs-symbol');
|
|
|
|
|
classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate'));
|
|
|
|
|
styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px';
|
|
|
|
|
styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px';
|
|
|
|
|
if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') {
|
|
|
|
|
var s = {};
|
|
|
|
|
s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px';
|
|
|
|
|
s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px';
|
|
|
|
|
overlay = '<div class="aprs-symbol aprs-symboltable-overlay" style="' + stylesToString(s) + '"></div>';
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.lat && msg.lon) {
|
|
|
|
|
classes.push('openwebrx-maps-pin');
|
2021-05-16 17:30:34 +02:00
|
|
|
overlay = '<svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg>';
|
2020-12-09 19:42:46 +01:00
|
|
|
}
|
|
|
|
|
var attrs = [
|
|
|
|
|
'class="' + classes.join(' ') + '"',
|
|
|
|
|
'style="' + stylesToString(styles) + '"'
|
|
|
|
|
].join(' ');
|
|
|
|
|
if (msg.lat && msg.lon) {
|
2022-11-30 01:07:16 +01:00
|
|
|
link = '<a ' + attrs + ' href="map?' + new URLSearchParams(source).toString() + '" target="openwebrx-map">' + overlay + '</a>';
|
2020-12-09 19:42:46 +01:00
|
|
|
} else {
|
|
|
|
|
link = '<div ' + attrs + '>' + overlay + '</div>'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$b.append($(
|
|
|
|
|
'<tr>' +
|
|
|
|
|
'<td>' + timestamp + '</td>' +
|
2022-11-30 01:07:16 +01:00
|
|
|
'<td class="callsign">' + callsign + '</td>' +
|
2020-12-09 19:42:46 +01:00
|
|
|
'<td class="coord">' + link + '</td>' +
|
2023-08-22 01:53:47 +02:00
|
|
|
'<td class="message">' + this.htmlEscape(msg.comment || msg.message || '') + '</td>' +
|
2020-12-09 19:42:46 +01:00
|
|
|
'</tr>'
|
|
|
|
|
));
|
2023-09-02 00:23:25 +02:00
|
|
|
this.scrollToBottom();
|
2020-12-09 19:42:46 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.fn.packetMessagePanel = function() {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new PacketMessagePanel(this));
|
2021-09-06 20:00:14 +02:00
|
|
|
}
|
2020-12-09 19:42:46 +01:00
|
|
|
return this.data('panel');
|
2020-12-09 19:53:37 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PocsagMessagePanel = function(el) {
|
|
|
|
|
MessagePanel.call(this, el);
|
|
|
|
|
this.initClearTimer();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
PocsagMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
2020-12-09 19:53:37 +01:00
|
|
|
|
2021-09-06 20:00:14 +02:00
|
|
|
PocsagMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message['mode'] === 'Pocsag';
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-09 19:53:37 +01:00
|
|
|
PocsagMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
|
|
|
|
'<th class="address">Address</th>' +
|
|
|
|
|
'<th class="message">Message</th>' +
|
|
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PocsagMessagePanel.prototype.pushMessage = function(msg) {
|
|
|
|
|
var $b = $(this.el).find('tbody');
|
|
|
|
|
$b.append($(
|
|
|
|
|
'<tr>' +
|
2020-12-09 20:08:50 +01:00
|
|
|
'<td class="address">' + msg.address + '</td>' +
|
2023-08-22 01:53:47 +02:00
|
|
|
'<td class="message">' + this.htmlEscape(msg.message) + '</td>' +
|
2020-12-09 19:53:37 +01:00
|
|
|
'</tr>'
|
|
|
|
|
));
|
2023-09-02 00:23:25 +02:00
|
|
|
this.scrollToBottom();
|
2020-12-09 19:53:37 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.fn.pocsagMessagePanel = function() {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new PocsagMessagePanel(this));
|
2021-09-06 15:05:33 +02:00
|
|
|
}
|
2020-12-09 19:53:37 +01:00
|
|
|
return this.data('panel');
|
2023-08-22 21:16:09 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AdsbMessagePanel = function(el) {
|
|
|
|
|
MessagePanel.call(this, el);
|
2023-08-23 00:40:24 +02:00
|
|
|
this.aircraft = {}
|
2023-08-28 21:57:52 +02:00
|
|
|
this.aircraftTrackingService = false;
|
2023-08-22 21:16:09 +02:00
|
|
|
this.initClearTimer();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
AdsbMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
2023-08-22 21:16:09 +02:00
|
|
|
|
|
|
|
|
AdsbMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message["mode"] === "ADSB";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AdsbMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
|
|
|
|
'<th class="address">ICAO</th>' +
|
2023-08-28 21:57:52 +02:00
|
|
|
'<th class="callsign">Flight</th>' +
|
2023-08-23 00:40:24 +02:00
|
|
|
'<th class="altitude">Altitude</th>' +
|
|
|
|
|
'<th class="speed">Speed</th>' +
|
|
|
|
|
'<th class="track">Track</th>' +
|
2023-08-23 18:44:05 +02:00
|
|
|
'<th class="verticalspeed">V/S</th>' +
|
2023-08-28 21:57:52 +02:00
|
|
|
'<th class="position">Position</th>' +
|
2023-08-23 00:40:24 +02:00
|
|
|
'<th class="messages">Messages</th>' +
|
2023-08-22 21:16:09 +02:00
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AdsbMessagePanel.prototype.pushMessage = function(message) {
|
2023-08-23 00:40:24 +02:00
|
|
|
if (!('icao' in message)) return;
|
|
|
|
|
if (!(message.icao in this.aircraft)) {
|
|
|
|
|
var el = $("<tr>");
|
|
|
|
|
$(this.el).find('tbody').append(el);
|
|
|
|
|
this.aircraft[message.icao] = {
|
|
|
|
|
el: el,
|
|
|
|
|
messages: 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var state = this.aircraft[message.icao];
|
|
|
|
|
Object.assign(state, message);
|
|
|
|
|
state.lastSeen = Date.now();
|
|
|
|
|
state.messages += 1;
|
|
|
|
|
|
|
|
|
|
var ifDefined = function(input, formatter) {
|
|
|
|
|
if (typeof(input) !== 'undefined') {
|
|
|
|
|
if (formatter) return formatter(input);
|
|
|
|
|
return input;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-23 18:44:05 +02:00
|
|
|
var coordRound = function(i) {
|
|
|
|
|
return Math.round(i * 1000) / 1000;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 21:57:52 +02:00
|
|
|
var getPosition = function(state) {
|
|
|
|
|
if (!('lat' in state) || !('lon') in state) return '';
|
|
|
|
|
return '<a href="map?icao=' + state.icao + '" target="openwebrx-map">' + coordRound(state.lat) + ', ' + coordRound(state.lon) + '</a>';
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-23 00:40:24 +02:00
|
|
|
state.el.html(
|
2023-08-28 21:57:52 +02:00
|
|
|
'<td>' + this.linkify(state, state.icao) + '</td>' +
|
|
|
|
|
'<td>' + this.linkify(state, ifDefined(state.identification)) + '</td>' +
|
2023-08-23 00:40:24 +02:00
|
|
|
'<td>' + ifDefined(state.altitude) + '</td>' +
|
2023-08-23 18:44:05 +02:00
|
|
|
'<td>' + ifDefined(state.groundspeed || state.IAS || state.TAS, Math.round) + '</td>' +
|
|
|
|
|
'<td>' + ifDefined(state.groundtrack || state.heading, Math.round) + '</td>' +
|
|
|
|
|
'<td>' + ifDefined(state.verticalspeed) + '</td>' +
|
2023-08-28 21:57:52 +02:00
|
|
|
'<td>' + getPosition(state) + '</td>' +
|
2023-08-23 00:40:24 +02:00
|
|
|
'<td>' + state.messages + '</td>'
|
|
|
|
|
);
|
2023-08-22 21:16:09 +02:00
|
|
|
};
|
|
|
|
|
|
2023-08-23 00:40:24 +02:00
|
|
|
AdsbMessagePanel.prototype.clearMessages = function(toRemain) {
|
|
|
|
|
var now = Date.now();
|
|
|
|
|
var me = this;
|
|
|
|
|
Object.entries(this.aircraft).forEach(function(e) {
|
|
|
|
|
if (now - e[1].lastSeen > toRemain) {
|
|
|
|
|
delete me.aircraft[e[0]];
|
|
|
|
|
e[1].el.remove();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AdsbMessagePanel.prototype.initClearTimer = function() {
|
|
|
|
|
var me = this;
|
|
|
|
|
if (me.removalInterval) clearInterval(me.removalInterval);
|
|
|
|
|
me.removalInterval = setInterval(function () {
|
|
|
|
|
me.clearMessages(30000);
|
|
|
|
|
}, 15000);
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-28 21:57:52 +02:00
|
|
|
AdsbMessagePanel.prototype.setAircraftTrackingService = function(service) {
|
|
|
|
|
this.aircraftTrackingService = service;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AdsbMessagePanel.prototype.linkify = function(state, text) {
|
|
|
|
|
var link = false;
|
|
|
|
|
switch (this.aircraftTrackingService) {
|
|
|
|
|
case 'flightaware':
|
|
|
|
|
link = 'https://flightaware.com/live/modes/' + state.icao;
|
|
|
|
|
if (state.identification) link += "/ident/" + state.identification
|
|
|
|
|
link += '/redirect';
|
|
|
|
|
break;
|
|
|
|
|
case 'planefinder':
|
|
|
|
|
if (state.identification) link = 'https://planefinder.net/flight/' + state.identification;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (link) {
|
|
|
|
|
return '<a target="_blank" href="' + link + '">' + text + '</a>';
|
|
|
|
|
}
|
|
|
|
|
return text;
|
|
|
|
|
|
|
|
|
|
};
|
2023-08-23 00:40:24 +02:00
|
|
|
|
2023-08-22 21:16:09 +02:00
|
|
|
$.fn.adsbMessagePanel = function () {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new AdsbMessagePanel(this));
|
|
|
|
|
}
|
|
|
|
|
return this.data('panel');
|
2023-09-01 23:37:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IsmMessagePanel = function(el) {
|
|
|
|
|
MessagePanel.call(this, el);
|
|
|
|
|
this.initClearTimer();
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
IsmMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
2023-09-01 23:37:40 +02:00
|
|
|
|
|
|
|
|
IsmMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message['mode'] === 'ISM';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IsmMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
|
|
|
|
'<th class="model">Model</th>' +
|
|
|
|
|
'<th class="id">ID</th>' +
|
|
|
|
|
'<th class="channel">Channel</th>' +
|
2023-09-01 23:53:31 +02:00
|
|
|
'<th class="data">Data</th>' +
|
2023-09-01 23:37:40 +02:00
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IsmMessagePanel.prototype.pushMessage = function(message) {
|
2023-09-02 00:23:25 +02:00
|
|
|
var $b = $(this.el).find('tbody');
|
2023-09-01 23:37:40 +02:00
|
|
|
|
|
|
|
|
var ifDefined = function(input, formatter) {
|
|
|
|
|
if (typeof(input) !== 'undefined') {
|
|
|
|
|
if (formatter) return formatter(input);
|
|
|
|
|
return input;
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mergeRemainingMessage = function(input, exclude) {
|
|
|
|
|
return Object.entries(input).map(function(entry) {
|
|
|
|
|
if (exclude.includes(entry[0])) return '';
|
|
|
|
|
return entry[0] + ': ' + entry[1] + ';';
|
|
|
|
|
}).join(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$b.append($(
|
|
|
|
|
'<tr>' +
|
|
|
|
|
'<td class="model">' + ifDefined(message.model) + '</td>' +
|
|
|
|
|
'<td class="id">' + ifDefined(message.id) + '</td>' +
|
|
|
|
|
'<td class="channel">' + ifDefined(message.channel) + '</td>' +
|
2023-09-01 23:53:31 +02:00
|
|
|
'<td class="data">' + this.htmlEscape(mergeRemainingMessage(message, ['model', 'id', 'channel', 'mode', 'time'])) + '</td>' +
|
2023-09-01 23:37:40 +02:00
|
|
|
'</tr>'
|
|
|
|
|
));
|
2023-09-02 00:23:25 +02:00
|
|
|
this.scrollToBottom();
|
2023-09-01 23:37:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.fn.ismMessagePanel = function() {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new IsmMessagePanel(this));
|
|
|
|
|
}
|
|
|
|
|
return this.data('panel');
|
2023-09-03 23:48:56 +02:00
|
|
|
};
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
AircraftMessagePanel = function(el) {
|
2023-09-03 23:48:56 +02:00
|
|
|
MessagePanel.call(this, el);
|
2023-09-07 02:47:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AircraftMessagePanel.prototype = Object.create(MessagePanel.prototype);
|
|
|
|
|
|
|
|
|
|
AircraftMessagePanel.prototype.renderAcars = function(acars) {
|
2023-09-08 15:38:48 +02:00
|
|
|
if (acars['more']) {
|
|
|
|
|
return '<h4>Partial ACARS message</h4>';
|
|
|
|
|
}
|
2023-09-07 02:47:09 +02:00
|
|
|
var details = '<h4>ACARS message</h4>';
|
|
|
|
|
if ('flight' in acars) {
|
2023-09-11 00:16:30 +02:00
|
|
|
details += '<div>Flight: ' + this.handleFlight(acars['flight']) + '</div>';
|
2023-09-07 02:47:09 +02:00
|
|
|
}
|
|
|
|
|
details += '<div>Registration: ' + acars['reg'].replace(/^\.+/g, '') + '</div>';
|
|
|
|
|
if ('media-adv' in acars) {
|
|
|
|
|
details += '<div>Media advisory</div>';
|
2023-09-10 18:41:21 +02:00
|
|
|
var mediaadv = acars['media-adv'];
|
|
|
|
|
if ('current_link' in mediaadv) {
|
|
|
|
|
details += '<div>Current link: ' + mediaadv['current_link']['descr'];
|
2023-09-07 02:47:09 +02:00
|
|
|
}
|
2023-09-10 18:41:21 +02:00
|
|
|
if ('links_avail' in mediaadv) {
|
|
|
|
|
details += '<div>Available links: ' + mediaadv['links_avail'].map(function (l) {
|
2023-09-07 02:47:09 +02:00
|
|
|
return l['descr'];
|
|
|
|
|
}).join(', ') + '</div>';
|
|
|
|
|
}
|
|
|
|
|
} else if ('arinc622' in acars) {
|
|
|
|
|
var arinc622 = acars['arinc622'];
|
|
|
|
|
if ('adsc' in arinc622) {
|
|
|
|
|
var adsc = arinc622['adsc'];
|
|
|
|
|
if ('tags' in adsc) {
|
|
|
|
|
adsc['tags'].forEach(function(tag) {
|
|
|
|
|
if ('basic_report' in tag) {
|
|
|
|
|
var basic_report = tag['basic_report'];
|
2023-09-11 00:16:30 +02:00
|
|
|
details += '<div>Basic ADS-C report</div>';
|
2023-09-07 02:47:09 +02:00
|
|
|
details += '<div>Position: ' + basic_report['lat'] + ', ' + basic_report['lon'] + '</div>';
|
|
|
|
|
details += '<div>Altitude: ' + basic_report['alt'] + '</div>';
|
2023-09-11 00:16:30 +02:00
|
|
|
} else if ('cancel_all_contracts' in tag) {
|
|
|
|
|
details += '<div>Cancel all ADS-C contracts</div>';
|
|
|
|
|
} else if ('cancel_contract' in tag) {
|
|
|
|
|
details += '<div>Cancel ADS-C contract</div>';
|
2023-09-07 02:47:09 +02:00
|
|
|
} else {
|
|
|
|
|
details += '<div>Unsupported tag</div>';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
details += '<div>Other ADS-C data</div>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// plain text
|
2023-09-08 15:38:48 +02:00
|
|
|
details += '<div>Label: ' + acars['label'] + '</div>';
|
2023-09-07 02:47:09 +02:00
|
|
|
details += '<div class="acars-message">' + acars['msg_text'] + '</div>';
|
|
|
|
|
}
|
|
|
|
|
return details;
|
2023-09-11 00:16:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AircraftMessagePanel.prototype.handleFlight = function(raw) {
|
|
|
|
|
return raw.replace(/^([0-9A-Z]{2})0*([0-9A-Z]+$)/, '$1$2');
|
|
|
|
|
};
|
2023-09-07 02:47:09 +02:00
|
|
|
|
|
|
|
|
HfdlMessagePanel = function(el) {
|
|
|
|
|
AircraftMessagePanel.call(this, el);
|
2023-09-03 23:48:56 +02:00
|
|
|
this.initClearTimer();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
HfdlMessagePanel.prototype = Object.create(AircraftMessagePanel.prototype);
|
2023-09-03 23:48:56 +02:00
|
|
|
|
|
|
|
|
HfdlMessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
2023-09-05 18:25:28 +02:00
|
|
|
'<th class="source">Source</th>' +
|
|
|
|
|
'<th class="destination">Destination</th>' +
|
|
|
|
|
'<th class="details">Details</th>' +
|
2023-09-03 23:48:56 +02:00
|
|
|
'</tr></thead>' +
|
|
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
HfdlMessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message['mode'] === 'HFDL';
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-11 00:16:30 +02:00
|
|
|
HfdlMessagePanel.prototype.renderPosition = function(hfnpdu) {
|
|
|
|
|
if ('pos' in hfnpdu) {
|
|
|
|
|
var pos = hfnpdu['pos'];
|
|
|
|
|
var lat = pos['lat'] || 180;
|
|
|
|
|
var lon = pos['lon'] || 180;
|
|
|
|
|
if (Math.abs(lat) <= 90 && Math.abs(lon) <= 180) {
|
|
|
|
|
return '<div>Position: ' + pos['lat'] + ', ' + pos['lon'] + '</div>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
HfdlMessagePanel.prototype.renderLogon = function(lpdu) {
|
|
|
|
|
var details = ''
|
|
|
|
|
if (lpdu['ac_info'] && lpdu['ac_info']['icao']) {
|
|
|
|
|
details += '<div>ICAO: ' + lpdu['ac_info']['icao'] + '</div>';
|
|
|
|
|
}
|
|
|
|
|
if (lpdu['hfnpdu']) {
|
|
|
|
|
var hfnpdu = lpdu['hfnpdu'];
|
|
|
|
|
if (hfnpdu['flight_id'] && hfnpdu['flight_id'] !== '') {
|
|
|
|
|
details += '<div>Flight: ' + this.handleFlight(lpdu['hfnpdu']['flight_id']) + '</div>'
|
|
|
|
|
}
|
|
|
|
|
details += this.renderPosition(hfnpdu);
|
|
|
|
|
}
|
|
|
|
|
return details;
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-03 23:48:56 +02:00
|
|
|
HfdlMessagePanel.prototype.pushMessage = function(message) {
|
2023-09-04 19:02:43 +02:00
|
|
|
var $b = $(this.el).find('tbody');
|
2023-09-05 18:25:28 +02:00
|
|
|
|
|
|
|
|
var src = '';
|
|
|
|
|
var dst = '';
|
|
|
|
|
var details = JSON.stringify(message);
|
|
|
|
|
|
|
|
|
|
var renderAddress = function(a) {
|
|
|
|
|
return a['id'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO remove safety net once parsing is complete
|
|
|
|
|
try {
|
|
|
|
|
var payload = message['hfdl'];
|
|
|
|
|
if ('spdu' in payload) {
|
|
|
|
|
var spdu = payload['spdu'];
|
|
|
|
|
src = renderAddress(spdu['src']);
|
|
|
|
|
details = '<h4>HFDL Squitter message</h4>'
|
|
|
|
|
details += '<div>Systable version: ' + spdu['systable_version'] + '</div>';
|
|
|
|
|
|
|
|
|
|
if ('gs_status' in spdu) {
|
|
|
|
|
details += spdu['gs_status'].map(function(gs){
|
|
|
|
|
return '<div>Ground station ' + gs['gs']['id'] + ' is operating on frequency ids ' + gs['freqs'].map(function(f) {return f['id']; }).join(', ') + '</div>';
|
|
|
|
|
}).join('')
|
|
|
|
|
}
|
|
|
|
|
} else if ('lpdu' in payload) {
|
|
|
|
|
var lpdu = payload['lpdu'];
|
|
|
|
|
src = renderAddress(lpdu['src']);
|
|
|
|
|
dst = renderAddress(lpdu['dst']);
|
|
|
|
|
if (lpdu['type']['id'] === 13 || lpdu['type']['id'] === 29) {
|
|
|
|
|
// unnumbered data
|
|
|
|
|
var hfnpdu = lpdu['hfnpdu'];
|
|
|
|
|
if (hfnpdu['type']['id'] === 209) {
|
|
|
|
|
// performance data
|
|
|
|
|
details = '<h4>Performance data</h4>';
|
2023-09-11 00:16:30 +02:00
|
|
|
details += '<div>Flight: ' + this.handleFlight(hfnpdu['flight_id']) + '</div>';
|
|
|
|
|
details += this.renderPosition(hfnpdu);
|
2023-09-05 18:25:28 +02:00
|
|
|
} else if (hfnpdu['type']['id'] === 255) {
|
|
|
|
|
// enveloped data
|
|
|
|
|
if ('acars' in hfnpdu) {
|
2023-09-07 02:47:09 +02:00
|
|
|
details = this.renderAcars(hfnpdu['acars']);
|
2023-09-05 18:25:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (lpdu['type']['id'] === 47) {
|
|
|
|
|
// logon denied
|
|
|
|
|
details = '<h4>Logon denied</h4>';
|
|
|
|
|
} else if (lpdu['type']['id'] === 63) {
|
|
|
|
|
details = '<h4>Logoff request</h4>';
|
|
|
|
|
if (lpdu['ac_info'] && lpdu['ac_info']['icao']) {
|
2023-09-10 21:00:45 +02:00
|
|
|
details += '<div>ICAO: ' + lpdu['ac_info']['icao'] + '</div>';
|
2023-09-05 18:25:28 +02:00
|
|
|
}
|
|
|
|
|
} else if (lpdu['type']['id'] === 79) {
|
|
|
|
|
details = '<h4>Logon resume</h4>';
|
2023-09-11 00:16:30 +02:00
|
|
|
details += this.renderLogon(lpdu);
|
2023-09-05 18:25:28 +02:00
|
|
|
} else if (lpdu['type']['id'] === 95) {
|
|
|
|
|
details = '<h4>Logon resume confirmation</h4>';
|
|
|
|
|
} else if (lpdu['type']['id'] === 143) {
|
|
|
|
|
details = '<h4>Logon request</h4>';
|
2023-09-11 00:16:30 +02:00
|
|
|
details += this.renderLogon(lpdu);
|
2023-09-05 18:25:28 +02:00
|
|
|
} else if (lpdu['type']['id'] === 159) {
|
|
|
|
|
details = '<h4>Logon confirmation</h4>';
|
|
|
|
|
if (lpdu['ac_info'] && lpdu['ac_info']['icao']) {
|
2023-09-10 21:00:45 +02:00
|
|
|
details += '<div>ICAO: ' + lpdu['ac_info']['icao'] + '</div>';
|
2023-09-05 18:25:28 +02:00
|
|
|
}
|
|
|
|
|
if (lpdu['assigned_ac_id']) {
|
|
|
|
|
details += '<div>Assigned aircraft ID: ' + lpdu['assigned_ac_id'] + '</div>';
|
|
|
|
|
}
|
|
|
|
|
} else if (lpdu['type']['id'] === 191) {
|
|
|
|
|
details = '<h4>Logon request (DLS)</h4>';
|
2023-09-11 00:16:30 +02:00
|
|
|
details += this.renderLogon(lpdu);
|
2023-09-05 18:25:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e, e.stack);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 19:02:43 +02:00
|
|
|
$b.append($(
|
|
|
|
|
'<tr>' +
|
2023-09-05 18:25:28 +02:00
|
|
|
'<td class="source">' + src + '</td>' +
|
|
|
|
|
'<td class="destination">' + dst + '</td>' +
|
|
|
|
|
'<td class="details">' + details + '</td>' +
|
2023-09-04 19:02:43 +02:00
|
|
|
'</tr>'
|
|
|
|
|
));
|
2023-09-05 18:25:28 +02:00
|
|
|
this.scrollToBottom();
|
2023-09-03 23:48:56 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.fn.hfdlMessagePanel = function() {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new HfdlMessagePanel(this));
|
|
|
|
|
}
|
|
|
|
|
return this.data('panel');
|
2023-09-04 19:02:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vdl2MessagePanel = function(el) {
|
2023-09-07 02:47:09 +02:00
|
|
|
AircraftMessagePanel.call(this, el);
|
2023-09-04 19:02:43 +02:00
|
|
|
this.initClearTimer();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 02:47:09 +02:00
|
|
|
Vdl2MessagePanel.prototype = Object.create(AircraftMessagePanel.prototype);
|
2023-09-04 19:02:43 +02:00
|
|
|
|
|
|
|
|
Vdl2MessagePanel.prototype.render = function() {
|
|
|
|
|
$(this.el).append($(
|
|
|
|
|
'<table>' +
|
|
|
|
|
'<thead><tr>' +
|
2023-09-07 02:47:09 +02:00
|
|
|
'<th class="source">Source</th>' +
|
|
|
|
|
'<th class="destination">Destination</th>' +
|
|
|
|
|
'<th class="details">Details</th>' +
|
|
|
|
|
'</tr></thead>' +
|
2023-09-04 19:02:43 +02:00
|
|
|
'<tbody></tbody>' +
|
|
|
|
|
'</table>'
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vdl2MessagePanel.prototype.supportsMessage = function(message) {
|
|
|
|
|
return message['mode'] === 'VDL2';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vdl2MessagePanel.prototype.pushMessage = function(message) {
|
|
|
|
|
var $b = $(this.el).find('tbody');
|
2023-09-07 02:47:09 +02:00
|
|
|
var src = '';
|
|
|
|
|
var dst = '';
|
|
|
|
|
var details = JSON.stringify(message);
|
|
|
|
|
|
|
|
|
|
var renderAddress = function(a) {
|
|
|
|
|
return '<div>' + a['addr'] + '</div><div>' + a['type'] + ( 'status' in a ? ' (' + a['status'] + ')' : '' ) + '</div>'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO remove safety net once parsing is complete
|
|
|
|
|
try {
|
|
|
|
|
var payload = message['vdl2'];
|
|
|
|
|
if ('avlc' in payload) {
|
|
|
|
|
var avlc = payload['avlc'];
|
|
|
|
|
src = renderAddress(avlc['src']);
|
|
|
|
|
dst = renderAddress(avlc['dst']);
|
|
|
|
|
|
|
|
|
|
if (avlc['frame_type'] === 'S') {
|
|
|
|
|
details = '<h4>Supervisory frame</h4>';
|
|
|
|
|
if (avlc['cmd'] === 'Receive Ready') {
|
|
|
|
|
details = '<h4>Receive Ready</h4>';
|
|
|
|
|
}
|
|
|
|
|
} else if (avlc['frame_type'] === 'I') {
|
|
|
|
|
details = '<h4>Information frame</h4>';
|
|
|
|
|
if ('acars' in avlc) {
|
|
|
|
|
details = this.renderAcars(avlc['acars']);
|
|
|
|
|
} else if ('x25' in avlc) {
|
|
|
|
|
var x25 = avlc['x25'];
|
|
|
|
|
if (!('reasm_status' in x25) || ['skipped', 'complete'].includes(x25['reasm_status'])) {
|
|
|
|
|
details = '<h4>X.25 frame</h4>';
|
|
|
|
|
if ('clnp' in x25) {
|
|
|
|
|
var clnp = x25['clnp']
|
|
|
|
|
if ('cotp' in clnp) {
|
|
|
|
|
var cotp = clnp['cotp'];
|
|
|
|
|
if ('cpdlc' in cotp) {
|
|
|
|
|
var cpdlc = cotp['cpdlc'];
|
|
|
|
|
details = '<h4>CPDLC</h4>';
|
|
|
|
|
if ('atc_downlink_message' in cpdlc) {
|
|
|
|
|
var atc_downlink_message = cpdlc['atc_downlink_message'];
|
|
|
|
|
if ('msg_data' in atc_downlink_message) {
|
|
|
|
|
var msg_data = atc_downlink_message['msg_data'];
|
|
|
|
|
if ('msg_elements' in msg_data) {
|
|
|
|
|
details += '<div>' + msg_data['msg_elements'].map(function(e) { return e['msg_element']['choice_label']; }).join(', ') + '</div>';
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
details += '<div>' + JSON.stringify(cpdlc) + '</div>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
details = '<h4>Partial X.25 frame</h4>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (avlc['frame_type'] === 'U') {
|
|
|
|
|
details = '<h4>Unnumbered frame</h4>';
|
|
|
|
|
if ('xid' in avlc) {
|
|
|
|
|
var xid = avlc['xid'];
|
|
|
|
|
details = '<h4>' + xid['type_descr'] + '</h4>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e, e.stack);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 19:02:43 +02:00
|
|
|
$b.append($(
|
|
|
|
|
'<tr>' +
|
2023-09-07 02:47:09 +02:00
|
|
|
'<td class="source">' + src + '</td>' +
|
|
|
|
|
'<td class="destination">' + dst + '</td>' +
|
|
|
|
|
'<td class="details">' + details + '</td>' +
|
2023-09-04 19:02:43 +02:00
|
|
|
'</tr>'
|
|
|
|
|
));
|
|
|
|
|
this.scrollToBottom();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.fn.vdl2MessagePanel = function() {
|
|
|
|
|
if (!this.data('panel')) {
|
|
|
|
|
this.data('panel', new Vdl2MessagePanel(this));
|
|
|
|
|
}
|
|
|
|
|
return this.data('panel');
|
2020-12-09 19:26:34 +01:00
|
|
|
};
|