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