Moved to websockets.
This commit is contained in:
parent
e6f4bd021f
commit
b2209ff868
26 changed files with 868 additions and 403 deletions
3
Gemfile
3
Gemfile
|
@ -58,3 +58,6 @@ gem 'simple-rss'
|
||||||
# To use debugger
|
# To use debugger
|
||||||
# gem 'ruby-debug'
|
# gem 'ruby-debug'
|
||||||
|
|
||||||
|
# event machine
|
||||||
|
gem 'eventmachine'
|
||||||
|
gem 'em-websocket'
|
||||||
|
|
|
@ -2,7 +2,8 @@ class App.Controller extends Spine.Controller
|
||||||
|
|
||||||
# add @title methode to set title
|
# add @title methode to set title
|
||||||
title: (name) ->
|
title: (name) ->
|
||||||
$('html head title').html( Config.product_name + ' - ' + Ti(name) )
|
# $('html head title').html( Config.product_name + ' - ' + Ti(name) )
|
||||||
|
document.title = Config.product_name + ' - ' + Ti(name)
|
||||||
|
|
||||||
# add @notify methode to create notification
|
# add @notify methode to create notification
|
||||||
notify: (data) ->
|
notify: (data) ->
|
||||||
|
@ -545,6 +546,13 @@ class App.Controller extends Spine.Controller
|
||||||
|
|
||||||
return newInstance
|
return newInstance
|
||||||
|
|
||||||
|
clearInterval: (interval_id) =>
|
||||||
|
# check global var
|
||||||
|
if !@intervalID
|
||||||
|
@intervalID = {}
|
||||||
|
|
||||||
|
clearInterval( @intervalID[interval_id] ) if @intervalID[interval_id]
|
||||||
|
|
||||||
interval: (action, interval, interval_id) =>
|
interval: (action, interval, interval_id) =>
|
||||||
|
|
||||||
# check global var
|
# check global var
|
||||||
|
@ -778,11 +786,14 @@ class App.ControllerModal extends App.Controller
|
||||||
super(options)
|
super(options)
|
||||||
|
|
||||||
modalShow: (params) =>
|
modalShow: (params) =>
|
||||||
@el.modal({
|
defaults = {
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
keyboard: true,
|
keyboard: true,
|
||||||
show: true
|
show: true,
|
||||||
})
|
}
|
||||||
|
data = $.extend({}, defaults, params)
|
||||||
|
@el.modal(data)
|
||||||
|
|
||||||
@el.bind('hidden', =>
|
@el.bind('hidden', =>
|
||||||
|
|
||||||
# navigate back to home page
|
# navigate back to home page
|
||||||
|
@ -805,3 +816,17 @@ class App.ControllerModal extends App.Controller
|
||||||
submit: (e) =>
|
submit: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@log 'You need to implement your own "submit" method!'
|
@log 'You need to implement your own "submit" method!'
|
||||||
|
|
||||||
|
class App.ErrorModal extends App.ControllerModal
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
@html App.view('error')(
|
||||||
|
message: @message
|
||||||
|
)
|
||||||
|
@modalShow(
|
||||||
|
backdrop: false,
|
||||||
|
keyboard: false,
|
||||||
|
)
|
||||||
|
|
|
@ -9,25 +9,29 @@ class App.DashboardActivityStream extends App.Controller
|
||||||
@items = []
|
@items = []
|
||||||
|
|
||||||
# refresh list ever 140 sec.
|
# refresh list ever 140 sec.
|
||||||
@interval( @fetch, 1400000, 'dashboard_activity_stream' )
|
# @interval( @fetch, 1400000, 'dashboard_activity_stream' )
|
||||||
|
@fetch()
|
||||||
|
Spine.bind 'activity_stream_rebuild', (data) =>
|
||||||
|
@log 'a_stream', data
|
||||||
|
@fetch()
|
||||||
|
|
||||||
fetch: =>
|
fetch: =>
|
||||||
|
|
||||||
# use cache of first page
|
# use cache of first page
|
||||||
if window.LastRefresh[ 'dashboard_activity_stream' ]
|
if window.LastRefresh[ 'activity_stream' ]
|
||||||
@render( window.LastRefresh[ 'dashboard_activity_stream' ] )
|
@load( window.LastRefresh[ 'activity_stream' ] )
|
||||||
|
|
||||||
# get data
|
# # get data
|
||||||
App.Com.ajax(
|
# App.Com.ajax(
|
||||||
id: 'dashoard_activity_stream',
|
# id: 'dashoard_activity_stream',
|
||||||
type: 'GET',
|
# type: 'GET',
|
||||||
url: '/activity_stream',
|
# url: '/activity_stream',
|
||||||
data: {
|
# data: {
|
||||||
limit: @limit,
|
# limit: @limit,
|
||||||
}
|
# }
|
||||||
processData: true,
|
# processData: true,
|
||||||
success: @load
|
# success: @load
|
||||||
)
|
# )
|
||||||
|
|
||||||
load: (data) =>
|
load: (data) =>
|
||||||
items = data.activity_stream
|
items = data.activity_stream
|
||||||
|
|
|
@ -4,35 +4,17 @@ class App.DashboardRss extends App.Controller
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
|
|
||||||
# refresh list ever 600 sec.
|
# refresh list ever 600 sec.
|
||||||
@interval( @fetch, 6000000, 'dashboard_rss' )
|
Spine.bind 'rss_rebuild', (data) =>
|
||||||
|
@load(data)
|
||||||
fetch: =>
|
|
||||||
|
|
||||||
# use cache of first page
|
# use cache of first page
|
||||||
if window.LastRefresh[ 'dashboard_rss' ]
|
if window.LastRefresh[ 'dashboard_rss' ]
|
||||||
@render( window.LastRefresh[ 'dashboard_rss' ] )
|
@load( window.LastRefresh[ 'dashboard_rss' ] )
|
||||||
|
|
||||||
# get data
|
|
||||||
App.Com.ajax(
|
|
||||||
id: 'dashboard_rss',
|
|
||||||
type: 'GET',
|
|
||||||
url: '/rss_fetch',
|
|
||||||
data: {
|
|
||||||
limit: @limit,
|
|
||||||
url: @url,
|
|
||||||
}
|
|
||||||
processData: true,
|
|
||||||
success: @load
|
|
||||||
)
|
|
||||||
|
|
||||||
load: (data) =>
|
load: (data) =>
|
||||||
items = data.items || []
|
items = data.items || []
|
||||||
|
@head = data.head || '?'
|
||||||
# set cache
|
|
||||||
window.LastRefresh[ 'dashboard_rss' ] = items
|
|
||||||
|
|
||||||
@render(items)
|
@render(items)
|
||||||
|
|
||||||
render: (items) ->
|
render: (items) ->
|
||||||
|
|
|
@ -271,7 +271,7 @@ class Index extends App.Controller
|
||||||
@tickets = []
|
@tickets = []
|
||||||
|
|
||||||
# rebuild navbar with updated ticket count of overviews
|
# rebuild navbar with updated ticket count of overviews
|
||||||
Spine.trigger 'navupdate_remote'
|
App.WebSocket.send( event: 'navupdate_ticket_overview' )
|
||||||
|
|
||||||
# fetch overview data again
|
# fetch overview data again
|
||||||
@fetch()
|
@fetch()
|
||||||
|
|
|
@ -15,7 +15,6 @@ class Index extends App.Controller
|
||||||
@navupdate '#get_started'
|
@navupdate '#get_started'
|
||||||
|
|
||||||
@master_user = 0
|
@master_user = 0
|
||||||
|
|
||||||
# @render()
|
# @render()
|
||||||
@fetch()
|
@fetch()
|
||||||
|
|
||||||
|
@ -102,12 +101,10 @@ class Index extends App.Controller
|
||||||
|
|
||||||
# rerender page
|
# rerender page
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
# error: =>
|
# error: =>
|
||||||
# @modalHide()
|
# @modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
relogin: (data, status, xhr) =>
|
relogin: (data, status, xhr) =>
|
||||||
@log 'login:success', data
|
@log 'login:success', data
|
||||||
|
|
||||||
|
@ -124,6 +121,4 @@ class Index extends App.Controller
|
||||||
@el.find('.agent_user').fadeIn()
|
@el.find('.agent_user').fadeIn()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Config.Routes['getting_started'] = Index
|
Config.Routes['getting_started'] = Index
|
||||||
|
|
|
@ -85,10 +85,14 @@ class Index extends App.Controller
|
||||||
for key, value of data.default_collections
|
for key, value of data.default_collections
|
||||||
App[key].refresh( value, options: { clear: true } )
|
App[key].refresh( value, options: { clear: true } )
|
||||||
|
|
||||||
|
# rebuild navbar with user data
|
||||||
Spine.trigger 'navrebuild', data.session
|
Spine.trigger 'navrebuild', data.session
|
||||||
|
|
||||||
# rebuild navbar with updated ticket count of overviews
|
# update websocked auth info
|
||||||
Spine.trigger 'navupdate_remote'
|
App.WebSocket.auth()
|
||||||
|
|
||||||
|
# rebuild navbar with ticket overview counter
|
||||||
|
App.WebSocket.send( event: 'navupdate_ticket_overview' )
|
||||||
|
|
||||||
# add notify
|
# add notify
|
||||||
Spine.trigger 'notify:removeall'
|
Spine.trigger 'notify:removeall'
|
||||||
|
|
|
@ -4,7 +4,6 @@ class Index extends Spine.Controller
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
@signout()
|
@signout()
|
||||||
|
|
||||||
signout: ->
|
signout: ->
|
||||||
|
|
|
@ -6,35 +6,46 @@ class App.Navigation extends App.Controller
|
||||||
@log 'nav...'
|
@log 'nav...'
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
sync_ticket_overview = =>
|
# update selected item
|
||||||
@interval( @ticket_overview, 30000, 'nav_ticket_overview' )
|
|
||||||
|
|
||||||
sync_recent_viewed = =>
|
|
||||||
@interval( @recent_viewed, 40000, 'nav_recent_viewed' )
|
|
||||||
|
|
||||||
Spine.bind 'navupdate', (data) =>
|
Spine.bind 'navupdate', (data) =>
|
||||||
@update(arguments[0])
|
@update(arguments[0])
|
||||||
|
|
||||||
|
# rebuild nav bar with given user data
|
||||||
Spine.bind 'navrebuild', (user) =>
|
Spine.bind 'navrebuild', (user) =>
|
||||||
@log 'navbarrebuild', user
|
@log 'navbarrebuild', user
|
||||||
@render(user)
|
@render(user)
|
||||||
|
|
||||||
Spine.bind 'navupdate_remote', (user) =>
|
# rebuild ticket overview data
|
||||||
@log 'navupdate_remote'
|
Spine.bind 'navupdate_ticket_overview', (data) =>
|
||||||
@delay( sync_ticket_overview, 500 )
|
@ticket_overview_build(data)
|
||||||
@delay( sync_recent_viewed, 1000 )
|
|
||||||
|
|
||||||
# rerender if new overview data is there
|
# rebuild recent viewd data
|
||||||
@delay( sync_ticket_overview, 800 )
|
Spine.bind 'update_recent_viewed', (data) =>
|
||||||
@delay( sync_recent_viewed, 1000 )
|
@recent_viewed_build(data)
|
||||||
|
|
||||||
render: (user) ->
|
render: (user) ->
|
||||||
nav_left = @getItems( navbar: Config.NavBar )
|
nav_left = @getItems( navbar: Config.NavBar )
|
||||||
nav_right = @getItems( navbar: Config.NavBarRight )
|
nav_right = @getItems( navbar: Config.NavBarRight )
|
||||||
|
|
||||||
|
# get open tabs to repopen on rerender
|
||||||
|
open_tab = {}
|
||||||
|
@el.find('.open').children('a').each( (i,d) =>
|
||||||
|
href = $(d).attr('href')
|
||||||
|
open_tab[href] = true
|
||||||
|
)
|
||||||
|
|
||||||
|
# get active tabs to reactivate on rerender
|
||||||
|
active_tab = {}
|
||||||
|
@el.find('.active').children('a').each( (i,d) =>
|
||||||
|
href = $(d).attr('href')
|
||||||
|
active_tab[href] = true
|
||||||
|
)
|
||||||
|
|
||||||
@html App.view('navigation')(
|
@html App.view('navigation')(
|
||||||
navbar_left: nav_left,
|
navbar_left: nav_left,
|
||||||
navbar_right: nav_right,
|
navbar_right: nav_right,
|
||||||
|
open_tab: open_tab,
|
||||||
|
active_tab: active_tab,
|
||||||
user: user,
|
user: user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -119,118 +130,65 @@ class App.Navigation extends App.Controller
|
||||||
|
|
||||||
update: (url) =>
|
update: (url) =>
|
||||||
@el.find('li').removeClass('active')
|
@el.find('li').removeClass('active')
|
||||||
# if url isnt '#'
|
|
||||||
@el.find("[href=\"#{url}\"]").parents('li').addClass('active')
|
@el.find("[href=\"#{url}\"]").parents('li').addClass('active')
|
||||||
# @el.find("[href*=\"#{url}\"]").parents('li').addClass('active')
|
|
||||||
|
|
||||||
# get data
|
ticket_overview_build: (data) =>
|
||||||
ticket_overview: =>
|
|
||||||
|
|
||||||
# do no load and rerender if sub-menu is open
|
# remove old views
|
||||||
open = @el.find('.open').val()
|
for key of Config.NavBar
|
||||||
if open isnt undefined
|
if Config.NavBar[key].parent is '#ticket/view'
|
||||||
return
|
delete Config.NavBar[key]
|
||||||
|
|
||||||
# do no load and rerender if user is not logged in
|
# add new views
|
||||||
if !window.Session['id']
|
for item in data
|
||||||
return
|
Config.NavBar['TicketOverview' + item.url] = {
|
||||||
|
prio: item.prio,
|
||||||
|
parent: '#ticket/view',
|
||||||
|
name: item.name,
|
||||||
|
count: item.count,
|
||||||
|
target: '#ticket/view/' + item.url,
|
||||||
|
role: ['Agent'],
|
||||||
|
}
|
||||||
|
|
||||||
# only of lod request is already done
|
# rebuild navbar
|
||||||
|
Spine.trigger 'navrebuild', window.Session
|
||||||
|
|
||||||
if !@req_overview
|
recent_viewed_build: (data) =>
|
||||||
@req_overview = App.Com.ajax(
|
|
||||||
id: 'navbar_ticket_overviews',
|
|
||||||
type: 'GET',
|
|
||||||
url: '/ticket_overviews',
|
|
||||||
data: {},
|
|
||||||
processData: true,
|
|
||||||
success: (data, status, xhr) =>
|
|
||||||
|
|
||||||
# remove old views
|
items = data.recent_viewed
|
||||||
for key of Config.NavBar
|
|
||||||
if Config.NavBar[key].parent is '#ticket/view'
|
|
||||||
delete Config.NavBar[key]
|
|
||||||
|
|
||||||
# add new views
|
# load user collection
|
||||||
for item in data
|
@loadCollection( type: 'User', data: data.users )
|
||||||
Config.NavBar['TicketOverview' + item.url] = {
|
|
||||||
prio: item.prio,
|
|
||||||
parent: '#ticket/view',
|
|
||||||
name: item.name,
|
|
||||||
count: item.count,
|
|
||||||
target: '#ticket/view/' + item.url,
|
|
||||||
role: ['Agent'],
|
|
||||||
}
|
|
||||||
|
|
||||||
# rebuild navbar
|
# load ticket collection
|
||||||
Spine.trigger 'navrebuild', window.Session
|
@loadCollection( type: 'Ticket', data: data.tickets )
|
||||||
|
|
||||||
# reset ajax call
|
# remove old views
|
||||||
@req_overview = undefined
|
for key of Config.NavBarRight
|
||||||
)
|
if Config.NavBarRight[key].parent is '#current_user'
|
||||||
|
part = key.split '::'
|
||||||
|
if part[0] is 'RecendViewed'
|
||||||
|
delete Config.NavBarRight[key]
|
||||||
|
|
||||||
# get data
|
# add new views
|
||||||
recent_viewed: =>
|
prio = 8000
|
||||||
|
for item in items
|
||||||
|
divider = false
|
||||||
|
navheader = false
|
||||||
|
if prio is 8000
|
||||||
|
divider = true
|
||||||
|
navheader = 'Recent Viewed'
|
||||||
|
ticket = App.Ticket.find(item.o_id)
|
||||||
|
prio++
|
||||||
|
Config.NavBarRight['RecendViewed::' + ticket.id + '-' + prio ] = {
|
||||||
|
prio: prio,
|
||||||
|
parent: '#current_user',
|
||||||
|
name: item.history_object.name + ' (' + ticket.title + ')',
|
||||||
|
target: '#ticket/zoom/' + ticket.id,
|
||||||
|
role: ['Agent'],
|
||||||
|
divider: divider,
|
||||||
|
navheader: navheader
|
||||||
|
}
|
||||||
|
|
||||||
# do no load and rerender if sub-menu is open
|
# rebuild navbar
|
||||||
open = @el.find('.open').val()
|
Spine.trigger 'navrebuild', window.Session
|
||||||
if open isnt undefined
|
|
||||||
return
|
|
||||||
|
|
||||||
# do no load and rerender if user is not logged in
|
|
||||||
if !window.Session['id']
|
|
||||||
return
|
|
||||||
|
|
||||||
# only of lod request is already done
|
|
||||||
if !@req_recent_viewed
|
|
||||||
@req_recent_viewed = App.Com.ajax(
|
|
||||||
id: 'navbar_recent_viewed',
|
|
||||||
type: 'GET',
|
|
||||||
url: '/recent_viewed',
|
|
||||||
data: {
|
|
||||||
limit: 5,
|
|
||||||
}
|
|
||||||
processData: true,
|
|
||||||
success: (data, status, xhr) =>
|
|
||||||
|
|
||||||
items = data.recent_viewed
|
|
||||||
|
|
||||||
# load user collection
|
|
||||||
@loadCollection( type: 'User', data: data.users )
|
|
||||||
|
|
||||||
# load ticket collection
|
|
||||||
@loadCollection( type: 'Ticket', data: data.tickets )
|
|
||||||
|
|
||||||
# remove old views
|
|
||||||
for key of Config.NavBarRight
|
|
||||||
if Config.NavBarRight[key].parent is '#current_user'
|
|
||||||
part = Config.NavBarRight[key].target.split '::'
|
|
||||||
if part is 'RecendViewed'
|
|
||||||
delete Config.NavBarRight[key]
|
|
||||||
|
|
||||||
# add new views
|
|
||||||
prio = 5000
|
|
||||||
for item in items
|
|
||||||
divider = false
|
|
||||||
navheader = false
|
|
||||||
if prio is 5000
|
|
||||||
divider = true
|
|
||||||
navheader = 'Recent Viewed'
|
|
||||||
ticket = App.Ticket.find(item.o_id)
|
|
||||||
prio++
|
|
||||||
Config.NavBarRight['RecendViewed::' + ticket.id] = {
|
|
||||||
prio: prio,
|
|
||||||
parent: '#current_user',
|
|
||||||
name: item.history_object.name + ' (' + ticket.title + ')',
|
|
||||||
target: '#ticket/zoom/' + ticket.id,
|
|
||||||
role: ['Agent'],
|
|
||||||
divider: divider,
|
|
||||||
navheader: navheader
|
|
||||||
}
|
|
||||||
|
|
||||||
# rebuild navbar
|
|
||||||
Spine.trigger 'navrebuild', window.Session
|
|
||||||
|
|
||||||
# reset ajax call
|
|
||||||
@req_recent_viewed = undefined
|
|
||||||
)
|
|
|
@ -11,6 +11,7 @@
|
||||||
#= require ./lib/bootstrap-popover.js
|
#= require ./lib/bootstrap-popover.js
|
||||||
#= require ./lib/bootstrap-modal.js
|
#= require ./lib/bootstrap-modal.js
|
||||||
#= require ./lib/bootstrap-tab.js
|
#= require ./lib/bootstrap-tab.js
|
||||||
|
#= require ./lib/bootstrap-transition.js
|
||||||
|
|
||||||
#= require ./lib/underscore.coffee
|
#= require ./lib/underscore.coffee
|
||||||
#= require ./lib/ba-linkify.js
|
#= require ./lib/ba-linkify.js
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
#not_used= require_tree ./lib
|
#not_used= require_tree ./lib
|
||||||
#= require_self
|
#= require_self
|
||||||
#= require ./lib/ajax.js.coffee
|
#= require ./lib/ajax.js.coffee
|
||||||
|
#= require ./lib/websocket.js.coffee
|
||||||
#= require ./lib/auth.js.coffee
|
#= require ./lib/auth.js.coffee
|
||||||
#= require ./lib/i18n.js.coffee
|
#= require ./lib/i18n.js.coffee
|
||||||
#= require_tree ./models
|
#= require_tree ./models
|
||||||
|
@ -57,13 +59,15 @@ Config2.set( 'a', 123)
|
||||||
console.log '1112222', Config2.get( 'a')
|
console.log '1112222', Config2.get( 'a')
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
||||||
class App.Run extends Spine.Controller
|
class App.Run extends Spine.Controller
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
@log 'RUN app'#, @
|
@log 'RUN app'
|
||||||
@el = $('#app')
|
@el = $('#app')
|
||||||
|
|
||||||
|
# create web socket connection
|
||||||
|
App.WebSocket.connect()
|
||||||
|
|
||||||
# init of i18n
|
# init of i18n
|
||||||
new App.i18n
|
new App.i18n
|
||||||
|
|
||||||
|
@ -84,27 +88,6 @@ class App.Run extends Spine.Controller
|
||||||
window.Session['UISelection'] = @getSelected() + ''
|
window.Session['UISelection'] = @getSelected() + ''
|
||||||
)
|
)
|
||||||
|
|
||||||
# @ws = new WebSocket("ws://localhost:3001/");
|
|
||||||
|
|
||||||
# Set event handlers.
|
|
||||||
# @ws.onopen = ->
|
|
||||||
# console.log("onopen")
|
|
||||||
|
|
||||||
# @ws.onmessage = (e) ->
|
|
||||||
# e.data contains received string.
|
|
||||||
# console.log("onmessage: " + e.data)
|
|
||||||
# eval e.data
|
|
||||||
|
|
||||||
# Spine.bind 'ws:send', (data) =>
|
|
||||||
# @log 'ws:send', data
|
|
||||||
# @ws.send(data);
|
|
||||||
|
|
||||||
# @ws.onclose = ->
|
|
||||||
# console.log("onclose")
|
|
||||||
|
|
||||||
# @ws.onerror = ->
|
|
||||||
# console.log("onerror")
|
|
||||||
|
|
||||||
getSelected: ->
|
getSelected: ->
|
||||||
text = '';
|
text = '';
|
||||||
if window.getSelection
|
if window.getSelection
|
||||||
|
@ -125,8 +108,20 @@ class App.Content extends Spine.Controller
|
||||||
for route, callback of Config.Routes
|
for route, callback of Config.Routes
|
||||||
do (route, callback) =>
|
do (route, callback) =>
|
||||||
@route(route, (params) ->
|
@route(route, (params) ->
|
||||||
|
|
||||||
|
# remember current controller
|
||||||
Config['ActiveController'] = route
|
Config['ActiveController'] = route
|
||||||
Spine.trigger( 'ws:send', JSON.stringify( { action: 'active_controller', controller: route, params: params } ) )
|
|
||||||
|
# send current controller
|
||||||
|
params_only = {}
|
||||||
|
for i of params
|
||||||
|
if typeof params[i] isnt 'object'
|
||||||
|
params_only[i] = params[i]
|
||||||
|
App.WebSocket.send(
|
||||||
|
action: 'active_controller',
|
||||||
|
controller: route,
|
||||||
|
params: params_only,
|
||||||
|
)
|
||||||
|
|
||||||
# unbind in controller area
|
# unbind in controller area
|
||||||
@el.unbind()
|
@el.unbind()
|
||||||
|
@ -136,7 +131,6 @@ class App.Content extends Spine.Controller
|
||||||
$('footer').waypoint('remove')
|
$('footer').waypoint('remove')
|
||||||
|
|
||||||
params.el = @el
|
params.el = @el
|
||||||
params.auth = @auth
|
|
||||||
new callback( params )
|
new callback( params )
|
||||||
|
|
||||||
# scroll to top
|
# scroll to top
|
||||||
|
|
|
@ -34,6 +34,9 @@ class App.Auth
|
||||||
# empty session
|
# empty session
|
||||||
window.Session = {}
|
window.Session = {}
|
||||||
|
|
||||||
|
# update websocked auth info
|
||||||
|
App.WebSocket.auth()
|
||||||
|
|
||||||
# rebuild navbar with new navbar items
|
# rebuild navbar with new navbar items
|
||||||
Spine.trigger 'navrebuild'
|
Spine.trigger 'navrebuild'
|
||||||
|
|
||||||
|
@ -51,6 +54,9 @@ class App.Auth
|
||||||
for key, value of data.session
|
for key, value of data.session
|
||||||
window.Session[key] = value
|
window.Session[key] = value
|
||||||
|
|
||||||
|
# update websocked auth info
|
||||||
|
App.WebSocket.auth()
|
||||||
|
|
||||||
# refresh/load default collections
|
# refresh/load default collections
|
||||||
for key, value of data.default_collections
|
for key, value of data.default_collections
|
||||||
App[key].refresh( value, options: { clear: true } )
|
App[key].refresh( value, options: { clear: true } )
|
||||||
|
@ -61,12 +67,14 @@ class App.Auth
|
||||||
# rebuild navbar with updated ticket count of overviews
|
# rebuild navbar with updated ticket count of overviews
|
||||||
Spine.trigger 'navupdate_remote'
|
Spine.trigger 'navupdate_remote'
|
||||||
|
|
||||||
|
|
||||||
error: (xhr, statusText, error) =>
|
error: (xhr, statusText, error) =>
|
||||||
console.log 'loginCheck:error'#, error, statusText, xhr.statusCode
|
console.log 'loginCheck:error'#, error, statusText, xhr.statusCode
|
||||||
|
|
||||||
# empty session
|
# empty session
|
||||||
window.Session = {}
|
window.Session = {}
|
||||||
|
|
||||||
|
# update websocked auth info
|
||||||
|
App.WebSocket.auth()
|
||||||
)
|
)
|
||||||
|
|
||||||
@logout: ->
|
@logout: ->
|
||||||
|
@ -75,4 +83,13 @@ class App.Auth
|
||||||
id: 'logout',
|
id: 'logout',
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
url: '/signout',
|
url: '/signout',
|
||||||
|
success: =>
|
||||||
|
|
||||||
|
# update websocked auth info
|
||||||
|
App.WebSocket.auth()
|
||||||
|
|
||||||
|
error: (xhr, statusText, error) =>
|
||||||
|
|
||||||
|
# update websocked auth info
|
||||||
|
App.WebSocket.auth()
|
||||||
)
|
)
|
61
app/assets/javascripts/app/lib/bootstrap-transition.js
vendored
Normal file
61
app/assets/javascripts/app/lib/bootstrap-transition.js
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/* ===================================================
|
||||||
|
* bootstrap-transition.js v2.0.4
|
||||||
|
* http://twitter.github.com/bootstrap/javascript.html#transitions
|
||||||
|
* ===================================================
|
||||||
|
* Copyright 2012 Twitter, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* ========================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
!function ($) {
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
"use strict"; // jshint ;_;
|
||||||
|
|
||||||
|
|
||||||
|
/* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
|
||||||
|
* ======================================================= */
|
||||||
|
|
||||||
|
$.support.transition = (function () {
|
||||||
|
|
||||||
|
var transitionEnd = (function () {
|
||||||
|
|
||||||
|
var el = document.createElement('bootstrap')
|
||||||
|
, transEndEventNames = {
|
||||||
|
'WebkitTransition' : 'webkitTransitionEnd'
|
||||||
|
, 'MozTransition' : 'transitionend'
|
||||||
|
, 'OTransition' : 'oTransitionEnd'
|
||||||
|
, 'msTransition' : 'MSTransitionEnd'
|
||||||
|
, 'transition' : 'transitionend'
|
||||||
|
}
|
||||||
|
, name
|
||||||
|
|
||||||
|
for (name in transEndEventNames){
|
||||||
|
if (el.style[name] !== undefined) {
|
||||||
|
return transEndEventNames[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}())
|
||||||
|
|
||||||
|
return transitionEnd && {
|
||||||
|
end: transitionEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
})()
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}(window.jQuery);
|
114
app/assets/javascripts/app/lib/websocket.js.coffee
Normal file
114
app/assets/javascripts/app/lib/websocket.js.coffee
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
$ = jQuery.sub()
|
||||||
|
|
||||||
|
class App.WebSocket
|
||||||
|
_instance = undefined # Must be declared here to force the closure on the class
|
||||||
|
@connect: (args) -> # Must be a static method
|
||||||
|
if _instance == undefined
|
||||||
|
_instance ?= new _Singleton
|
||||||
|
_instance
|
||||||
|
|
||||||
|
@send: (args) -> # Must be a static method
|
||||||
|
@connect()
|
||||||
|
_instance.send(args)
|
||||||
|
|
||||||
|
@auth: (args) -> # Must be a static method
|
||||||
|
@connect()
|
||||||
|
_instance.auth(args)
|
||||||
|
|
||||||
|
# The actual Singleton class
|
||||||
|
class _Singleton extends Spine.Controller
|
||||||
|
queue: []
|
||||||
|
|
||||||
|
constructor: (@args) ->
|
||||||
|
@connect()
|
||||||
|
|
||||||
|
send: (data) =>
|
||||||
|
console.log 'ws:send trying', data, @ws, @ws.readyState
|
||||||
|
|
||||||
|
# A value of 0 indicates that the connection has not yet been established.
|
||||||
|
# A value of 1 indicates that the connection is established and communication is possible.
|
||||||
|
# A value of 2 indicates that the connection is going through the closing handshake.
|
||||||
|
# A value of 3 indicates that the connection has been closed or could not be opened.
|
||||||
|
if @ws.readyState is 0
|
||||||
|
@queue.push data
|
||||||
|
else
|
||||||
|
console.log( 'ws:send', data )
|
||||||
|
string = JSON.stringify( data )
|
||||||
|
@ws.send(string)
|
||||||
|
|
||||||
|
auth: (data) =>
|
||||||
|
|
||||||
|
# logon websocket
|
||||||
|
data = {
|
||||||
|
action: 'login',
|
||||||
|
session: window.Session
|
||||||
|
}
|
||||||
|
@send(data)
|
||||||
|
|
||||||
|
close: =>
|
||||||
|
@ws.close()
|
||||||
|
|
||||||
|
connect: =>
|
||||||
|
# console.log '------------ws connect....--------------'
|
||||||
|
|
||||||
|
if !window.WebSocket
|
||||||
|
@error = new App.ErrorModal(
|
||||||
|
message: 'Sorry, no websocket support!'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
@ws = new window.WebSocket( "ws://" + window.location.hostname + ":6042/" )
|
||||||
|
|
||||||
|
# Set event handlers.
|
||||||
|
@ws.onopen = =>
|
||||||
|
console.log( "onopen" )
|
||||||
|
|
||||||
|
# close error message if exists
|
||||||
|
if @error
|
||||||
|
@error.modalHide()
|
||||||
|
@error = undefined
|
||||||
|
|
||||||
|
@auth()
|
||||||
|
|
||||||
|
# empty queue
|
||||||
|
for item in @queue
|
||||||
|
console.log( 'ws:send queue', item )
|
||||||
|
@send(item)
|
||||||
|
@queue = []
|
||||||
|
|
||||||
|
@ws.onmessage = (e) ->
|
||||||
|
pipe = JSON.parse( e.data )
|
||||||
|
# console.log( "ws:onmessage", pipe )
|
||||||
|
|
||||||
|
# go through all blocks
|
||||||
|
for item in pipe
|
||||||
|
|
||||||
|
# fill collection
|
||||||
|
if item['collection']
|
||||||
|
console.log( "ws:onmessage collection:" + item['collection'] )
|
||||||
|
window.LastRefresh[ item['collection'] ] = item['data']
|
||||||
|
|
||||||
|
# fire event
|
||||||
|
if item['event']
|
||||||
|
console.log( "ws:onmessage event:" + item['event'] )
|
||||||
|
Spine.trigger( item['event'], item['data'] )
|
||||||
|
|
||||||
|
# bind to send messages
|
||||||
|
Spine.bind 'ws:send', (data) =>
|
||||||
|
@send(data)
|
||||||
|
|
||||||
|
@ws.onclose = (e) =>
|
||||||
|
console.log( "onclose", e )
|
||||||
|
|
||||||
|
# show error message
|
||||||
|
if !@error
|
||||||
|
@error = new App.ErrorModal(
|
||||||
|
message: 'No connection to websocket, trying to reconnect...'
|
||||||
|
)
|
||||||
|
|
||||||
|
# try reconnect after 5 sec.
|
||||||
|
@delay @connect, 5000
|
||||||
|
|
||||||
|
@ws.onerror = ->
|
||||||
|
console.log( "onerror" )
|
||||||
|
|
17
app/assets/javascripts/app/views/error.jst.eco
Normal file
17
app/assets/javascripts/app/views/error.jst.eco
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<!--
|
||||||
|
<a href="#" class="close">×</a>
|
||||||
|
-->
|
||||||
|
<h3><%- T('Error') %></h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<p><%= @message %>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<!--
|
||||||
|
<button class="btn btn-primary submit"><%- T('Submit') %></button>
|
||||||
|
<button class="btn cancel"><%- T('Cancel') %></button>
|
||||||
|
-->
|
||||||
|
</div>
|
|
@ -5,7 +5,7 @@
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<% for item in @navbar_left: %>
|
<% for item in @navbar_left: %>
|
||||||
<% if item.child: %>
|
<% if item.child: %>
|
||||||
<li class="dropdown">
|
<li class="dropdown <% if @open_tab[item.target] : %>open<% end %>">
|
||||||
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
|
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<% for item in item.child: %>
|
<% for item in item.child: %>
|
||||||
|
@ -15,12 +15,12 @@
|
||||||
<% if item.navheader: %>
|
<% if item.navheader: %>
|
||||||
<li class="nav-header"><%- T(item.navheader) %></li>
|
<li class="nav-header"><%- T(item.navheader) %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
|
<li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<% else: %>
|
<% else: %>
|
||||||
<li><a href="<%= item.target %>"><%- T(item.name) %></a></li>
|
<li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %></a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
<ul class="nav pull-right">
|
<ul class="nav pull-right">
|
||||||
<% for item in @navbar_right: %>
|
<% for item in @navbar_right: %>
|
||||||
<% if item.child: %>
|
<% if item.child: %>
|
||||||
<li class="dropdown">
|
<li class="dropdown <% if @open_tab[item.target] : %>open<% end %>">
|
||||||
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
|
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<% for item in item.child: %>
|
<% for item in item.child: %>
|
||||||
|
@ -42,12 +42,12 @@
|
||||||
<% if item.navheader: %>
|
<% if item.navheader: %>
|
||||||
<li class="nav-header"><%- T(item.navheader) %></li>
|
<li class="nav-header"><%- T(item.navheader) %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
|
<li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<% else: %>
|
<% else: %>
|
||||||
<li><a href="<%= item.target %>"><%- T(item.name) %></a></li>
|
<li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %></a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -3,56 +3,10 @@ class ActivityController < ApplicationController
|
||||||
|
|
||||||
# GET /activity_stream
|
# GET /activity_stream
|
||||||
def activity_stream
|
def activity_stream
|
||||||
activity_stream = History.activity_stream(current_user, params[:limit])
|
activity_stream = History.activity_stream_fulldata(current_user, params[:limit])
|
||||||
|
|
||||||
# get related users
|
|
||||||
users = {}
|
|
||||||
tickets = []
|
|
||||||
articles = []
|
|
||||||
activity_stream.each {|item|
|
|
||||||
|
|
||||||
# load article ids
|
|
||||||
if item['history_object'] == 'Ticket'
|
|
||||||
ticket = Ticket.find( item['o_id'] ).attributes
|
|
||||||
tickets.push ticket
|
|
||||||
|
|
||||||
# load users
|
|
||||||
if !users[ ticket['owner_id'] ]
|
|
||||||
users[ ticket['owner_id'] ] = user_data_full( ticket['owner_id'] )
|
|
||||||
end
|
|
||||||
if !users[ ticket['customer_id'] ]
|
|
||||||
users[ ticket['customer_id'] ] = user_data_full( ticket['customer_id'] )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if item['history_object'] == 'Ticket::Article'
|
|
||||||
article = Ticket::Article.find( item['o_id'] ).attributes
|
|
||||||
if !article['subject'] || article['subject'] == ''
|
|
||||||
article['subject'] = Ticket.find( article['ticket_id'] ).title
|
|
||||||
end
|
|
||||||
articles.push article
|
|
||||||
|
|
||||||
# load users
|
|
||||||
if !users[ article['created_by_id'] ]
|
|
||||||
users[ article['created_by_id'] ] = user_data_full( article['created_by_id'] )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if item['history_object'] == 'User'
|
|
||||||
users[ item['o_id'] ] = user_data_full( item['o_id'] )
|
|
||||||
end
|
|
||||||
|
|
||||||
# load users
|
|
||||||
if !users[ item['created_by_id'] ]
|
|
||||||
users[ item['created_by_id'] ] = user_data_full( item['created_by_id'] )
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
# return result
|
# return result
|
||||||
render :json => {
|
render :json => activity_stream
|
||||||
:activity_stream => activity_stream,
|
|
||||||
:tickets => tickets,
|
|
||||||
:articles => articles,
|
|
||||||
:users => users,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
|
@ -161,52 +161,7 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_data_full (user_id)
|
def user_data_full (user_id)
|
||||||
|
user = User.user_data_full(user_id)
|
||||||
# get user
|
|
||||||
user = User.find_fulldata(user_id)
|
|
||||||
|
|
||||||
# do not show password
|
|
||||||
user['password'] = ''
|
|
||||||
|
|
||||||
|
|
||||||
# TEMP: compat. reasons
|
|
||||||
user['preferences'] = {} if user['preferences'] == nil
|
|
||||||
|
|
||||||
items = []
|
|
||||||
if user['preferences'][:tickets_open].to_i > 0
|
|
||||||
item = {
|
|
||||||
:url => '',
|
|
||||||
:name => 'open',
|
|
||||||
:count => user['preferences'][:tickets_open] || 0,
|
|
||||||
:title => 'Open Tickets',
|
|
||||||
:class => 'user-tickets',
|
|
||||||
:data => 'open'
|
|
||||||
}
|
|
||||||
items.push item
|
|
||||||
end
|
|
||||||
if user['preferences'][:tickets_closed].to_i > 0
|
|
||||||
item = {
|
|
||||||
:url => '',
|
|
||||||
:name => 'closed',
|
|
||||||
:count => user['preferences'][:tickets_closed] || 0,
|
|
||||||
:title => 'Closed Tickets',
|
|
||||||
:class => 'user-tickets',
|
|
||||||
:data => 'closed'
|
|
||||||
}
|
|
||||||
items.push item
|
|
||||||
end
|
|
||||||
|
|
||||||
# show linked topics and items
|
|
||||||
if items.count > 0
|
|
||||||
topic = {
|
|
||||||
:title => 'Tickets',
|
|
||||||
:items => items,
|
|
||||||
}
|
|
||||||
user['links'] = []
|
|
||||||
user['links'].push topic
|
|
||||||
end
|
|
||||||
|
|
||||||
return user
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,36 +3,10 @@ class RecentViewedController < ApplicationController
|
||||||
|
|
||||||
# GET /recent_viewed
|
# GET /recent_viewed
|
||||||
def recent_viewed
|
def recent_viewed
|
||||||
recent_viewed = History.recent_viewed(current_user)
|
recent_viewed = History.recent_viewed_fulldata(current_user)
|
||||||
|
|
||||||
# get related users
|
|
||||||
users = {}
|
|
||||||
tickets = []
|
|
||||||
recent_viewed.each {|item|
|
|
||||||
|
|
||||||
# load article ids
|
|
||||||
# if item.history_object == 'Ticket'
|
|
||||||
tickets.push Ticket.find( item['o_id'] ).attributes
|
|
||||||
# end
|
|
||||||
# if item.history_object 'Ticket::Article'
|
|
||||||
# tickets.push Ticket::Article.find(item.o_id)
|
|
||||||
# end
|
|
||||||
# if item.history_object 'User'
|
|
||||||
# tickets.push User.find(item.o_id)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# load users
|
|
||||||
if !users[ item['created_by_id'] ]
|
|
||||||
users[ item['created_by_id'] ] = user_data_full( item['created_by_id'] )
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
# return result
|
# return result
|
||||||
render :json => {
|
render :json => recent_viewed
|
||||||
:recent_viewed => recent_viewed,
|
|
||||||
:tickets => tickets,
|
|
||||||
:users => users,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
|
@ -3,33 +3,9 @@ class RssController < ApplicationController
|
||||||
|
|
||||||
# GET /rss_fetch
|
# GET /rss_fetch
|
||||||
def fetch
|
def fetch
|
||||||
url = params[:url]
|
items = RSS.fetch(params[:url], params[:limit])
|
||||||
limit = params[:limit] || 10
|
if items == nil
|
||||||
|
render :json => { :message => "failed to fetch #{ params[:url] }", :status => :unprocessable_entity }
|
||||||
cache_key = 'rss::' + url
|
|
||||||
items = Rails.cache.read( cache_key )
|
|
||||||
if !items
|
|
||||||
response = Net::HTTP.get_response( URI.parse(url) )
|
|
||||||
if response.code.to_s != '200'
|
|
||||||
render :json => { :message => "failed to fetch #{url}, code: #{response.code}"}, :status => :unprocessable_entity
|
|
||||||
return
|
|
||||||
end
|
|
||||||
rss = SimpleRSS.parse response.body
|
|
||||||
items = []
|
|
||||||
fetched = 0
|
|
||||||
rss.items.each { |item|
|
|
||||||
record = {
|
|
||||||
:id => item.id,
|
|
||||||
:title => item.title,
|
|
||||||
:summary => item.summary,
|
|
||||||
:link => item.link,
|
|
||||||
:published => item.published
|
|
||||||
}
|
|
||||||
items.push record
|
|
||||||
fetched += 1
|
|
||||||
break item if fetched == limit.to_i
|
|
||||||
}
|
|
||||||
Rails.cache.write( cache_key, items, :expires_in => 4.hours )
|
|
||||||
end
|
end
|
||||||
render :json => { :items => items }
|
render :json => { :items => items }
|
||||||
end
|
end
|
||||||
|
|
|
@ -136,14 +136,65 @@ class History < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
return datas
|
return datas
|
||||||
end
|
end
|
||||||
|
def self.activity_stream_fulldata(user, limit = 10)
|
||||||
|
activity_stream = History.activity_stream( user, limit )
|
||||||
|
|
||||||
|
# get related users
|
||||||
|
users = {}
|
||||||
|
tickets = []
|
||||||
|
articles = []
|
||||||
|
activity_stream.each {|item|
|
||||||
|
|
||||||
|
# load article ids
|
||||||
|
if item['history_object'] == 'Ticket'
|
||||||
|
ticket = Ticket.find( item['o_id'] ).attributes
|
||||||
|
tickets.push ticket
|
||||||
|
|
||||||
|
# load users
|
||||||
|
if !users[ ticket['owner_id'] ]
|
||||||
|
users[ ticket['owner_id'] ] = User.user_data_full( ticket['owner_id'] )
|
||||||
|
end
|
||||||
|
if !users[ ticket['customer_id'] ]
|
||||||
|
users[ ticket['customer_id'] ] = User.user_data_full( ticket['customer_id'] )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if item['history_object'] == 'Ticket::Article'
|
||||||
|
article = Ticket::Article.find( item['o_id'] ).attributes
|
||||||
|
if !article['subject'] || article['subject'] == ''
|
||||||
|
article['subject'] = Ticket.find( article['ticket_id'] ).title
|
||||||
|
end
|
||||||
|
articles.push article
|
||||||
|
|
||||||
|
# load users
|
||||||
|
if !users[ article['created_by_id'] ]
|
||||||
|
users[ article['created_by_id'] ] = User.user_data_full( article['created_by_id'] )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if item['history_object'] == 'User'
|
||||||
|
users[ item['o_id'] ] = User.user_data_full( item['o_id'] )
|
||||||
|
end
|
||||||
|
|
||||||
|
# load users
|
||||||
|
if !users[ item['created_by_id'] ]
|
||||||
|
users[ item['created_by_id'] ] = User.user_data_full( item['created_by_id'] )
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
:activity_stream => activity_stream,
|
||||||
|
:tickets => tickets,
|
||||||
|
:articles => articles,
|
||||||
|
:users => users,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def self.recent_viewed(user)
|
def self.recent_viewed(user)
|
||||||
# g = Group.where( :active => true ).joins(:users).where( 'users.id' => user.id )
|
# g = Group.where( :active => true ).joins(:users).where( 'users.id' => user.id )
|
||||||
stream = History.select("distinct(histories.o_id), created_by_id, history_attribute_id, history_type_id, history_object_id, value_from, value_to").
|
stream = History.select("distinct(o_id), created_by_id, history_type_id, history_object_id, created_at").
|
||||||
where( :history_object_id => History::Object.where( :name => 'Ticket').first.id ).
|
where( :history_object_id => History::Object.where( :name => 'Ticket').first.id ).
|
||||||
where( :history_type_id => History::Type.where( :name => ['viewed']) ).
|
where( :history_type_id => History::Type.where( :name => ['viewed'] ) ).
|
||||||
where( :created_by_id => user.id ).
|
where( :created_by_id => user.id ).
|
||||||
order('created_at DESC, id DESC').
|
order('created_at DESC, id ASC').
|
||||||
limit(10)
|
limit(10)
|
||||||
datas = []
|
datas = []
|
||||||
stream.each do |item|
|
stream.each do |item|
|
||||||
|
@ -153,9 +204,49 @@ class History < ActiveRecord::Base
|
||||||
datas.push data
|
datas.push data
|
||||||
# item['history_attribute'] = item.history_attribute
|
# item['history_attribute'] = item.history_attribute
|
||||||
end
|
end
|
||||||
|
# puts 'pppppppppp'
|
||||||
|
# puts datas.inspect
|
||||||
return datas
|
return datas
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.recent_viewed_fulldata(user)
|
||||||
|
recent_viewed = History.recent_viewed(user)
|
||||||
|
|
||||||
|
# get related users
|
||||||
|
users = {}
|
||||||
|
tickets = []
|
||||||
|
recent_viewed.each {|item|
|
||||||
|
|
||||||
|
# load article ids
|
||||||
|
# if item.history_object == 'Ticket'
|
||||||
|
ticket = Ticket.find( item['o_id'] ).attributes
|
||||||
|
tickets.push ticket
|
||||||
|
# end
|
||||||
|
# if item.history_object 'Ticket::Article'
|
||||||
|
# tickets.push Ticket::Article.find(item.o_id)
|
||||||
|
# end
|
||||||
|
# if item.history_object 'User'
|
||||||
|
# tickets.push User.find(item.o_id)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# load users
|
||||||
|
if !users[ ticket['owner_id'] ]
|
||||||
|
users[ ticket['owner_id'] ] = User.user_data_full( ticket['owner_id'] )
|
||||||
|
end
|
||||||
|
if !users[ ticket['created_by_id'] ]
|
||||||
|
users[ ticket['created_by_id'] ] = User.user_data_full( ticket['created_by_id'] )
|
||||||
|
end
|
||||||
|
if !users[ item['created_by_id'] ]
|
||||||
|
users[ item['created_by_id'] ] = User.user_data_full( item['created_by_id'] )
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
:recent_viewed => recent_viewed,
|
||||||
|
:tickets => tickets,
|
||||||
|
:users => users,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def check_type
|
def check_type
|
||||||
puts '--------------'
|
puts '--------------'
|
||||||
|
|
|
@ -123,7 +123,6 @@ class Ticket < ActiveRecord::Base
|
||||||
return subject
|
return subject
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Ticket.overview(
|
# Ticket.overview(
|
||||||
# :view => 'some_view_url',
|
# :view => 'some_view_url',
|
||||||
# :current_user_id => 123,
|
# :current_user_id => 123,
|
||||||
|
|
|
@ -207,6 +207,54 @@ Your #{config.product_name} Team
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.user_data_full (user_id)
|
||||||
|
|
||||||
|
# get user
|
||||||
|
user = User.find_fulldata(user_id)
|
||||||
|
|
||||||
|
# do not show password
|
||||||
|
user['password'] = ''
|
||||||
|
|
||||||
|
# TEMP: compat. reasons
|
||||||
|
user['preferences'] = {} if user['preferences'] == nil
|
||||||
|
|
||||||
|
items = []
|
||||||
|
if user['preferences'][:tickets_open].to_i > 0
|
||||||
|
item = {
|
||||||
|
:url => '',
|
||||||
|
:name => 'open',
|
||||||
|
:count => user['preferences'][:tickets_open] || 0,
|
||||||
|
:title => 'Open Tickets',
|
||||||
|
:class => 'user-tickets',
|
||||||
|
:data => 'open'
|
||||||
|
}
|
||||||
|
items.push item
|
||||||
|
end
|
||||||
|
if user['preferences'][:tickets_closed].to_i > 0
|
||||||
|
item = {
|
||||||
|
:url => '',
|
||||||
|
:name => 'closed',
|
||||||
|
:count => user['preferences'][:tickets_closed] || 0,
|
||||||
|
:title => 'Closed Tickets',
|
||||||
|
:class => 'user-tickets',
|
||||||
|
:data => 'closed'
|
||||||
|
}
|
||||||
|
items.push item
|
||||||
|
end
|
||||||
|
|
||||||
|
# show linked topics and items
|
||||||
|
if items.count > 0
|
||||||
|
topic = {
|
||||||
|
:title => 'Tickets',
|
||||||
|
:items => items,
|
||||||
|
}
|
||||||
|
user['links'] = []
|
||||||
|
user['links'].push topic
|
||||||
|
end
|
||||||
|
|
||||||
|
return user
|
||||||
|
end
|
||||||
|
|
||||||
# update all users geo data
|
# update all users geo data
|
||||||
def self.geo_update_all
|
def self.geo_update_all
|
||||||
User.all.each { |user|
|
User.all.each { |user|
|
||||||
|
|
|
@ -13,8 +13,11 @@ require 'google_oauth2_database'
|
||||||
# load notification factory (replace all tags)
|
# load notification factory (replace all tags)
|
||||||
require 'notification_factory'
|
require 'notification_factory'
|
||||||
|
|
||||||
# load gmaps lookup
|
# load lib
|
||||||
require 'gmaps'
|
require 'gmaps'
|
||||||
|
require 'rss'
|
||||||
|
|
||||||
|
require 'web_socket'
|
||||||
|
|
||||||
# Initialize the rails application
|
# Initialize the rails application
|
||||||
Zammad::Application.initialize!
|
Zammad::Application.initialize!
|
||||||
|
|
31
lib/rss.rb
Normal file
31
lib/rss.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module RSS
|
||||||
|
def self.fetch(url, limit = 10)
|
||||||
|
cache_key = 'rss::' + url
|
||||||
|
items = Rails.cache.read( cache_key )
|
||||||
|
if !items
|
||||||
|
puts 'fetch rss...'
|
||||||
|
response = Net::HTTP.get_response( URI.parse(url) )
|
||||||
|
if response.code.to_s != '200'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rss = SimpleRSS.parse response.body
|
||||||
|
items = []
|
||||||
|
fetched = 0
|
||||||
|
rss.items.each { |item|
|
||||||
|
record = {
|
||||||
|
:id => item.id,
|
||||||
|
:title => item.title,
|
||||||
|
:summary => item.summary,
|
||||||
|
:link => item.link,
|
||||||
|
:published => item.published
|
||||||
|
}
|
||||||
|
items.push record
|
||||||
|
fetched += 1
|
||||||
|
break item if fetched == limit.to_i
|
||||||
|
}
|
||||||
|
Rails.cache.write( cache_key, items, :expires_in => 4.hours )
|
||||||
|
end
|
||||||
|
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
end
|
175
lib/web_socket.rb
Normal file
175
lib/web_socket.rb
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
module Session
|
||||||
|
@path = '/tmp/websocket'
|
||||||
|
|
||||||
|
def self.create( client_id, session )
|
||||||
|
path = @path + '/' + client_id.to_s
|
||||||
|
FileUtils.mkpath path
|
||||||
|
File.open( path + '/session', 'w' ) { |file|
|
||||||
|
user = { :id => session['id'] }
|
||||||
|
file.puts Marshal.dump(user)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.get( client_id )
|
||||||
|
session_file = @path + '/' + client_id.to_s + '/session'
|
||||||
|
data = nil
|
||||||
|
return if !File.exist? session_file
|
||||||
|
File.open( session_file, 'r' ) { |file|
|
||||||
|
all = ''
|
||||||
|
while line = file.gets
|
||||||
|
all = all + line
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
data = Marshal.load( all )
|
||||||
|
rescue
|
||||||
|
return
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.transaction( client_id, data )
|
||||||
|
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s
|
||||||
|
if File::exists?( filename )
|
||||||
|
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-1'
|
||||||
|
if File::exists?( filename )
|
||||||
|
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-2'
|
||||||
|
if File::exists?( filename )
|
||||||
|
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-3'
|
||||||
|
if File::exists?( filename )
|
||||||
|
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-4'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
File.open( filename, 'w' ) { |file|
|
||||||
|
file.puts data.to_json
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.jobs
|
||||||
|
state_client_ids = {}
|
||||||
|
while true
|
||||||
|
client_ids = self.sessions
|
||||||
|
client_ids.each { |client_id|
|
||||||
|
|
||||||
|
if !state_client_ids[client_id]
|
||||||
|
state_client_ids[client_id] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# get current user
|
||||||
|
user_session = Session.get( client_id )
|
||||||
|
next if !user_session
|
||||||
|
next if !user_session[:id]
|
||||||
|
user = User.find( user_session[:id] )
|
||||||
|
|
||||||
|
# overviews
|
||||||
|
result = Ticket.overview(
|
||||||
|
:current_user_id => user.id,
|
||||||
|
)
|
||||||
|
if state_client_ids[client_id][:overview] != result
|
||||||
|
state_client_ids[client_id][:overview] = result
|
||||||
|
|
||||||
|
# send update to browser
|
||||||
|
Session.transaction( client_id, {
|
||||||
|
:action => 'load',
|
||||||
|
:data => result,
|
||||||
|
:event => 'navupdate_ticket_overview',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
# recent viewed
|
||||||
|
recent_viewed = History.recent_viewed(user)
|
||||||
|
if state_client_ids[client_id][:recent_viewed] != recent_viewed
|
||||||
|
state_client_ids[client_id][:recent_viewed] = recent_viewed
|
||||||
|
|
||||||
|
# tickets and users
|
||||||
|
recent_viewed = History.recent_viewed_fulldata(user)
|
||||||
|
|
||||||
|
# send update to browser
|
||||||
|
Session.transaction( client_id, {
|
||||||
|
:action => 'load',
|
||||||
|
:data => recent_viewed,
|
||||||
|
:event => 'update_recent_viewed',
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
# activity stream
|
||||||
|
activity_stream = History.activity_stream(user)
|
||||||
|
if state_client_ids[client_id][:activity_stream] != activity_stream
|
||||||
|
state_client_ids[client_id][:activity_stream] = activity_stream
|
||||||
|
|
||||||
|
activity_stream = History.activity_stream_fulldata(user)
|
||||||
|
|
||||||
|
# send update to browser
|
||||||
|
Session.transaction( client_id, {
|
||||||
|
:event => 'activity_stream_rebuild',
|
||||||
|
:collection => 'activity_stream',
|
||||||
|
:data => activity_stream,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
# rss view
|
||||||
|
rss_items = RSS.fetch( 'http://www.heise.de/newsticker/heise-atom.xml', 8 )
|
||||||
|
if state_client_ids[client_id][:rss_items] != rss_items
|
||||||
|
state_client_ids[client_id][:rss_items] = rss_items
|
||||||
|
|
||||||
|
# send update to browser
|
||||||
|
Session.transaction( client_id, {
|
||||||
|
:event => 'rss_rebuild',
|
||||||
|
:collection => 'dashboard_rss',
|
||||||
|
:data => {
|
||||||
|
head: 'Heise ATOM',
|
||||||
|
items: rss_items,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.sessions
|
||||||
|
path = @path + '/'
|
||||||
|
data = []
|
||||||
|
Dir.foreach( path ) do |entry|
|
||||||
|
if entry != '.' && entry != '..'
|
||||||
|
data.push entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.queue( client_id )
|
||||||
|
path = @path + '/' + client_id.to_s + '/'
|
||||||
|
data = []
|
||||||
|
Dir.foreach( path ) do |entry|
|
||||||
|
if /^transaction/.match( entry )
|
||||||
|
data.push Session.queue_file( path + entry )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.queue_file( filename )
|
||||||
|
data = nil
|
||||||
|
File.open( filename, 'r' ) { |file|
|
||||||
|
all = ''
|
||||||
|
while line = file.gets
|
||||||
|
all = all + line
|
||||||
|
end
|
||||||
|
data = JSON.parse( all )
|
||||||
|
}
|
||||||
|
File.delete( filename )
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.destory( client_id )
|
||||||
|
path = @path + '/' + client_id.to_s
|
||||||
|
FileUtils.rm_rf path
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
86
script/websocket-server.rb
Normal file
86
script/websocket-server.rb
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
$LOAD_PATH << './lib'
|
||||||
|
require 'rubygems'
|
||||||
|
require 'eventmachine'
|
||||||
|
require 'em-websocket'
|
||||||
|
require 'json'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'web_socket'
|
||||||
|
require 'optparse'
|
||||||
|
|
||||||
|
# Look for -o with argument, and -I and -D boolean arguments
|
||||||
|
options = {
|
||||||
|
:p => 6042,
|
||||||
|
:b => '0.0.0.0',
|
||||||
|
}
|
||||||
|
OptionParser.new do |opts|
|
||||||
|
opts.banner = "Usage: websocket-server.rb [options]"
|
||||||
|
|
||||||
|
opts.on("-p", "--port [OPT]", "port of websocket server") do |p|
|
||||||
|
options[:p] = p
|
||||||
|
end
|
||||||
|
opts.on("-b", "--bind [OPT]", "bind address") do |b|
|
||||||
|
options[:b] = b
|
||||||
|
end
|
||||||
|
end.parse!
|
||||||
|
|
||||||
|
puts "Starting websocket server on #{ options[:b] }:#{ options[:p] }"
|
||||||
|
|
||||||
|
@clients = {}
|
||||||
|
EventMachine.run {
|
||||||
|
EventMachine::WebSocket.start( :host => options[:b], :port => options[:p] ) do |ws|
|
||||||
|
|
||||||
|
# register client connection
|
||||||
|
ws.onopen {
|
||||||
|
client_id = ws.object_id
|
||||||
|
puts 'Client ' + client_id.to_s + ' connected'
|
||||||
|
|
||||||
|
if !@clients.include? client_id
|
||||||
|
@clients[client_id] = {
|
||||||
|
:websocket => ws,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
# unregister client connection
|
||||||
|
ws.onclose {
|
||||||
|
client_id = ws.object_id
|
||||||
|
puts 'Client ' + client_id.to_s + ' disconnected'
|
||||||
|
|
||||||
|
if @clients.include? client_id
|
||||||
|
@clients.delete client_id
|
||||||
|
end
|
||||||
|
Session.destory( client_id )
|
||||||
|
}
|
||||||
|
|
||||||
|
# manage messages
|
||||||
|
ws.onmessage { |msg|
|
||||||
|
|
||||||
|
client_id = ws.object_id
|
||||||
|
puts 'From Client ' + client_id.to_s + ' received message: ' + msg
|
||||||
|
data = JSON.parse(msg)
|
||||||
|
|
||||||
|
# get session
|
||||||
|
if data['action'] == 'login'
|
||||||
|
@clients[client_id][:session] = data['session']
|
||||||
|
Session.create( client_id, data['session'] )
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
EventMachine.add_periodic_timer(0.4) {
|
||||||
|
# puts "loop"
|
||||||
|
@clients.each { |client_id, client|
|
||||||
|
# puts 'checking client...' + client_id.to_s
|
||||||
|
begin
|
||||||
|
queue = Session.queue( client_id )
|
||||||
|
if queue && queue[0]
|
||||||
|
puts "send to #{client_id} " + queue.inspect
|
||||||
|
client[:websocket].send( queue.to_json )
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
puts 'problem'
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue