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.
404 lines
8.8 KiB
404 lines
8.8 KiB
6 years ago
|
import React, { Component } from 'react';
|
||
|
import './Timeline.scss';
|
||
|
import axios from 'axios';
|
||
|
import Event from './Event';
|
||
|
import EventNode from './Components/Timeline/EventNode';
|
||
|
import DateNode from './Components/Timeline/DateNode';
|
||
|
import EndNode from './Components/Timeline/EndNode';
|
||
|
import EventMessages from './Components/Timeline/EventMessages';
|
||
|
import moment from 'moment';
|
||
|
import WebSocketStatus from './Components/WebSocketStatus';
|
||
|
|
||
|
class Timeline extends Component {
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
|
||
|
var host = window.location.protocol + '//' + window.location.host;
|
||
|
|
||
|
if (process.env.NODE_ENV === 'development') {
|
||
|
host = 'https://10.0.0.1:8002';
|
||
|
}
|
||
|
|
||
|
this.state = {
|
||
|
events: [],
|
||
|
loaded: false,
|
||
|
error: false,
|
||
|
wsIsOpen: false,
|
||
|
wsIsAuthenticated: false,
|
||
|
wsIsRecovering: false,
|
||
|
wsURI: null,
|
||
|
wsAuthKey: null,
|
||
|
host: host
|
||
|
};
|
||
|
|
||
|
this.wsSocket = null;
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
setInterval(function() {
|
||
|
self.recover();
|
||
|
}, 5000);
|
||
|
}
|
||
|
|
||
|
componentDidMount() {
|
||
|
this.fetchEventList();
|
||
|
this.initUserNotification();
|
||
|
}
|
||
|
|
||
|
initUserNotification() {
|
||
|
if(!('Notification' in window))
|
||
|
return;
|
||
|
|
||
|
// Not yet approved?
|
||
|
if (Notification.permission === 'default') {
|
||
|
|
||
|
// Request permission
|
||
|
return Notification.requestPermission(function() {
|
||
|
//console.log("Got permission!");
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
showUserNotification(event) {
|
||
|
|
||
|
if(!('Notification' in window))
|
||
|
return;
|
||
|
|
||
|
if(Notification.permission !== "granted")
|
||
|
return;
|
||
|
|
||
|
// define new notification
|
||
|
var n = new Notification(
|
||
|
event.getSubtitle(),
|
||
|
{
|
||
|
'body': event.getTitle(),
|
||
|
'tag' : "event-" + event.id
|
||
|
}
|
||
|
);
|
||
|
|
||
|
// notify when shown successfull
|
||
|
n.onshow = function () {
|
||
|
console.log("onshow");
|
||
|
};
|
||
|
|
||
|
// remove the notification from Notification Center when clicked.
|
||
|
n.onclick = function () {
|
||
|
this.close();
|
||
|
console.log("onclick");
|
||
|
};
|
||
|
|
||
|
// callback function when the notification is closed.
|
||
|
n.onclose = function () {
|
||
|
console.log("onclose");
|
||
|
};
|
||
|
|
||
|
// notification cannot be presented to the user, this event is fired if the permission level is set to denied or default.
|
||
|
n.onerror = function () {
|
||
|
console.log("onerror");
|
||
|
};
|
||
|
}
|
||
|
|
||
|
recover() {
|
||
|
var self = this;
|
||
|
|
||
|
if(!self.state.wsIsOpen) {
|
||
|
self.initWebsocketConnection(self.state.wsURI);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!self.state.wsIsAuthenticated) {
|
||
|
self.authenticateWebsocketConnection();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
fetchEventList() {
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
axios.get(this.state.host + '/api/status')
|
||
|
.then(function(res) {
|
||
|
const events = res.data.events.map(obj => new Event(obj));
|
||
|
|
||
|
const wsURI = res.data['wss-uri'] === undefined ? null : res.data['wss-uri'];
|
||
|
|
||
|
self.setState({
|
||
|
events: events,
|
||
|
loaded: true,
|
||
|
error: false,
|
||
|
wsURI: wsURI,
|
||
|
wsAuthKey: res.data['auth-key']
|
||
|
});
|
||
|
|
||
|
// Once we get to know the web socket port, we can make the web socket connection
|
||
|
if(wsURI && !self.state.wsIsRecovering) {
|
||
|
self.initWebsocketConnection(res.data['wss-uri']);
|
||
|
}
|
||
|
|
||
|
})
|
||
|
.catch(err => {
|
||
|
console.warn(err);
|
||
|
this.setState({
|
||
|
loaded: true,
|
||
|
error: true
|
||
|
});
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
addOrUpdateEvent(event) {
|
||
|
|
||
|
this.setState((prevState, props) => {
|
||
|
|
||
|
var newEvents = [];
|
||
|
var inserted = false;
|
||
|
|
||
|
for(var key in prevState.events) {
|
||
|
if(prevState.hasOwnProperty(key))
|
||
|
continue;
|
||
|
var curEvent = prevState.events[key];
|
||
|
|
||
|
if(curEvent.id === event.id) {
|
||
|
newEvents.push(event);
|
||
|
inserted = true;
|
||
|
} else {
|
||
|
newEvents.push(curEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!inserted) {
|
||
|
newEvents.push(event);
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
events: newEvents
|
||
|
}
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
getEventWithId(id) {
|
||
|
var self = this;
|
||
|
|
||
|
for(var key in self.state.events) {
|
||
|
|
||
|
if(self.state.hasOwnProperty(key))
|
||
|
continue;
|
||
|
|
||
|
var event = self.state.events[key];
|
||
|
|
||
|
if(event.id === id)
|
||
|
return event;
|
||
|
|
||
|
}
|
||
|
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
authenticateWebsocketConnection(authKey) {
|
||
|
var self = this;
|
||
|
|
||
|
// Authenticate
|
||
|
self.wsSocket.send(JSON.stringify({
|
||
|
"type": "authenticate",
|
||
|
"auth-key": self.state.wsAuthKey
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
handleJSONMessage(data) {
|
||
|
var event;
|
||
|
var self = this;
|
||
|
|
||
|
// Auth key was invalid, maybe the server restarted
|
||
|
if(data.type === "bad-auth-key") {
|
||
|
|
||
|
self.setState({
|
||
|
wsIsAuthenticated: false,
|
||
|
wsIsRecovering: true
|
||
|
});
|
||
|
|
||
|
// Get a fresh auth key
|
||
|
self.fetchEventList();
|
||
|
|
||
|
} else if(data.type === "authenticated") {
|
||
|
|
||
|
if(self.state.wsIsRecovering) {
|
||
|
self.fetchEventList();
|
||
|
}
|
||
|
|
||
|
self.setState({
|
||
|
wsIsAuthenticated: true,
|
||
|
wsIsRecovering: false
|
||
|
});
|
||
|
|
||
|
} else if(data.type === "new-event") {
|
||
|
|
||
|
event = new Event(data.event);
|
||
|
this.addOrUpdateEvent(event);
|
||
|
|
||
|
} else if(data.type === "event-updated") {
|
||
|
|
||
|
event = new Event(data.event);
|
||
|
this.addOrUpdateEvent(event);
|
||
|
|
||
|
} else if(data.type === "event-success") {
|
||
|
|
||
|
event = this.getEventWithId(data.id);
|
||
|
|
||
|
if(event && event.type === "WebhookAction") {
|
||
|
this.showUserNotification(event);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
console.log("Unknown event: " + data.type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
initWebsocketConnection(uri) {
|
||
|
var self = this;
|
||
|
|
||
|
if(!uri)
|
||
|
return;
|
||
|
|
||
|
self.wsSocket = new WebSocket(uri);
|
||
|
self.wsSocket.binaryType = "arraybuffer";
|
||
|
self.wsSocket.onopen = function() {
|
||
|
|
||
|
self.setState({
|
||
|
wsIsOpen: true,
|
||
|
});
|
||
|
|
||
|
self.authenticateWebsocketConnection();
|
||
|
|
||
|
};
|
||
|
|
||
|
self.wsSocket.onmessage = (e) => {
|
||
|
if (typeof e.data === "string") {
|
||
|
try {
|
||
|
var data = JSON.parse(e.data);
|
||
|
self.handleJSONMessage(data);
|
||
|
} catch(e) {
|
||
|
console.error(e);
|
||
|
}
|
||
|
} else {
|
||
|
var arr = new Uint8Array(e.data);
|
||
|
var hex = '';
|
||
|
for (var i = 0; i < arr.length; i++) {
|
||
|
hex += ('00' + arr[i].toString(16)).substr(-2);
|
||
|
}
|
||
|
console.log("Binary message received: " + hex);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
self.wsSocket.onclose = function() {
|
||
|
|
||
|
self.wsSocket.close();
|
||
|
self.wsSocket = null;
|
||
|
|
||
|
self.setState({
|
||
|
wsIsOpen: false,
|
||
|
wsIsRecovering: true
|
||
|
});
|
||
|
|
||
|
if(self.wsReconnectTimeout !== undefined) {
|
||
|
clearTimeout(self.wsReconnectTimeout);
|
||
|
}
|
||
|
|
||
|
// Try to reconnect again after 2 seconds
|
||
|
self.wsReconnectTimeout = setTimeout(function() {
|
||
|
|
||
|
self.initWebsocketConnection(uri);
|
||
|
self.wsReconnectTimeout = undefined;
|
||
|
}, 2000);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
function sendText() {
|
||
|
if (isopen) {
|
||
|
socket.send("Hello, world!");
|
||
|
console.log("Text message sent.");
|
||
|
} else {
|
||
|
console.log("Connection not opened.")
|
||
|
}
|
||
|
};
|
||
|
function sendBinary() {
|
||
|
if (isopen) {
|
||
|
var buf = new ArrayBuffer(32);
|
||
|
var arr = new Uint8Array(buf);
|
||
|
for (i = 0; i < arr.length; ++i) arr[i] = i;
|
||
|
socket.send(buf);
|
||
|
console.log("Binary message sent.");
|
||
|
} else {
|
||
|
console.log("Connection not opened.")
|
||
|
}
|
||
|
};
|
||
|
*/
|
||
|
|
||
|
getDate(timestamp) {
|
||
|
return moment.unix(timestamp).format("YYYY-MM-DD");
|
||
|
}
|
||
|
|
||
|
getTimelineObjects() {
|
||
|
var rows = [];
|
||
|
var last_date = '';
|
||
|
var events = this.state.events;
|
||
|
|
||
|
rows.push(<EndNode key="now" />);
|
||
|
|
||
|
for (var i=events.length-1; i >= 0; i--) {
|
||
|
var event = events[i];
|
||
|
|
||
|
rows.push(<EventNode event={event} key={i} alignment={i%2===0 ? 'left' : 'right'} >
|
||
|
<EventMessages event={event} />
|
||
|
</EventNode>);
|
||
|
|
||
|
var cur_date = this.getDate(event.timestamp);
|
||
|
var next_date = "";
|
||
|
if(i > 0)
|
||
|
next_date = this.getDate(events[i-1].timestamp);
|
||
|
|
||
|
if(next_date === cur_date)
|
||
|
continue;
|
||
|
|
||
|
if(cur_date !== last_date) {
|
||
|
rows.push(<DateNode date={cur_date} key={cur_date} first={i===0} />);
|
||
|
last_date = cur_date;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rows;
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
|
||
|
if(!this.state.loaded) {
|
||
|
return (
|
||
|
<div className="Timeline">
|
||
|
<div className="status-message">Connecting to {this.state.host}..</div>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if(this.state.error) {
|
||
|
return (
|
||
|
<div className="Timeline">
|
||
|
<div className="status-message">Unable to connect to {this.state.host}</div>
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div className="Timeline">
|
||
|
<div className="primary-view">
|
||
|
{this.getTimelineObjects()}
|
||
|
</div>
|
||
|
<WebSocketStatus wsIsOpen={this.state.wsIsOpen} wsIsRecovering={this.state.wsIsRecovering} wsURI={this.state.wsURI} wsIsAuthenticated={this.state.wsIsAuthenticated} />
|
||
|
</div>
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default Timeline;
|