diff --git a/Gemfile b/Gemfile
index ee1d14e34..c805a5c45 100644
--- a/Gemfile
+++ b/Gemfile
@@ -37,9 +37,9 @@ gem 'daemons'
gem 'simple-rss'
# e. g. on linux we need a javascript execution
-# gem 'libv8', '~> 3.11.8'
-# gem 'execjs'
-# gem 'therubyracer'
+gem 'libv8', '~> 3.11.8'
+gem 'execjs'
+gem 'therubyracer'
# e. g. for mysql you need to load mysql
gem 'mysql2'
diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee
index 342e8aefb..d597a65ff 100644
--- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee
+++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee
@@ -657,9 +657,13 @@ class App.ControllerForm extends App.Controller
item = $( App.view('generic/textarea')( attribute: attribute ) + '
' )
a = =>
- $( item[0] ).expanding()
- $( item[0] ).on('focus', ->
+ visible = $( item[0] ).is(":visible")
+ if visible && !$( item[0] ).expanding('active')
$( item[0] ).expanding()
+ $( item[0] ).on('focus', ->
+ visible = $( item[0] ).is(":visible")
+ if visible && !$( item[0] ).expanding('active')
+ $( item[0] ).expanding()
)
App.Delay.set( a, 80 )
diff --git a/app/assets/javascripts/app/controllers/_dashboard/ticket_search.js.coffee b/app/assets/javascripts/app/controllers/_dashboard/ticket_search.js.coffee
new file mode 100644
index 000000000..f2288d253
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_dashboard/ticket_search.js.coffee
@@ -0,0 +1,132 @@
+class App.DashboardTicketSearch extends App.Controller
+ events:
+ 'click [data-type=page]': 'page'
+
+ constructor: ->
+ super
+ @start_page = 1
+ @navupdate '#'
+
+ # render
+ @fetch()
+
+ fetch: (force) =>
+
+ @ajax(
+ id: 'dashboard_ticket_search' + @name,
+ type: 'GET',
+ url: @apiPath + '/tickets/search',
+ data:
+ condition: @condition
+ order: @order
+ detail: true
+ limit: 200
+ processData: true,
+ success: (data) =>
+
+ @load( data, true )
+ )
+
+ load: (data = false, ajax = false) =>
+
+ if ajax
+ App.Store.write( 'dashboard_ticket_search' + @name, data )
+
+ # load assets
+ App.Collection.loadAssets( data.assets )
+
+ @render( data )
+
+ else
+ data = App.Store.get( 'dashboard_ticket_search' + @name )
+ if data
+ @render( data )
+
+
+ render: (data) ->
+ return if !data
+ return if !data.tickets
+
+ @overview =
+ name: @name
+ @tickets_count = data.tickets_count
+ @ticket_ids = data.tickets
+ per_page = @per_page || 5
+ pages_total = parseInt( ( @tickets_count / per_page ) + 0.99999 ) || 1
+ html = App.view('dashboard/ticket')(
+ overview: @overview,
+ pages_total: pages_total,
+ start_page: @start_page,
+ )
+ html = $(html)
+ html.find('li').removeClass('active')
+ html.find(".page [data-id=\"#{@start_page}\"]").parents('li').addClass('active')
+
+ @tickets_in_table = []
+ start = ( @start_page-1 ) * 5
+ end = ( @start_page ) * 5
+ i = start
+ while i < end
+ i = i + 1
+ if @ticket_ids[ i - 1 ]
+ @tickets_in_table.push App.Ticket.fullLocal( @ticket_ids[ i - 1 ] )
+
+ openTicket = (id,e) =>
+ ticket = App.Ticket.fullLocal(id)
+ @navigate ticket.uiUrl()
+ callbackTicketTitleAdd = (value, object, attribute, attributes, refObject) =>
+ attribute.title = object.title
+ value
+ callbackLinkToTicket = (value, object, attribute, attributes, refObject) =>
+ attribute.link = object.uiUrl()
+ value
+ callbackResetLink = (value, object, attribute, attributes, refObject) =>
+ attribute.link = undefined
+ value
+ callbackUserPopover = (value, object, attribute, attributes, refObject) =>
+ attribute.class = 'user-popover'
+ attribute.data =
+ id: refObject.id
+ value
+
+ new App.ControllerTable(
+ overview: @view.d
+ el: html.find('.table-overview'),
+ model: App.Ticket
+ objects: @tickets_in_table,
+ checkbox: false
+ groupBy: @group_by
+ bindRow:
+ events:
+ 'click': openTicket
+ callbackAttributes:
+ customer_id:
+ [ callbackResetLink, callbackUserPopover ]
+ owner_id:
+ [ callbackResetLink, callbackUserPopover ]
+ title:
+ [ callbackLinkToTicket, callbackTicketTitleAdd ]
+ number:
+ [ callbackLinkToTicket, callbackTicketTitleAdd ]
+ )
+
+ @html html
+
+ # show frontend times
+ @frontendTimeUpdate()
+
+ # start user popups
+ @userPopups()
+
+ zoom: (e) =>
+ e.preventDefault()
+ id = $(e.target).parents('[data-id]').data('id')
+
+ @navigate 'ticket/zoom/' + id
+
+ page: (e) =>
+ e.preventDefault()
+ id = $(e.target).data('id')
+ @start_page = id
+ @load()
+
diff --git a/app/assets/javascripts/app/controllers/organization_zoom.js.coffee b/app/assets/javascripts/app/controllers/organization_zoom.js.coffee
index f1f50a851..027f370a7 100644
--- a/app/assets/javascripts/app/controllers/organization_zoom.js.coffee
+++ b/app/assets/javascripts/app/controllers/organization_zoom.js.coffee
@@ -38,6 +38,11 @@ class App.OrganizationZoom extends App.Controller
organization: organization
)
+ new Overviews(
+ el: @el
+ organization: organization
+ )
+
new App.UpdateTastbar(
genericObject: organization
)
@@ -60,6 +65,55 @@ class App.OrganizationZoom extends App.Controller
ui: @
)
+class Overviews extends App.Controller
+ constructor: ->
+ super
+
+ # subscribe and reload data / fetch new data if triggered
+ @subscribeId = App.Organization.full( @organization.id, @render, false, true )
+
+ release: =>
+ App.Organization.unsubscribe(@subscribeId)
+
+ render: (organization) =>
+
+ plugins =
+ main:
+ my_organization:
+ controller: App.DashboardTicketSearch,
+ params:
+ name: 'Tickets of Organization'
+ condition:
+ 'tickets.state_id': [ 1,2,3,4,6 ]
+ 'tickets.organization_id': organization.id
+ order:
+ by: 'created_at'
+ direction: 'DESC'
+ view:
+ d: [ 'number', 'title', 'customer', 'state', 'priority', 'created_at' ]
+ view_mode_default: 'd'
+
+ for area, plugins of plugins
+ for name, plugin of plugins
+ target = area + '_' + name
+ @el.find('.' + area + '-overviews').append('')
+ if plugin.controller
+ params = plugin.params || {}
+ params.el = @el.find( '#' + target )
+ new plugin.controller( params )
+
+ dndOptions =
+ handle: 'h2.can-move'
+ placeholder: 'can-move-plcaeholder'
+ tolerance: 'pointer'
+ distance: 15
+ opacity: 0.6
+ forcePlaceholderSize: true
+
+ @el.find( '#sortable' ).sortable( dndOptions )
+ @el.find( '#sortable-sidebar' ).sortable( dndOptions )
+
+
class Widgets extends App.Controller
constructor: ->
super
diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
index e8d169326..fc89d6846 100644
--- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
+++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee
@@ -157,14 +157,6 @@ class App.TicketZoom extends App.Controller
if !@editWidget || _.isEmpty( App.TaskManager.get(@task_key).state )
@editWidget = @Edit()
- # show text module UI
- if !@isRole('Customer')
- new App.WidgetTextModule(
- el: @el.find('textarea')
- data:
- ticket: @ticket
- )
-
# scroll to article if given
if @article_id && document.getElementById( 'article-' + @article_id )
offset = document.getElementById( 'article-' + @article_id ).offsetTop
@@ -331,6 +323,8 @@ class Edit extends App.Controller
release: =>
@autosaveStop()
+ if @subscribeIdTextModule
+ App.Ticket.unsubscribe(@subscribeIdTextModule)
render: ->
@@ -429,6 +423,19 @@ class Edit extends App.Controller
# enable user popups
@userPopups()
+ # show text module UI
+ if !@isRole('Customer')
+ textModule = new App.WidgetTextModule(
+ el: @el.find('textarea')
+ data:
+ ticket: ticket
+ )
+ callback = (ticket) =>
+ textModule.reload(
+ ticket: ticket
+ )
+ @subscribeIdTextModule = ticket.subscribe( callback )
+
autosaveStop: =>
@clearInterval( 'autosave' )
diff --git a/app/assets/javascripts/app/controllers/user_zoom.js.coffee b/app/assets/javascripts/app/controllers/user_zoom.js.coffee
index 3aca70da5..b39212aea 100644
--- a/app/assets/javascripts/app/controllers/user_zoom.js.coffee
+++ b/app/assets/javascripts/app/controllers/user_zoom.js.coffee
@@ -36,10 +36,16 @@ class App.UserZoom extends App.Controller
render: (user) =>
+
@html App.view('user_zoom')(
user: user
)
+ new Overviews(
+ el: @el
+ user: user
+ )
+
new App.UpdateTastbar(
genericObject: user
)
@@ -62,6 +68,74 @@ class App.UserZoom extends App.Controller
ui: @
)
+class Overviews extends App.Controller
+ constructor: ->
+ super
+
+ # subscribe and reload data / fetch new data if triggered
+ @subscribeId = App.User.full( @user.id, @render, false, true )
+
+ release: =>
+ App.User.unsubscribe(@subscribeId)
+
+ render: (user) =>
+
+ plugins = {
+ main: {
+ my_assigned: {
+ controller: App.DashboardTicketSearch,
+ params: {
+ name: 'Tickets of User'
+ condition:
+ 'tickets.state_id': [ 1,2,3,4,6 ]
+ 'tickets.customer_id': user.id
+ order:
+ by: 'created_at'
+ direction: 'DESC'
+ view:
+ d: [ 'number', 'title', 'state', 'priority', 'created_at' ]
+ view_mode_default: 'd'
+ },
+ },
+ },
+ }
+ if user.organization_id
+ plugins.main.my_organization = {
+ controller: App.DashboardTicketSearch,
+ params: {
+ name: 'Tickets of Organization'
+ condition:
+ 'tickets.state_id': [ 1,2,3,4,6 ]
+ 'tickets.organization_id': user.organization_id
+ order:
+ by: 'created_at'
+ direction: 'DESC'
+ view:
+ d: [ 'number', 'title', 'customer', 'state', 'priority', 'created_at' ]
+ view_mode_default: 'd'
+ },
+ }
+
+ for area, plugins of plugins
+ for name, plugin of plugins
+ target = area + '_' + name
+ @el.find('.' + area + '-overviews').append('')
+ if plugin.controller
+ params = plugin.params || {}
+ params.el = @el.find( '#' + target )
+ new plugin.controller( params )
+
+ dndOptions =
+ handle: 'h2.can-move'
+ placeholder: 'can-move-plcaeholder'
+ tolerance: 'pointer'
+ distance: 15
+ opacity: 0.6
+ forcePlaceholderSize: true
+
+ @el.find( '#sortable' ).sortable( dndOptions )
+ @el.find( '#sortable-sidebar' ).sortable( dndOptions )
+
class Widgets extends App.Controller
constructor: ->
super
diff --git a/app/assets/javascripts/app/controllers/widget/organization.js.coffee b/app/assets/javascripts/app/controllers/widget/organization.js.coffee
index fb0b59445..82a386080 100644
--- a/app/assets/javascripts/app/controllers/widget/organization.js.coffee
+++ b/app/assets/javascripts/app/controllers/widget/organization.js.coffee
@@ -42,11 +42,15 @@ class App.WidgetOrganization extends App.Controller
)
a = =>
- @el.find('textarea').expanding()
- @el.find('textarea').on('focus', =>
+ visible = @el.find('textarea').is(":visible")
+ if visible && !@el.find('textarea').expanding('active')
@el.find('textarea').expanding()
+ @el.find('textarea').on('focus', (e) =>
+ visible = @el.find('textarea').is(":visible")
+ if visible && !@el.find('textarea').expanding('active')
+ @el.find('textarea').expanding()
)
- @delay( a, 80 )
+ @delay( a, 40 )
# enable user popups
@userPopups()
diff --git a/app/assets/javascripts/app/controllers/widget/user.js.coffee b/app/assets/javascripts/app/controllers/widget/user.js.coffee
index 23d41cd65..ac8b48d0d 100644
--- a/app/assets/javascripts/app/controllers/widget/user.js.coffee
+++ b/app/assets/javascripts/app/controllers/widget/user.js.coffee
@@ -14,6 +14,10 @@ class App.WidgetUser extends App.ControllerDrox
render: (user) =>
+ # execute callback on render/rerender
+ if @callback
+ @callback(user)
+
# get display data
userData = []
for item2 in App.User.configure_attributes
@@ -72,11 +76,15 @@ class App.WidgetUser extends App.ControllerDrox
)
a = =>
- @el.find('textarea').expanding()
- @el.find('textarea').on('focus', =>
+ visible = @el.find('textarea').is(":visible")
+ if visible && !@el.find('textarea').expanding('active')
@el.find('textarea').expanding()
+ @el.find('textarea').on('focus', (e) =>
+ visible = @el.find('textarea').is(":visible")
+ if visible && !@el.find('textarea').expanding('active')
+ @el.find('textarea').expanding()
)
- @delay( a, 80 )
+ @delay( a, 40 )
@userTicketPopups(
selector: '.user-tickets'
diff --git a/app/assets/javascripts/app/models/_application_model.js.coffee b/app/assets/javascripts/app/models/_application_model.js.coffee
index 67218ebff..b47641675 100644
--- a/app/assets/javascripts/app/models/_application_model.js.coffee
+++ b/app/assets/javascripts/app/models/_application_model.js.coffee
@@ -300,7 +300,7 @@ class App.Model extends Spine.Model
# subscribe and render data after local change
@bind(
- 'refresh change'
+ 'change'
(items) =>
# check if result is array or singel item
@@ -310,7 +310,26 @@ class App.Model extends Spine.Model
for item in items
for key, callback of App[ @className ].SUBSCRIPTION_ITEM[ item.id ]
item = App[ @className ]._fillUp( item )
- callback(item)
+ callback(item, 'change')
+ )
+
+ @changeTable = {}
+ @bind(
+ 'refresh'
+ (items) =>
+
+ # check if result is array or singel item
+ if !_.isArray(items)
+ items = [items]
+
+ for item in items
+ for key, callback of App[ @className ].SUBSCRIPTION_ITEM[ item.id ]
+
+ # only trigger callbacks if object has changed
+ if !@changeTable[key] || @changeTable[key] isnt item.updated_at
+ @changeTable[key] = item.updated_at
+ item = App[ @className ]._fillUp( item )
+ callback(item, 'refresh')
)
# subscribe and render data after server change
diff --git a/app/assets/javascripts/app/views/organization_zoom.jst.eco b/app/assets/javascripts/app/views/organization_zoom.jst.eco
index 3bca91fd7..c58b66d87 100644
--- a/app/assets/javascripts/app/views/organization_zoom.jst.eco
+++ b/app/assets/javascripts/app/views/organization_zoom.jst.eco
@@ -5,7 +5,9 @@
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/user_zoom.jst.eco b/app/assets/javascripts/app/views/user_zoom.jst.eco
index 5b011cb50..c58b66d87 100644
--- a/app/assets/javascripts/app/views/user_zoom.jst.eco
+++ b/app/assets/javascripts/app/views/user_zoom.jst.eco
@@ -4,8 +4,10 @@
\ No newline at end of file
diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb
index 326ccde40..05c2e7837 100644
--- a/app/controllers/tickets_controller.rb
+++ b/app/controllers/tickets_controller.rb
@@ -364,7 +364,9 @@ class TicketsController < ApplicationController
tickets = Ticket.search(
:limit => params[:limit],
:query => params[:term],
+ :condition => params[:condition],
:current_user => current_user,
+ :detail => params[:detail]
)
assets = {}
ticket_result = []
@@ -375,8 +377,9 @@ class TicketsController < ApplicationController
# return result
render :json => {
- :tickets => ticket_result,
- :assets => assets,
+ :tickets => ticket_result,
+ :tickets_count => tickets.count,
+ :assets => assets,
}
end
diff --git a/app/models/ticket/search.rb b/app/models/ticket/search.rb
index 14abbacb2..0d9b5c113 100644
--- a/app/models/ticket/search.rb
+++ b/app/models/ticket/search.rb
@@ -16,6 +16,20 @@ returns
result = [ticket_model1, ticket_model2]
+
+search tickets
+
+ result = Ticket.search(
+ :current_user => User.find(123),
+ :query => 'search something',
+ :limit => 15,
+ :full => 0
+ )
+
+returns
+
+ result = [1,3,5,6,7]
+
=end
def search (params)
@@ -24,9 +38,13 @@ returns
query = params[:query]
limit = params[:limit] || 12
current_user = params[:current_user]
+ full = false
+ if params[:full] || !params.has_key?(:full)
+ full = true
+ end
# try search index backend
- if SearchIndexBackend.enabled?
+ if !params[:detail] && SearchIndexBackend.enabled?
query_extention = {}
query_extention['bool'] = {}
query_extention['bool']['must'] = []
@@ -39,28 +57,31 @@ returns
groups.each {|group|
group_condition.push group.name
}
- condition = {
+ access_condition = {
'query_string' => { 'default_field' => 'Ticket.group.name', 'query' => "\"#{group_condition.join('" OR "')}\"" }
}
- query_extention['bool']['must'].push condition
+ query_extention['bool']['must'].push access_condition
else
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
- condition = {
+ access_condition = {
'query_string' => { 'default_field' => 'Ticket.customer_id', 'query' => current_user.id }
}
# customer_id: XXX
# conditions = [ 'customer_id = ?', current_user.id ]
else
- condition = {
+ access_condition = {
'query_string' => { 'query' => "Ticket.customer_id:#{current_user.id} OR Ticket.organization_id:#{current_user.organization.id}" }
}
# customer_id: XXX OR organization_id: XXX
# conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
end
- query_extention['bool']['must'].push condition
+ query_extention['bool']['must'].push access_condition
end
ids = SearchIndexBackend.search( query, limit, 'Ticket', query_extention )
+ if !full
+ return ids
+ end
tickets = []
ids.each { |id|
tickets.push Ticket.lookup( :id => id )
@@ -69,38 +90,53 @@ returns
end
# fallback do sql query
- conditions = []
+ access_condition = []
if current_user.is_role('Agent')
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', current_user.id ).
where( 'groups.active = ?', true ).
map( &:id )
- conditions = [ 'group_id IN (?)', group_ids ]
+ access_condition = [ 'group_id IN (?)', group_ids ]
else
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
- conditions = [ 'customer_id = ?', current_user.id ]
+ access_condition = [ 'customer_id = ?', current_user.id ]
else
- conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
+ access_condition = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
end
end
# do query
# - stip out * we already search for *query* -
- query.gsub! '*', ''
- tickets_all = Ticket.select('DISTINCT(tickets.id)').
- where(conditions).
- where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
- joins(:articles).
- order('`tickets`.`created_at` DESC').
- limit(limit)
-
- # build result list
- tickets = []
- tickets_all.each do |ticket|
- tickets.push Ticket.lookup( :id => ticket.id )
+ if query
+ query.gsub! '*', ''
+ tickets_all = Ticket.select('DISTINCT(tickets.id)').
+ where(access_condition).
+ where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
+ joins(:articles).
+ order('`tickets`.`created_at` DESC').
+ limit(limit)
+ else
+ tickets_all = Ticket.select('DISTINCT(tickets.id)').
+ where(access_condition).
+ where(params[:condition]).
+ order('`tickets`.`created_at` DESC').
+ limit(limit)
end
- tickets
+ # build result list
+ if !full
+ ids = []
+ tickets_all.each { |ticket|
+ ids.push ticket.id
+ }
+ return ids
+ end
+
+ tickets = []
+ tickets_all.each { |ticket|
+ tickets.push Ticket.lookup( :id => ticket.id )
+ }
+ return tickets
end
end
diff --git a/script/init.d/zammad b/script/init.d/zammad
new file mode 100755
index 000000000..a0470b145
--- /dev/null
+++ b/script/init.d/zammad
@@ -0,0 +1,151 @@
+#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides: zammad
+# Required-Start: $local_fs $remote_fs $network $syslog
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Zammad application
+# Description: Zammad application
+### END INIT INFO
+
+
+APP_ROOT="/home/zammad/zammad"
+PID_PATH="$APP_ROOT/tmp/pids"
+WEB_SERVER_PID="$PID_PATH/puma.pid"
+WEBSOCKET_SERVER_PID="$PID_PATH/websocket.pid"
+SCHEDULER_SERVER_PID="$PID_PATH/websocket.pid"
+
+APP_USER="zammad"
+PUMA_OPTS="-p 3000 -d -e production --pidfile $WEB_SERVER_PID"
+WEBSOCKET_OPTS="-d"
+SCHEDULER_OPTS=""
+
+NAME="zammad"
+DESC="Zammad application"
+
+
+check_daemons() {
+ PUMA_PID=-1
+ PUMA_OK=1
+ if [ -f $WEB_SERVER_PID ]; then
+ PUMA_PID=$(pgrep -F $WEB_SERVER_PID)
+ PUMA_OK=$?
+ fi
+ WEBSOCKET_PID=-1
+ WEBSOCKET_OK=1
+ if [ -f $WEBSOCKET_SERVER_PID ]; then
+ WEBSOCKET_PID=$(pgrep -F $WEBSOCKET_SERVER_PID)
+ WEBSOCKET_OK=$?
+ fi
+ SCHEDULER_PID=1
+ SCHEDULER_OK=1
+ if [ -f $SCHEDULER_SERVER_PID ]; then
+ SCHEDULER_PID=$(pgrep -F $SCHEDULER_SERVER_PID)
+ SCHEDULER_OK=$?
+ fi
+}
+
+execute() {
+ sudo -u $APP_USER -H bash -l -c "$1"
+}
+
+start() {
+ cd $APP_ROOT
+ check_daemons
+ if [ $PUMA_OK -eq 0 ]; then
+ echo Puma is already running
+ else
+ execute "RAILS_ENV=production puma $PUMA_OPTS"
+ fi
+ if [ $WEBSOCKET_OK -eq 0 ]; then
+ echo Websocket server is already running
+ else
+ execute "RAILS_ENV=production script/websocket-server.rb start $WEBSOCKET_OPTS"
+ fi
+ execute "RAILS_ENV=production script/scheduler.rb start $SCHEDULER_OPTS"
+ echo "$DESC started"
+}
+
+stop() {
+ cd $APP_ROOT
+ check_daemons
+ if [ "$PUMA_OK" -eq 0 -o $WEBSOCKET_OK -eq 0 ]; then
+ ## Program is running, stop it.
+ if [ $PUMA_OK -eq 0 ]; then
+ kill -QUIT $PUMA_PID
+ test -f "$WEB_SERVER_PID" && rm -f "$WEB_SERVER_PID"
+ fi
+ if [ $WEBSOCKET_OK -eq 0 ]; then
+ execute "RAILS_ENV=production script/websocket-server.rb stop $WEBSOCKET_OPTS"
+ test -f "$WEBSOCKET_SERVER_PID" && rm -f "$WEBSOCKET_SERVER_PID"
+ fi
+ execute "RAILS_ENV=production script/scheduler.rb stop $SCHEDULER_OPTS"
+ echo "$DESC stopped"
+ else
+ ## Program is not running, exit with error.
+ echo "Error! $DESC is not started!"
+ exit 1
+ fi
+}
+
+restart() {
+ cd $APP_ROOT
+ check_daemons
+ if [ "$PUMA_OK" -eq 0 -o WEBSOCKET_OK -eq 0 ]; then
+ echo "Restarting $DESC..."
+ kill -USR2 $PUMA_PID
+ execute "RAILS_ENV=production script/websocket-server.rb stop $WEBSOCKET_OPTS"
+ execute "RAILS_ENV=production script/websocket-server.rb start $WEBSOCKET_OPTS"
+ execute "RAILS_ENV=production script/scheduler.rb restart $SCHEDULER_OPTS"
+ echo "$DESC restarted."
+ else
+ echo "Error, $NAME not running!"
+ exit 1
+ fi
+}
+
+status() {
+ cd $APP_ROOT
+ check_daemons
+ if [ $PUMA_OK -eq 0 ]; then
+ echo "$DESC / Puma (pid $PUMA_PID) is running."
+ else
+ echo "$DESC / Puma (pid $PUMA_PID) is not running."
+ fi
+ if [ $WEBSOCKET_OK -eq 0 ]; then
+ echo "Websocket server (pid $WEBSOCKET_PID) is running."
+ else
+ echo "Websocket server (pid $WEBSOCKET_PID) is not running."
+ fi
+ execute "RAILS_ENV=production script/scheduler.rb status $SCHEDULER_OPTS"
+}
+
+## Check to see if we are running as root first.
+## Found at http://www.cyberciti.biz/tips/shell-root-user-check-script.html
+if [ "$(id -u)" != "0" ]; then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ restart
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: service zammad {start|stop|restart|reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/script/install.sh b/script/install.sh
index aee26438a..67da70b27 100644
--- a/script/install.sh
+++ b/script/install.sh
@@ -1,15 +1,106 @@
#!/bin/bash
-get_distro(){
- arch=$(uname -m)
- kernel=$(uname -r)
- if [ -f /etc/lsb-release ]; then
- os=$(lsb_release -s -d)
- elif [ -f /etc/debian_version ]; then
- os="Debian $(cat /etc/debian_version)"
- elif [ -f /etc/redhat-release ]; then
- os=`cat /etc/redhat-release`
- else
- os="$(uname -s) $(uname -r)"
- fi
+
+USER=zammad
+REPOURL=git@github.com:martini/zammad.git
+DBNAME=zammad
+DBUSER=zammad
+
+function check_requirements() {
+ items="git useradd sudo getent curl bash gcc make svn apg"
+ for item in $items
+ do
+ which $item > /dev/null
+ if [ $? -ne 0 ]; then
+ echo Please install $item and start this script again.
+ exit 1
+ fi
+ done
}
+function check_os()
+{
+ # Debian
+ if [ -f /etc/debian_version ]; then
+ OS=Debian
+ local MAJOR=$(cut -d. /etc/debian_version -f1)
+ if [ $MAJOR -lt 7 ]; then
+ echo Please check the supported operating systems
+ exit 1
+ fi
+ fi
+}
+
+check_requirements
+check_os
+
+
+#
+# @TODO Should the mysql user be created?
+# @TODO Install Elasticsearch?
+# @TODO Should the script create a VirtualHost or a config file to include for apache/nginx?
+#
+
+#
+# Check for zammad user and create if needed
+#
+id -u "${USER}" > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ useradd -c 'user running zammad' -m -s /bin/bash $USER
+fi
+
+#
+# find the user's homedir and primary group name
+#
+HOMEDIR=$(getent passwd $USER | cut -d: -f 6)
+GROUP=$(id -gn $USER)
+
+cd "${HOMEDIR}"
+sudo -u "${USER}" -H git clone $REPOURL zammad
+cd zammad
+LATEST=$(git tag --list|sort|tail -1)
+git checkout tags/"${LATEST}"
+chown -R "${USER}":"${GROUP}" .
+
+#
+# RVM
+#
+sudo -u "${USER}" -H bash -c 'curl -sSL https://get.rvm.io | bash -s stable'
+
+
+#
+# install Ruby
+#
+sudo -u "${USER}" -H bash -l -c 'rvm install 2.1.2'
+sudo -u "${USER}" -H bash -l -c 'rvm alias create default 2.1.2'
+
+#
+# after rvm requirements
+# Installing required packages: gawk, g++, libreadline6-dev, zlib1g-dev, libssl-dev, libyaml-dev, libsqlite3-dev, sqlite3, autoconf, libgdbm-dev, libncurses5-dev, automake, libtool, bison, pkg-config, libffi-dev................
+
+
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && gem install rails --no-ri --no-rdoc'
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && bundle install'
+
+DBPASS=$(apg -x8|head -1)
+echo Password $DBPASS
+mysql -e "GRANT ALL ON ${DBNAME}.* to '${DBUSER}'@'localhost' IDENTIFIED BY '$DBPASS'";
+sudo -u $USER -H cp ${HOMEDIR}/zammad/config/database.yml.dist ${HOMEDIR}/zammad/config/database.yml
+sudo -u $USER -H sed -i s/some_pass/${DBPASS}/g ${HOMEDIR}/zammad/config/database.yml
+sudo -u $USER -H sed -i s/some_user/${DBUSER}/g ${HOMEDIR}/zammad/config/database.yml
+sudo -u $USER -H sed -i s/zammad_prod/zammad/g ${HOMEDIR}/zammad/config/database.yml
+
+#
+#
+#
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && RAILS_ENV=production rake db:create'
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && RAILS_ENV=production rake db:migrate'
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && RAILS_ENV=production rake db:seed'
+sudo -u "${USER}" -H bash -l -c 'cd ~/zammad && RAILS_ENV=production rake assets:precompile'
+
+cp "${HOMEDIR}/zammad/script/init.d/zammad /etc/init.d/zammad"
+chmod +x /etc/init.d/zammad
+
+if [ "$OS" = "Debian" ]; then
+ update-rc.d zammad defaults
+fi
+
diff --git a/test/browser/text_module_test.rb b/test/browser/agent_ticket_actions_level5_test.rb
similarity index 76%
rename from test/browser/text_module_test.rb
rename to test/browser/agent_ticket_actions_level5_test.rb
index edfeb348c..a0a7e9a27 100644
--- a/test/browser/text_module_test.rb
+++ b/test/browser/agent_ticket_actions_level5_test.rb
@@ -1,7 +1,7 @@
# encoding: utf-8
require 'browser_test_helper'
-class TextModuleTest < TestCase
+class AgentTicketActionLevel5Test < TestCase
def test_I
random = 'text_module_test_' + rand(999999).to_s
random2 = 'text_module_test_' + rand(999999).to_s
@@ -155,6 +155,13 @@ class TextModuleTest < TestCase
def test_II
random = 'text_II_module_test_' + rand(999999).to_s
+ user_rand = rand(999999).to_s
+ login = 'agent-text-module-' + user_rand
+ firstname = 'Text' + user_rand
+ lastname = 'Module' + user_rand
+ email = 'agent-text-module-' + user_rand + '@example.com'
+ password = 'agentpw'
+
# user
tests = [
{
@@ -258,6 +265,22 @@ class TextModuleTest < TestCase
],
},
+
+ # create user
+ {
+ :name => 'create user',
+ :action => [
+ {
+ :where => :instance1,
+ :execute => 'create_user',
+ :login => login,
+ :firstname => firstname,
+ :lastname => lastname,
+ :email => email,
+ :password => password,
+ },
+ ],
+ },
{
:name => 'check if text module exists in instance2, for ready to use',
:action => [
@@ -365,6 +388,12 @@ class TextModuleTest < TestCase
:name => 'verify zoom',
:action => [
+ {
+ :where => :instance1,
+ :execute => 'click',
+ :css => 'a[href="#manage"]',
+ },
+
# create ticket
{
:where => :instance2,
@@ -406,7 +435,7 @@ class TextModuleTest < TestCase
},
{
:execute => 'wait',
- :value => 1,
+ :value => 0.5,
},
{
:where => :instance2,
@@ -433,6 +462,113 @@ class TextModuleTest < TestCase
},
],
},
+ {
+ :name => 'change customer',
+ :action => [
+
+ {
+ :where => :instance1,
+ :execute => 'click',
+ :css => 'a[href="#manage"]',
+ },
+
+ # create ticket
+ {
+ :where => :instance2,
+ :execute => 'click',
+ :css => '.active .action button',
+ },
+ {
+ :execute => 'wait',
+ :value => 1,
+ },
+ {
+ :where => :instance2,
+ :execute => 'click',
+ :css => '.active .action [data-type="customer"]',
+ },
+ {
+ :execute => 'wait',
+ :value => 1,
+ },
+ {
+ :where => :instance2,
+ :execute => 'set',
+ :css => '#form-customer input[name="customer_id_autocompletion"]',
+ :value => firstname,
+ },
+ {
+ :execute => 'wait',
+ :value => 4,
+ },
+ {
+ :where => :instance2,
+ :execute => 'sendkey',
+ :css => '#form-customer input[name="customer_id_autocompletion"]',
+ :value => :arrow_down,
+ },
+ {
+ :where => :instance2,
+ :execute => 'sendkey',
+ :css => '#form-customer input[name="customer_id_autocompletion"]',
+ :value => :tab,
+ },
+ {
+ :where => :instance2,
+ :execute => 'wait',
+ :value => 1,
+ },
+ {
+ :where => :instance2,
+ :execute => 'click',
+ :css => '.modal-content [type="submit"]',
+ },
+ {
+ :where => :instance2,
+ :execute => 'wait',
+ :value => 4,
+ },
+ {
+ :where => :instance2,
+ :execute => 'set',
+ :css => '.active textarea[name=body]',
+ :value => '::' + random,
+ },
+ {
+ :execute => 'wait',
+ :value => 0.2,
+ },
+# {
+# :where => :instance2,
+# :execute => 'match',
+# :css => 'body',
+# :value => random,
+# :match_result => true,
+# },
+ {
+ :where => :instance2,
+ :execute => 'click',
+ :css => '.-sew-list-item.selected',
+ },
+ {
+ :execute => 'wait',
+ :value => 1,
+ },
+ {
+ :where => :instance2,
+ :execute => 'match',
+ :css => '.active textarea[name=body]',
+ :value => 'some content ' + lastname,
+ :match_result => true,
+ },
+ {
+ :execute => 'wait',
+ :value => 2,
+ },
+ ],
+ },
+
+
]
browser_double_test(tests)
end
diff --git a/test/browser/manage_test.rb b/test/browser/manage_test.rb
index 178403470..c6f119967 100644
--- a/test/browser/manage_test.rb
+++ b/test/browser/manage_test.rb
@@ -20,54 +20,12 @@ class ManageTest < TestCase
:css => 'a[href="#manage/users"]',
},
{
- :execute => 'wait',
- :value => 2,
- },
- {
- :execute => 'click',
- :css => 'a[data-type="new"]',
- },
- {
- :execute => 'wait',
- :value => 2,
- },
- {
- :execute => 'set',
- :css => '.modal input[name=login]',
- :value => 'some login' + random,
- },
- {
- :execute => 'set',
- :css => '.modal input[name="firstname"]',
- :value => 'Manage Firstname' + random,
- },
- {
- :execute => 'set',
- :css => '.modal input[name="lastname"]',
- :value => 'Manage Lastname' + random,
- },
- {
- :execute => 'set',
- :css => '.modal input[name="email"]',
- :value => user_email,
- },
- {
- :execute => 'set',
- :css => '.modal input[name="password"]',
- :value => 'some-pass',
- },
- {
- :execute => 'set',
- :css => '.modal input[name="password_confirm"]',
- :value => 'some-pass',
- },
- {
- :execute => 'click',
- :css => '.modal input[name="role_ids"][value="3"]',
- },
- {
- :execute => 'click',
- :css => '.modal button.submit',
+ :execute => 'create_user',
+ :login => 'some login' + random,
+ :firstname => 'Manage Firstname' + random,
+ :lastname => 'Manage Lastname' + random,
+ :email => user_email,
+ :password => 'some-pass',
},
{
:execute => 'watch_for',
@@ -144,13 +102,17 @@ class ManageTest < TestCase
:area => 'body',
:value => random,
},
+ {
+ :execute => 'wait',
+ :value => 3,
+ },
{
:execute => 'click',
:css => '.table-overview tr:last-child td',
},
{
:execute => 'wait',
- :value => 2,
+ :value => 1,
},
{
:execute => 'set',
@@ -168,12 +130,12 @@ class ManageTest < TestCase
},
{
:execute => 'watch_for',
- :area => 'body',
+ :area => 'body table',
:value => 'some sla update ' + random,
},
{
:execute => 'wait',
- :value => 1,
+ :value => 4,
},
{
:execute => 'click',
@@ -189,7 +151,7 @@ class ManageTest < TestCase
},
{
:execute => 'wait',
- :value => 2,
+ :value => 3,
},
{
:execute => 'match',
diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb
index 24419cc67..b946a036e 100644
--- a/test/browser_test_helper.rb
+++ b/test/browser_test_helper.rb
@@ -253,9 +253,47 @@ class TestCase < Test::Unit::TestCase
return
end
sleep 0.33
- }
+ }
assert( false, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" )
return
+ elsif action[:execute] == 'create_user'
+
+ instance.find_element( { :css => 'a[href="#manage"]' } ).click
+ instance.find_element( { :css => 'a[href="#manage/users"]' } ).click
+ sleep 2
+ instance.find_element( { :css => 'a[data-type="new"]' } ).click
+ sleep 2
+ element = instance.find_element( { :css => '.modal input[name=login]' } )
+ element.clear
+ element.send_keys( action[:login] )
+ element = instance.find_element( { :css => '.modal input[name=firstname]' } )
+ element.clear
+ element.send_keys( action[:firstname] )
+ element = instance.find_element( { :css => '.modal input[name=lastname]' } )
+ element.clear
+ element.send_keys( action[:lastname] )
+ element = instance.find_element( { :css => '.modal input[name=email]' } )
+ element.clear
+ element.send_keys( action[:email] )
+ element = instance.find_element( { :css => '.modal input[name=password]' } )
+ element.clear
+ element.send_keys( action[:password] )
+ element = instance.find_element( { :css => '.modal input[name=password_confirm]' } )
+ element.clear
+ element.send_keys( action[:password] )
+ instance.find_element( { :css => '.modal input[name="role_ids"][value="3"]' } ).click
+ instance.find_element( { :css => '.modal button.submit' } ).click
+ (1..14).each {|loop|
+ element = instance.find_element( { :css => 'body' } )
+ text = element.text
+ if text =~ /#{Regexp.quote(action[:lastname])}/
+ assert( true, "(#{test[:name]}) user created" )
+ return
+ end
+ sleep 0.5
+ }
+ assert( true, "(#{test[:name]}) user creation failed" )
+ return
elsif action[:execute] == 'create_ticket'
instance.find_element( { :css => 'a[href="#new"]' } ).click
instance.find_element( { :css => 'a[href="#ticket/create/call_inbound"]' } ).click