diff --git a/app/assets/images/kuroko2/kuroko-logo-error.png b/app/assets/images/kuroko2/kuroko-logo-error.png new file mode 100644 index 00000000..fb9830e6 Binary files /dev/null and b/app/assets/images/kuroko2/kuroko-logo-error.png differ diff --git a/app/assets/images/kuroko2/kuroko-logo-success.png b/app/assets/images/kuroko2/kuroko-logo-success.png new file mode 100644 index 00000000..04ba8b25 Binary files /dev/null and b/app/assets/images/kuroko2/kuroko-logo-success.png differ diff --git a/app/assets/javascripts/kuroko2/application.js b/app/assets/javascripts/kuroko2/application.js index a428e1a8..6b646a09 100644 --- a/app/assets/javascripts/kuroko2/application.js +++ b/app/assets/javascripts/kuroko2/application.js @@ -1,6 +1,7 @@ //= require jquery //= require jquery_ujs //= require ./bootstrap +//= require ./js.cookie.js //= stub kuroko2/instance_linker //= require_tree //= require moment @@ -25,4 +26,42 @@ jQuery(function ($) { $(".right-side").toggleClass("strech"); } }); + + var showNotificationStatus = function () { + if (Notification.permission === 'granted') { + if (Cookies.get('notification') === 'on') { + $('#notification').html(" on"); + } else { + $('#notification').html(" off"); + } + } else if (Notification.permission === 'denied') { + $('#notification').html(" off"); + } + } + + $('#notification').click(function (e) { + if (!('Notification' in window)) { + return; + } + + if (Notification.permission === 'default') { + Notification.requestPermission(function (permission) { + if (permission === "granted") { + Cookies.set('notification', 'on'); + } + showNotificationStatus(); + }); + } else if (Notification.permission === 'granted') { + if (Cookies.get('notification') === 'on') { + Cookies.set('notification', 'off'); + } else { + Cookies.set('notification', 'on'); + } + showNotificationStatus(); + } + }); + + if ('Notification' in window) { + showNotificationStatus(); + } }); diff --git a/app/assets/javascripts/kuroko2/job_instances.js b/app/assets/javascripts/kuroko2/job_instances.js index 785d12c0..9a9d205d 100644 --- a/app/assets/javascripts/kuroko2/job_instances.js +++ b/app/assets/javascripts/kuroko2/job_instances.js @@ -3,11 +3,25 @@ jQuery(function ($) { var logIntervalId; + var notifyIfNeeded = function (status, name, image) { + if (!('Notification' in window)) { + return; + } + + if (Notification.permission === 'granted' && Cookies.get('notification') === 'on') { + var notification = new Notification("[" + status + "] " + name, {"icon": image[status.toLowerCase()]}); + notification.onclick = function () { + notification.close(); + window.focus(); + }; + } + }; var updateInstance = function () { var instancePath = $('#instance').data("instance-path"); $.get(instancePath, function (data) { $('#instance').replaceWith(data); + notifyIfNeeded($('#instance-status').text(), $('#definition-name').text(), $('#notification').data()); }); }; var updateLogs = function () { diff --git a/app/assets/javascripts/kuroko2/js.cookie.js b/app/assets/javascripts/kuroko2/js.cookie.js new file mode 100644 index 00000000..12fa0eeb --- /dev/null +++ b/app/assets/javascripts/kuroko2/js.cookie.js @@ -0,0 +1,156 @@ +/*! + * JavaScript Cookie v2.1.3 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader = false; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + var result; + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + if (!converter.write) { + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + } else { + value = converter.write(value, key); + } + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + return (document.cookie = [ + key, '=', value, + attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE + attributes.path ? '; path=' + attributes.path : '', + attributes.domain ? '; domain=' + attributes.domain : '', + attributes.secure ? '; secure' : '' + ].join('')); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = parts[0].replace(rdecode, decodeURIComponent); + cookie = converter.read ? + converter.read(cookie, name) : converter(cookie, name) || + cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.set = api; + api.get = function (key) { + return api.call(api, key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/app/assets/stylesheets/kuroko2/application.scss b/app/assets/stylesheets/kuroko2/application.scss index 910a2e54..0fe49566 100644 --- a/app/assets/stylesheets/kuroko2/application.scss +++ b/app/assets/stylesheets/kuroko2/application.scss @@ -88,3 +88,7 @@ font-size: 1.3em; fill: #666; } + +#notification { + cursor: pointer; +} diff --git a/app/views/kuroko2/job_instances/_instance.html.slim b/app/views/kuroko2/job_instances/_instance.html.slim index 741d7408..d266a0e6 100644 --- a/app/views/kuroko2/job_instances/_instance.html.slim +++ b/app/views/kuroko2/job_instances/_instance.html.slim @@ -1,6 +1,6 @@ .box#instance data-instance-path="#{naked_job_definition_job_instance_path(@definition, @instance)}" .box-header - h2.box-title + h2#definition-name.box-title = link_to "##{@definition.id} #{@definition.name}", job_definition_path(@definition) .box-body table.table diff --git a/app/views/layouts/kuroko2/application.html.slim b/app/views/layouts/kuroko2/application.html.slim index df4b173c..45201452 100644 --- a/app/views/layouts/kuroko2/application.html.slim +++ b/app/views/layouts/kuroko2/application.html.slim @@ -31,6 +31,9 @@ html img.img-circle src='#{current_user.image}' alt="#{current_user.name}" .pull-left.info p Hello, #{current_user.name} + p#notification data-success='#{asset_url('kuroko2/kuroko-logo-success.png')}' data-error='#{asset_url('kuroko2/kuroko-logo-error.png')}' + i.fa.fa-volume-off + | Turn on notification ul.sidebar-menu li class=('active' if controller_name == 'dashboard') = link_to raw(' Dashboard'), root_path diff --git a/lib/kuroko2/engine.rb b/lib/kuroko2/engine.rb index b38c0b09..10c1721e 100644 --- a/lib/kuroko2/engine.rb +++ b/lib/kuroko2/engine.rb @@ -50,6 +50,8 @@ class Engine < ::Rails::Engine config.action_mailer.smtp_settings = Kuroko2.config.action_mailer.smtp_settings.to_h.symbolize_keys || {} + app.config.assets.precompile += %w(kuroko2/kuroko-logo-success.png kuroko2/kuroko-logo-error.png) + if Kuroko2.config.extentions && Kuroko2.config.extentions.controller Kuroko2.config.extentions.controller.each do |extention| Kuroko2::ApplicationController.include(Module.const_get(extention, false))