diff --git a/Gemfile b/Gemfile index 537856bf0..2474d3fcd 100644 --- a/Gemfile +++ b/Gemfile @@ -55,6 +55,7 @@ gem 'net-ldap' gem 'writeexcel' gem 'icalendar' +gem 'browser' # event machine gem 'eventmachine' diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 5bbb9ac4f..f531f504f 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -210,7 +210,7 @@ class App.Controller extends Spine.Controller # console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation')) ui.frontendTimeUpdateItem(item) ) - App.Interval.set( update, 30000, 'frontendTimeUpdate', 'ui' ) + App.Interval.set( update, 61000, 'frontendTimeUpdate', 'ui' ) frontendTimeUpdateItem: (item) => timestamp = item.data('time') diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee index d2b8fba67..0dfa0912b 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee @@ -401,9 +401,6 @@ class App.GenericHistory extends App.ControllerModal # enable user popups @userPopups() - # show frontend times - @delay( @frontendTimeUpdate, 800, 'ui-time-update' ) - sortorder: => @items = @items.reverse() diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee index dd68029d8..1acd639d6 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_table.js.coffee @@ -274,9 +274,4 @@ class App.ControllerTable extends App.Controller ) ) - time = => - @frontendTimeUpdate() - @delay(time, 80) # to show time immediately for normal tables - @delay(time, 280) # to show time immediately for tables in modal dialog - table diff --git a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee index 7e49d6792..9455f3b63 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.js.coffee @@ -51,9 +51,6 @@ class App.DashboardActivityStream extends App.Controller @$('.activity-entries').remove() @el.append html - # update time - @frontendTimeUpdate() - renderItem: (item) -> html = $(App.view('dashboard/activity_stream')( item: item diff --git a/app/assets/javascripts/app/controllers/_profile/devices.js.coffee b/app/assets/javascripts/app/controllers/_profile/devices.js.coffee new file mode 100644 index 000000000..4cd832e12 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_profile/devices.js.coffee @@ -0,0 +1,50 @@ +class Index extends App.Controller + events: + 'click [data-type=delete]': 'delete' + + constructor: -> + super + return if !@authenticate() + @title 'Devices', true + + @load() + @interval( + => + @load() + 62000 + ) + + # fetch data, render view + load: => + @ajax( + id: 'user_devices' + type: 'GET' + url: @apiPath + '/user_devices' + success: (data) => + @render(data) + ) + + render: (data) => + @html App.view('profile/devices')( devices: data ) + + delete: (e) => + e.preventDefault() + id = $(e.target).closest('a').data('device-id') + + @ajax( + id: 'user_devices_delete' + type: 'DELETE' + url: "#{@apiPath}/user_devices/#{id}" + processData: true + success: @load + error: @error + ) + + error: (xhr, status, error) => + data = JSON.parse( xhr.responseText ) + @notify( + type: 'error' + msg: App.i18n.translateContent( data.message ) + ) + +App.Config.set( 'Devices', { prio: 3100, name: 'Devices', parent: '#profile', target: '#profile/devices', controller: Index }, 'NavBarProfile' ) diff --git a/app/assets/javascripts/app/controllers/navigation.js.coffee b/app/assets/javascripts/app/controllers/navigation.js.coffee index 56fc6dac4..c0a99af81 100644 --- a/app/assets/javascripts/app/controllers/navigation.js.coffee +++ b/app/assets/javascripts/app/controllers/navigation.js.coffee @@ -142,15 +142,15 @@ class App.Navigation extends App.ControllerWidgetPermanent searchFunction = => # use cache for search result - if @searchResultCache[@term] - @renderResult( @searchResultCache[@term] ) + if @searchResultCache[@query] + @renderResult( @searchResultCache[@query] ) App.Ajax.request( id: 'search' type: 'GET' url: @apiPath + '/search' data: - term: @term + query: @query processData: true, success: (data, status, xhr) => @@ -158,25 +158,21 @@ class App.Navigation extends App.ControllerWidgetPermanent App.Collection.loadAssets( data.assets ) # cache search result - @searchResultCache[@term] = data.result + @searchResultCache[@query] = data.result - result = data.result - for area in result - if area.name is 'Ticket' - area.result = [] - for id in area.ids - ticket = App.Ticket.find( id ) - area.result.push ticket.searchResultAttributes() - else if area.name is 'User' - area.result = [] - for id in area.ids - user = App.User.find( id ) - area.result.push user.searchResultAttributes() - else if area.name is 'Organization' - area.result = [] - for id in area.ids - organization = App.Organization.find( id ) - area.result.push organization.searchResultAttributes() + result = {} + for item in data.result + if App[item.type] && App[item.type].find + if !result[item.type] + result[item.type] = [] + item_object = App[item.type].find(item.id) + if item_object.searchResultAttributes + item_object_search_attributes = item_object.searchResultAttributes() + result[item.type].push item_object_search_attributes + else + @log 'error', "No such model #{item.type.toLocaleLowerCase()}.searchResultAttributes()" + else + @log 'error', "No such model App.#{item.type}" @renderResult(result) @@ -219,9 +215,9 @@ class App.Navigation extends App.ControllerWidgetPermanent removePopovers() # check if search is needed - term = @$('#global-search').val().trim() - return if !term - @term = term + query = @$('#global-search').val().trim() + return if !query + @query = query @delay( searchFunction, 220, 'search' ) ) @@ -239,11 +235,11 @@ class App.Navigation extends App.ControllerWidgetPermanent return # on other keys, show result - term = @$('#global-search').val().trim() - return if !term - return if term is @term - @term = term - @$('.search').toggleClass('filled', !!@term) + query = @$('#global-search').val().trim() + return if !query + return if query is @query + @query = query + @$('.search').toggleClass('filled', !!@query) @delay( searchFunction, 200, 'search' ) ) diff --git a/app/assets/javascripts/app/controllers/session.js.coffee b/app/assets/javascripts/app/controllers/session.js.coffee index a52e9198f..b208ee116 100644 --- a/app/assets/javascripts/app/controllers/session.js.coffee +++ b/app/assets/javascripts/app/controllers/session.js.coffee @@ -42,9 +42,6 @@ class Index extends App.ControllerContent sessions: data.sessions ) - # show frontend times - @frontendTimeUpdate() - destroy: (e) -> e.preventDefault() sessionId = $( e.target ).closest('a').data('session-id') diff --git a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee index b7e62c5de..b48221797 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.js.coffee @@ -321,9 +321,6 @@ class Table extends App.Controller # start organization popups @organizationPopups() - # show frontend times - @frontendTimeUpdate() - # start bulk action observ @el.find('.bulkAction').append( @bulk_form() ) if @el.find('.table-overview').find('input[name="bulk"]:checked').length isnt 0 diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.js.coffee index dc7172f67..845371ab1 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.js.coffee @@ -112,9 +112,6 @@ class ArticleViewItem extends App.Controller article: @article ) - # show frontend times - @frontendTimeUpdate() - # set see more option @setSeeMore() diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/meta.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/meta.js.coffee index cbc3865ea..613171847 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/meta.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/meta.js.coffee @@ -12,8 +12,5 @@ class App.TicketZoomMeta extends App.Controller isCustomer: @isRole('Customer') ) - # show frontend times - @frontendTimeUpdate() - release: => App.Ticket.unsubscribe( @subscribeId ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee index 971131515..5613532d0 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee @@ -130,9 +130,6 @@ class App.OnlineNotificationWidget extends App.Controller $( App.view('widget/online_notification_content')(items: items) ) ) - # show frontend times - @frontendTimeUpdate() - createContainer: => @removeContainer() diff --git a/app/assets/javascripts/app/controllers/widget/ticket_stats.js.coffee b/app/assets/javascripts/app/controllers/widget/ticket_stats.js.coffee index 34f4fac04..c838c47cf 100644 --- a/app/assets/javascripts/app/controllers/widget/ticket_stats.js.coffee +++ b/app/assets/javascripts/app/controllers/widget/ticket_stats.js.coffee @@ -144,7 +144,6 @@ class TicketStatsList extends App.Controller limit: @limit ) - @frontendTimeUpdate() @ticketPopups() showAll: (e) => diff --git a/app/assets/javascripts/app/index.js.coffee b/app/assets/javascripts/app/index.js.coffee index 36fcebebf..602831265 100644 --- a/app/assets/javascripts/app/index.js.coffee +++ b/app/assets/javascripts/app/index.js.coffee @@ -107,8 +107,13 @@ class App extends Spine.Controller # use pretty time for datetime else if attribute_config.tag is 'datetime' isHtmlEscape = true - result = "?" - #result = App.i18n.translateTimestamp(result) + timestamp = App.i18n.translateTimestamp(result) + escalation = false + cssClass = attribute_config.class || '' + if cssClass.match 'escalation' + escalation = true + humanTime = App.PrettyDate.humanTime(result, escalation) + result = "" if !isHtmlEscape && typeof result is 'string' result = App.Utils.htmlEscape(result) @@ -216,6 +221,14 @@ class App extends Spine.Controller params.humanFileSize = ( size ) -> App.Utils.humanFileSize(size) + # define pretty/human time helper + params.humanTime = ( time, escalation = false, cssClass = '') -> + timestamp = App.i18n.translateTimestamp(time) + if escalation + cssClass += ' escalation' + humanTime = App.PrettyDate.humanTime(time, escalation) + "" + # define template JST["app/views/#{name}"](params) template diff --git a/app/assets/javascripts/app/lib/app_init/track.js.coffee b/app/assets/javascripts/app/lib/app_init/track.js.coffee index b1f2dee0e..1448e9e32 100644 --- a/app/assets/javascripts/app/lib/app_init/track.js.coffee +++ b/app/assets/javascripts/app/lib/app_init/track.js.coffee @@ -58,6 +58,7 @@ class _trackSingleton ) # log ajax calls + ### disabled, only needed for debugging $(document).bind( 'ajaxComplete', ( e, request, settings ) => # do not log ui requests @@ -92,6 +93,7 @@ class _trackSingleton } ) ) + ### $(window).bind( 'beforeunload' diff --git a/app/assets/javascripts/app/lib/app_post/auth.js.coffee b/app/assets/javascripts/app/lib/app_post/auth.js.coffee index 6d4225960..751288a58 100644 --- a/app/assets/javascripts/app/lib/app_post/auth.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/auth.js.coffee @@ -2,6 +2,7 @@ class App.Auth @login: (params) -> App.Log.notice 'Auth', 'login', params + params.data['fingerprint'] = App.Browser.fingerprint() App.Ajax.request( id: 'login' type: 'POST' @@ -21,12 +22,15 @@ class App.Auth ) @loginCheck: -> + params = + fingerprint: App.Browser.fingerprint() App.Log.debug 'Auth', 'loginCheck' App.Ajax.request( id: 'login_check' async: false - type: 'GET' + type: 'POST' url: App.Config.get('api_path') + '/signshow' + data: JSON.stringify(params) success: (data, status, xhr) => # set login (config, session, ...) diff --git a/app/assets/javascripts/app/lib/app_post/browser.coffee b/app/assets/javascripts/app/lib/app_post/browser.coffee index d82f5a9a5..8d3013879 100644 --- a/app/assets/javascripts/app/lib/app_post/browser.coffee +++ b/app/assets/javascripts/app/lib/app_post/browser.coffee @@ -47,6 +47,32 @@ class App.Browser # allow browser true + @fingerprint: -> + localStorage = window['localStorage'] + + # read from local storage + if localStorage + fingerprint = localStorage.getItem('fingerprint') + return fingerprint if fingerprint + + # detect fingerprint + data = @detection() + resolution = "#{window.screen.availWidth}x#{window.screen.availHeight}/#{window.screen.pixelDepth}" + timezone = new Date().toString().match(/\s\(.+?\)$/) + hashCode = (s) -> + s.split('').reduce( + (a,b) -> + a=((a<<5)-a)+b.charCodeAt(0) + a&a + 0 + ) + fingerprint = hashCode("#{data.browser.name}#{data.browser.major}#{data.os}#{resolution}#{timezone}") + + # write to local storage + if localStorage + localStorage.setItem('fingerprint', fingerprint) + fingerprint + @message: (data, version) -> new App.ControllerModal( head: 'Browser too old!' diff --git a/app/assets/javascripts/app/lib/app_post/interface_handle.js.coffee b/app/assets/javascripts/app/lib/app_post/interface_handle.js.coffee index 89cacaa2f..ef9c813a1 100644 --- a/app/assets/javascripts/app/lib/app_post/interface_handle.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/interface_handle.js.coffee @@ -21,6 +21,9 @@ class App.Run extends App.Controller # create web socket connection App.WebSocket.connect() + # start frontend time update + @frontendTimeUpdate() + # start navbars @setupWidget( 'Navigations', 'nav', @el ) diff --git a/app/assets/javascripts/app/lib/app_post/websocket.js.coffee b/app/assets/javascripts/app/lib/app_post/websocket.js.coffee index 907907f73..7f40dafdb 100644 --- a/app/assets/javascripts/app/lib/app_post/websocket.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/websocket.js.coffee @@ -115,8 +115,8 @@ class _webSocketSingleton extends App.Controller # logon websocket data = action: 'login' - session: - id: App.Session.get('id') + session_id: App.Config.get('session_id') + fingerprint: App.Browser.fingerprint() @send(data) spool: => diff --git a/app/assets/javascripts/app/views/dashboard/activity_stream.jst.eco b/app/assets/javascripts/app/views/dashboard/activity_stream.jst.eco index 123b0fbe5..825d6e473 100644 --- a/app/assets/javascripts/app/views/dashboard/activity_stream.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/activity_stream.jst.eco @@ -5,7 +5,7 @@ <%= @item.created_by.displayName() %> <%- @T( @item.type ) %> <%- @T( @item.object_name ) %><% if @item.title: %> (<%= @item.title %>)<% end %> - ? + <%- @humanTime(@item.created_at, false, 'activity-time') %> diff --git a/app/assets/javascripts/app/views/generic/history.jst.eco b/app/assets/javascripts/app/views/generic/history.jst.eco index e1b3b8409..d074af13d 100644 --- a/app/assets/javascripts/app/views/generic/history.jst.eco +++ b/app/assets/javascripts/app/views/generic/history.jst.eco @@ -5,7 +5,7 @@ <% for item in @items: %> <%= item.created_by.displayName() %> - - ? + <%- @humanTime(item.created_at) %>