summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gitautodeploy/events.py35
-rw-r--r--gitautodeploy/gitautodeploy.py26
-rw-r--r--gitautodeploy/httpserver.py11
-rw-r--r--webui/src/Components/Timeline/EventNode.js82
-rw-r--r--webui/src/Event.js88
-rw-r--r--webui/src/Timeline.js154
6 files changed, 256 insertions, 140 deletions
diff --git a/gitautodeploy/events.py b/gitautodeploy/events.py
index 8bbaaa1..5e177a0 100644
--- a/gitautodeploy/events.py
+++ b/gitautodeploy/events.py
@@ -33,10 +33,7 @@ class SystemEvent(object):
def register_message(self, message, level="INFO"):
self.messages.append(message)
- self.hub.update_action(self, message)
-
- def notify(self):
- self.hub.notify(self)
+ self.hub.notify_observers(type="event-updated", event=self.dict_repr())
def set_id(self, id):
self.id = id
@@ -46,9 +43,12 @@ class SystemEvent(object):
def set_waiting(self, value):
self.waiting = value
+ self.hub.notify_observers(type="event-updated", event=self.dict_repr())
def set_success(self, value):
self.success = value
+ self.hub.notify_observers(type="event-updated", event=self.dict_repr())
+ self.hub.notify_observers(type="event-success", id=self.id, success=value)
def log_debug(self, message):
self.logger.debug(message)
@@ -70,8 +70,8 @@ class SystemEvent(object):
self.logger.critical(message)
self.register_message(message, "CRITICAL")
- def update(self):
- self.hub.update_action(self)
+ #def update(self):
+ # self.hub.notify_observers(type="event-updated", event=self.dict_repr())
class WebhookAction(SystemEvent):
@@ -119,6 +119,19 @@ class StartupEvent(SystemEvent):
data['ws-started'] = self.ws_started
return data
+ def set_http_started(self, value):
+ self.http_started = value
+ self.hub.notify_observers(type="event-updated", event=self.dict_repr())
+ self.validate_success()
+
+ def set_ws_started(self, value):
+ self.ws_started = value
+ self.hub.notify_observers(type="event-updated", event=self.dict_repr())
+ self.validate_success()
+
+ def validate_success(self):
+ if self.http_started and self.ws_started:
+ self.set_success(True)
class EventStore(object):
@@ -134,7 +147,7 @@ class EventStore(object):
if observer in self.observers:
self.observers.remove(observer)
- def update_observers(self, *args, **kwargs):
+ def notify_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)
@@ -143,18 +156,12 @@ class EventStore(object):
event.register_hub(self)
self.next_id = self.next_id + 1
self.actions.append(event)
- self.update_observers(event=event)
+ self.notify_observers(type="new-event", event=event.dict_repr())
# Store max 100 actions
if len(self.actions) > 100:
self.actions.pop(0)
- def notify(self, event):
- self.update_observers(event=event)
-
- def update_action(self, event, message=None):
- self.update_observers(event=event, message=message)
-
def dict_repr(self):
action_repr = []
for action in self.actions:
diff --git a/gitautodeploy/gitautodeploy.py b/gitautodeploy/gitautodeploy.py
index 784f662..5484019 100644
--- a/gitautodeploy/gitautodeploy.py
+++ b/gitautodeploy/gitautodeploy.py
@@ -158,11 +158,11 @@ class GitAutoDeploy(object):
def update(self, *args, **kwargs):
import json
- message = {
- 'type': 'unknown'
- }
+ #message = {
+ # 'type': 'unknown'
+ #}
- if 'event' in kwargs:
+ #if 'event' in kwargs:
#if 'message' in kwargs:
# message = {
# 'type': 'event-message',
@@ -170,13 +170,13 @@ class GitAutoDeploy(object):
# 'message': kwargs['message']
# }
#else:
- message = {
- 'type': 'event-update',
- 'event-id': kwargs['event'].id,
- 'event': kwargs['event'].dict_repr()
- }
+ #message = {
+ # 'type': 'event-update',
+ # 'event-id': kwargs['event'].id,
+ # 'event': kwargs['event'].dict_repr()
+ #}
- data = json.dumps(message).encode('utf-8')
+ data = json.dumps(kwargs).encode('utf-8')
for client in self._ws_clients:
client.sendMessage(data)
@@ -300,8 +300,7 @@ class GitAutoDeploy(object):
self._startup_event.log_info("Listening for http connections on %s port %s" % (sa[0], sa[1]))
self._startup_event.http_address = sa[0]
self._startup_event.http_port = sa[1]
- self._startup_event.http_started = True
- self._startup_event.notify()
+ self._startup_event.set_http_started(True)
except socket.error as e:
self._startup_event.log_critical("Error on socket: %s" % e)
@@ -352,8 +351,7 @@ class GitAutoDeploy(object):
self._startup_event.log_info("Listening for web socket connections on %s port %s" % (self._config['web-ui']['ws-host'], self._config['web-ui']['ws-port']))
self._startup_event.ws_address = self._config['web-ui']['ws-host']
self._startup_event.ws_port = self._config['web-ui']['ws-port']
- self._startup_event.ws_started = True
- self._startup_event.notify()
+ self._startup_event.set_ws_started(True)
# Serve forever (until reactor.stop())
reactor.run(installSignalHandlers=False)
diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py
index 1ed03a8..b5cd801 100644
--- a/gitautodeploy/httpserver.py
+++ b/gitautodeploy/httpserver.py
@@ -146,9 +146,8 @@ class WebbhookRequestProcessor(object):
result.append(repo_result)
action.log_info("Deploy commands were executed")
- action.set_success(True)
action.set_waiting(False)
- action.update()
+ action.set_success(True)
return result
@@ -321,8 +320,8 @@ def WebhookRequestHandlerFactory(config, event_store):
request_headers = dict((k.lower(), v) for k, v in request_headers.items())
action = WebhookAction(self.client_address, request_headers, request_body)
- action.set_waiting(True)
event_store.register_action(action)
+ action.set_waiting(True)
action.log_info('Incoming request from %s:%s' % (self.client_address[0], self.client_address[1]))
@@ -400,9 +399,8 @@ def WebhookRequestHandlerFactory(config, event_store):
self.send_error(400, 'Unprocessable request')
action.log_warning('Unable to process incoming request from %s:%s' % (self.client_address[0], self.client_address[1]))
test_case['expected']['status'] = 400
- action.set_success(False)
action.set_waiting(False)
- action.update()
+ action.set_success(False)
return
except Exception as e:
@@ -412,9 +410,8 @@ def WebhookRequestHandlerFactory(config, event_store):
test_case['expected']['status'] = 500
action.log_warning("Unable to process request")
- action.set_success(False)
action.set_waiting(False)
- action.update()
+ action.set_success(False)
raise e
diff --git a/webui/src/Components/Timeline/EventNode.js b/webui/src/Components/Timeline/EventNode.js
index 9e7bebc..74022a7 100644
--- a/webui/src/Components/Timeline/EventNode.js
+++ b/webui/src/Components/Timeline/EventNode.js
@@ -1,103 +1,31 @@
import React, { Component } from 'react';
import './EventNode.scss';
-import moment from 'moment';
class EventNode extends Component {
- getColorClass() {
-
- if(this.props.event.type === "StartupEvent")
- return "green";
-
- if(this.props.event.type === "WebhookAction")
- return "blue";
-
- return "purple";
- }
-
- getTitle() {
-
- if(this.props.event.type === "StartupEvent")
- return "Startup";
-
- if(this.props.event.type === "WebhookAction")
- return "Webhook";
-
- return this.props.event.type;
- }
-
- getSubtitle() {
-
- if(this.props.event.type === "StartupEvent") {
-
- if(this.isWaiting())
- return "Starting up.."
-
- return "Listening for incoming connections";
- }
-
- if(this.props.event.type === "WebhookAction") {
- if(this.props.event.messages.length)
- return this.props.event.messages[this.props.event.messages.length - 1]
- return "Incoming request from " + this.props.event['client-address'];
- }
-
- return this.props.event.type;
- }
-
- getDate() {
- return moment.unix(this.props.event.timestamp).format("YYYY-MM-DD");
- }
-
- getTime() {
- return moment.unix(this.props.event.timestamp).format("HH:mm");
- }
-
- getIconName() {
-
- if(this.props.event.success === false)
- return "alert"
-
- if(this.props.event.type === "StartupEvent")
- return "alert-circle";
-
- return "check";
- }
-
- isWaiting() {
- if(this.props.event.type === "StartupEvent") {
- if(this.props.event['ws-started'] !== true || this.props.event['http-started'] !== true) {
- return true;
- }
- } else if(this.props.event.waiting === true) {
- return true;
- }
- return false;
- }
-
getIconElement() {
- if(this.isWaiting()) {
+ if(this.props.event.isWaiting()) {
return (
<div className="icon spinner"></div>
);
}
return (
- <i className={"icon mdi mdi-" + this.getIconName()} />
+ <i className={"icon mdi mdi-" + this.props.event.getIconName()} />
);
}
render() {
return (
- <div className={"EventNode " + this.props.alignment + " " + this.getColorClass()}>
+ <div className={"EventNode " + this.props.alignment + " " + this.props.event.getColorClass()}>
<span className="horizontal-line"></span>
<span className="timeline-icon"></span>
<div className="inner">
<div className="header">
{this.getIconElement()}
- <p className="title">{this.getTitle()}</p>
- <p className="subtitle">{this.getSubtitle()}</p>
+ <p className="title">{this.props.event.getTitle()}</p>
+ <p className="subtitle">{this.props.event.getSubtitle()}</p>
</div>
<div className="body">
{this.props.children}
diff --git a/webui/src/Event.js b/webui/src/Event.js
new file mode 100644
index 0000000..f74908d
--- /dev/null
+++ b/webui/src/Event.js
@@ -0,0 +1,88 @@
+import moment from 'moment';
+
+class Event {
+
+ constructor(event) {
+ var self = this;
+ self.event = event;
+
+ for(var key in event) {
+ if(!event.hasOwnProperty(key))
+ continue;
+ self[key] = event[key];
+ }
+ }
+
+ getColorClass() {
+
+ if(this.event.type === "StartupEvent")
+ return "green";
+
+ if(this.event.type === "WebhookAction")
+ return "blue";
+
+ return "purple";
+ }
+
+ getTitle() {
+
+ if(this.event.type === "StartupEvent")
+ return "Startup";
+
+ if(this.event.type === "WebhookAction")
+ return "Webhook";
+
+ return this.event.type;
+ }
+
+ getSubtitle() {
+
+ if(this.event.type === "StartupEvent") {
+
+ if(this.isWaiting())
+ return "Starting up.."
+
+ return "Listening for incoming connections";
+ }
+
+ if(this.event.type === "WebhookAction") {
+ if(this.event.messages.length)
+ return this.event.messages[this.event.messages.length - 1]
+ return "Incoming request from " + this.event['client-address'];
+ }
+
+ return this.event.type;
+ }
+
+ getDate() {
+ return moment.unix(this.event.timestamp).format("YYYY-MM-DD");
+ }
+
+ getTime() {
+ return moment.unix(this.event.timestamp).format("HH:mm");
+ }
+
+ getIconName() {
+
+ if(this.event.success === false)
+ return "alert"
+
+ if(this.event.type === "StartupEvent")
+ return "alert-circle";
+
+ return "check";
+ }
+
+ isWaiting() {
+ if(this.event.type === "StartupEvent") {
+ if(this.event['ws-started'] !== true || this.event['http-started'] !== true) {
+ return true;
+ }
+ } else if(this.event.waiting === true) {
+ return true;
+ }
+ return false;
+ }
+}
+
+export default Event;
diff --git a/webui/src/Timeline.js b/webui/src/Timeline.js
index bf35994..6e97842 100644
--- a/webui/src/Timeline.js
+++ b/webui/src/Timeline.js
@@ -1,6 +1,7 @@
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';
@@ -23,6 +24,61 @@ class Timeline extends Component {
componentDidMount() {
this.fetchEventList();
this.initWebsocketConnection();
+ 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");
+ };
+
}
fetchEventList() {
@@ -41,7 +97,7 @@ class Timeline extends Component {
{
//obj.key = obj.id;
//console.log(obj);
- return obj;
+ return new Event(obj);
}
);
this.setState({ events: events, loaded: true });
@@ -52,48 +108,90 @@ class Timeline extends Component {
}
+ 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;
+ }
+
handleJSONMessage(data) {
- if(data.type === "event-update") {
+ var event;
- this.setState((prevState, props) => {
+ if(data.type === "new-event") {
- var newEvents = [];
- var inserted = false;
+ event = new Event(data.event);
+ this.addOrUpdateEvent(event);
- for(var key in prevState.events) {
- if(prevState.hasOwnProperty(key))
- continue;
- var event = prevState.events[key];
+ } else if(data.type === "event-updated") {
- if(event.id === data.event.id) {
- newEvents.push(data.event);
- inserted = true;
- } else {
- newEvents.push(event);
- }
- }
+ event = new Event(data.event);
+ this.addOrUpdateEvent(event);
- if(!inserted) {
- newEvents.push(data.event);
- }
+ } else if(data.type === "event-success") {
- return {
- events: newEvents
- }
-
- });
+ event = this.getEventWithId(data.id);
+
+ if(event && event.type === "WebhookAction") {
+
+ this.showUserNotification(event);
+
+ }
} else {
- console.log("Unknown event");
- console.log(data);
+
+ console.log("Unknown event: " + data.type);
+
}
}
initWebsocketConnection() {
var self = this;
- var scheme = window.location.protocol == "https" ? "wss" : "ws";
+ var scheme = window.location.protocol === "https" ? "wss" : "ws";
var uri = scheme + "://" + window.location.hostname + ":9000";
if (process.env.NODE_ENV === "development") {
@@ -104,7 +202,7 @@ class Timeline extends Component {
this.wsSocket.binaryType = "arraybuffer";
this.wsSocket.onopen = function() {
- console.log("Connected!");
+ //console.log("Connected!");
this.wsIsOpen = true;
}