Merge branch 'develop' into interface
Conflicts: app/assets/javascripts/app/views/dashboard/ticket.jst.eco app/assets/javascripts/app/views/organization_zoom.jst.eco app/assets/javascripts/app/views/user_zoom.jst.eco
This commit is contained in:
commit
c61dfbfe9a
18 changed files with 840 additions and 117 deletions
6
Gemfile
6
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'
|
||||
|
|
|
@ -657,8 +657,12 @@ class App.ControllerForm extends App.Controller
|
|||
item = $( App.view('generic/textarea')( attribute: attribute ) + '<div class="file-uploader ' + attribute.class + '" id="' + fileUploaderId + '"></div>' )
|
||||
|
||||
a = =>
|
||||
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 )
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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('<div class="" id="' + target + '"></div>')
|
||||
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
|
||||
|
|
|
@ -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' )
|
||||
|
||||
|
|
|
@ -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('<div class="" id="' + target + '"></div>')
|
||||
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
|
||||
|
|
|
@ -42,11 +42,15 @@ class App.WidgetOrganization extends App.Controller
|
|||
)
|
||||
|
||||
a = =>
|
||||
visible = @el.find('textarea').is(":visible")
|
||||
if visible && !@el.find('textarea').expanding('active')
|
||||
@el.find('textarea').expanding()
|
||||
@el.find('textarea').on('focus', =>
|
||||
@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()
|
||||
|
|
|
@ -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 = =>
|
||||
visible = @el.find('textarea').is(":visible")
|
||||
if visible && !@el.find('textarea').expanding('active')
|
||||
@el.find('textarea').expanding()
|
||||
@el.find('textarea').on('focus', =>
|
||||
@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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
<div class="main flex">
|
||||
|
||||
<div class="page-header">
|
||||
<h1><%= @organization.displayName() %></h1>
|
||||
<h1></h1>
|
||||
</div>
|
||||
|
||||
<div class="main-overviews" id="sortable"></div>
|
||||
|
||||
</div>
|
|
@ -8,4 +8,6 @@
|
|||
<h1></h1>
|
||||
</div>
|
||||
|
||||
<div class="main-overviews" id="sortable"></div>
|
||||
|
||||
</div>
|
|
@ -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 = []
|
||||
|
@ -376,6 +378,7 @@ class TicketsController < ApplicationController
|
|||
# return result
|
||||
render :json => {
|
||||
:tickets => ticket_result,
|
||||
:tickets_count => tickets.count,
|
||||
:assets => assets,
|
||||
}
|
||||
end
|
||||
|
|
|
@ -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* -
|
||||
if query
|
||||
query.gsub! '*', ''
|
||||
tickets_all = Ticket.select('DISTINCT(tickets.id)').
|
||||
where(conditions).
|
||||
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
|
||||
|
||||
# build result list
|
||||
if !full
|
||||
ids = []
|
||||
tickets_all.each { |ticket|
|
||||
ids.push ticket.id
|
||||
}
|
||||
return ids
|
||||
end
|
||||
|
||||
tickets = []
|
||||
tickets_all.each do |ticket|
|
||||
tickets_all.each { |ticket|
|
||||
tickets.push Ticket.lookup( :id => ticket.id )
|
||||
end
|
||||
|
||||
tickets
|
||||
}
|
||||
return tickets
|
||||
end
|
||||
|
||||
end
|
||||
|
|
151
script/init.d/zammad
Executable file
151
script/init.d/zammad
Executable file
|
@ -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
|
|
@ -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)"
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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',
|
||||
|
|
|
@ -256,6 +256,44 @@ class TestCase < Test::Unit::TestCase
|
|||
}
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue