diff options
-rw-r--r-- | gitautodeploy/events.py | 35 | ||||
-rw-r--r-- | gitautodeploy/gitautodeploy.py | 26 | ||||
-rw-r--r-- | gitautodeploy/httpserver.py | 11 | ||||
-rw-r--r-- | webui/src/Components/Timeline/EventNode.js | 82 | ||||
-rw-r--r-- | webui/src/Event.js | 88 | ||||
-rw-r--r-- | webui/src/Timeline.js | 154 |
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; } |