diff --git a/app/assets/javascripts/app/controllers/_application_controller.coffee b/app/assets/javascripts/app/controllers/_application_controller.coffee index 9cfe5ff03..c6fc9fa3f 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.coffee @@ -185,6 +185,17 @@ class App.Controller extends Spine.Controller formValidate: (data) -> App.ControllerForm.validate(data) + # get all query params of the url + queryParam: -> + return if !@query + pairs = @query.split(';') + params = {} + for pair in pairs + result = pair.match('(.+?)=(.*)') + if result && result[1] + params[result[1]] = result[2] + params + # redirectToLogin: (data) -> # diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.coffee index 7234f6c92..f764e5c57 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_generic.coffee @@ -309,6 +309,23 @@ class App.ControllerConfirm extends App.ControllerModal if @callback @callback() +class App.ControllerErrorModal extends App.ControllerModal + buttonClose: true + buttonCancel: false + buttonSubmit: 'Close' + #buttonClass: 'btn--danger' + head: 'Error' + #small: true + #shown: true + + content: -> + @message + + onSubmit: => + @close() + if @callback + @callback() + class App.ControllerDrox extends App.Controller constructor: (params) -> super diff --git a/app/assets/javascripts/app/controllers/_integration/idoit.coffee b/app/assets/javascripts/app/controllers/_integration/idoit.coffee new file mode 100644 index 000000000..724207069 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_integration/idoit.coffee @@ -0,0 +1,94 @@ +class Index extends App.ControllerIntegrationBase + featureIntegration: 'idoit_integration' + featureName: 'i-doit' + featureConfig: 'idoit_config' + description: [ + ['This service allows you to connect i-doit objects with Zammad.'] + ] + events: + 'change .js-switch input': 'switch' + + render: => + super + new Form( + el: @$('.js-form') + ) + + new App.HttpLog( + el: @$('.js-log') + facility: 'idoit' + ) + +class Form extends App.Controller + events: + 'submit form': 'update' + + constructor: -> + super + @render() + + currentConfig: -> + App.Setting.get('idoit_config') + + setConfig: (value) -> + App.Setting.set('idoit_config', value, {notify: true}) + + render: => + @config = @currentConfig() + + @html App.view('integration/idoit')( + config: @config + ) + + update: (e) => + e.preventDefault() + @config = @formParam(e.target) + @validateAndSave() + + validateAndSave: => + @ajax( + id: 'idoit' + type: 'POST' + url: "#{@apiPath}/integration/idoit/verify" + data: JSON.stringify( + method: 'cmdb.object_types' + api_token: @config.api_token + endpoint: @config.endpoint + client_id: @config.client_id + ) + success: (data, status, xhr) => + if data.result is 'failed' + new App.ControllerErrorModal( + message: data.message + container: @el.closest('.content') + ) + return + @setConfig(@config) + + error: (data, status) => + + # do not close window if request is aborted + return if status is 'abort' + + details = data.responseJSON || {} + @notify( + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to save!') + ) + ) + +class State + @current: -> + App.Setting.get('idoit_integration') + +App.Config.set( + 'IntegrationIdoit' + { + name: 'i-doit' + target: '#system/integration/idoit' + description: 'CMDB to document complex relations of your network components.' + controller: Index + state: State + } + 'NavBarIntegrations' +) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee index d4123771e..617c83f0c 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee @@ -9,6 +9,7 @@ class App.TicketCreate extends App.Controller constructor: (params) -> super + @sidebarState = {} # define default type @default_type = 'phone-in' @@ -91,6 +92,8 @@ class App.TicketCreate extends App.Controller else @$('[name="cc"]').closest('.form-group').addClass('hide') + App.TaskManager.touch(@task_key) + meta: => text = '' if @articleAttributes @@ -99,10 +102,10 @@ class App.TicketCreate extends App.Controller if title text = "#{text}: #{title}" meta = - url: @url() - head: text - title: text - id: @id + url: @url() + head: text + title: text + id: @id iconClass: 'pen' url: => @@ -335,25 +338,42 @@ class App.TicketCreate extends App.Controller user: App.Session.get() ) - new Sidebar( - el: @sidebar - params: @formDefault - textModule: @textModule + $('#tags').tokenfield() + + @sidebarWidget = new App.TicketCreateSidebar( + el: @sidebar + params: @formDefault + sidebarState: @sidebarState + task_key: @task_key + query: @query ) - $('#tags').tokenfield() + if @formDefault.customer_id + callback = (customer) => + @localUserInfoCallback(@formDefault, customer) + App.User.full(@formDefault.customer_id, callback) # update taskbar with new meta data App.TaskManager.touch(@task_key) localUserInfo: (e) => - + return if !@sidebarWidget params = App.ControllerForm.params($(e.target).closest('form')) - new Sidebar( - el: @sidebar - params: params - textModule: @textModule + if params.customer_id + callback = (customer) => + @localUserInfoCallback(params, customer) + App.User.full(params.customer_id, callback) + return + @localUserInfoCallback(params) + + localUserInfoCallback: (params, customer = {}) => + @sidebarWidget.render(params) + @textModule.reload( + config: App.Config.all() + user: App.Session.get() + ticket: + customer: customer ) cancel: (e) -> @@ -478,6 +498,10 @@ class App.TicketCreate extends App.Controller # scroll to top ui.scrollTo() + # add sidebar params + if ui.sidebarWidget + ui.sidebarWidget.commit(ticket_id: @id) + # access to group for group_id, access of App.Session.get('group_ids') if @group_id.toString() is group_id.toString() @@ -498,114 +522,6 @@ class App.TicketCreate extends App.Controller ) ) -class Sidebar extends App.Controller - constructor: -> - super - - # load user - if @params['customer_id'] - App.User.full(@params['customer_id'], @render) - return - - # render ui - @render() - - render: (user) => - - items = [] - if user - - showCustomer = (el) => - # update text module UI - if @textModule - @textModule.reload( - ticket: - customer: user - user: App.Session.get() - ) - - new App.WidgetUser( - el: el - user_id: user.id - ) - - editCustomer = (e, el) => - new App.ControllerGenericEdit( - id: @params.customer_id - genericObject: 'User' - screen: 'edit' - pageData: - title: 'Users' - object: 'User' - objects: 'Users' - container: @el.closest('.content') - ) - items.push { - head: 'Customer' - name: 'customer' - icon: 'person' - actions: [ - { - title: 'Edit Customer' - name: 'Edit Customer' - class: 'glyphicon glyphicon-edit' - callback: editCustomer - }, - ] - callback: showCustomer - } - - if user.organization_id - editOrganization = (e, el) => - new App.ControllerGenericEdit( - id: user.organization_id - genericObject: 'Organization' - pageData: - title: 'Organizations' - object: 'Organization' - objects: 'Organizations' - container: @el.closest('.content') - ) - showOrganization = (el) -> - new App.WidgetOrganization( - el: el - organization_id: user.organization_id - ) - items.push { - head: 'Organization' - name: 'organization' - icon: 'group' - actions: [ - { - title: 'Edit Organization' - name: 'Edit Organization' - class: 'glyphicon glyphicon-edit' - callback: editOrganization - }, - ] - callback: showOrganization - } - - showTemplates = (el) -> - - # show template UI - new App.WidgetTemplate( - el: el - #template_id: template['id'] - ) - - items.push { - head: 'Templates' - name: 'template' - icon: 'templates' - callback: showTemplates - } - - new App.Sidebar( - el: @el - items: items - ) - class Router extends App.ControllerPermanent requiredPermission: 'ticket.agent' constructor: (params) -> @@ -621,6 +537,9 @@ class Router extends App.ControllerPermanent if params.customer_id split = "/customer/#{params.customer_id}" + if params.query + split = "/query/#{params.query}" + id = Math.floor( Math.random() * 99999 ) @navigate "#ticket/create/id/#{id}#{split}" return @@ -631,6 +550,7 @@ class Router extends App.ControllerPermanent article_id: params.article_id type: params.type customer_id: params.customer_id + query: params.query id: params.id App.TaskManager.execute( @@ -646,6 +566,7 @@ App.Config.set('ticket/create/', Router, 'Routes') App.Config.set('ticket/create/id/:id', Router, 'Routes') App.Config.set('ticket/create/customer/:customer_id', Router, 'Routes') App.Config.set('ticket/create/id/:id/customer/:customer_id', Router, 'Routes') +App.Config.set('ticket/create/id/:id/query/:query', Router, 'Routes') # split ticket App.Config.set('ticket/create/:ticket_id/:article_id', Router, 'Routes') diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee new file mode 100644 index 000000000..88b151449 --- /dev/null +++ b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar.coffee @@ -0,0 +1,43 @@ +class App.TicketCreateSidebar extends App.Controller + constructor: -> + super + @render() + + reload: (args) => + for key, backend of @sidebarBackends + if backend && backend.reload + backend.reload(args) + + commit: (args) => + for key, backend of @sidebarBackends + if backend && backend.commit + backend.commit(args) + + render: (params) => + if params + @params = params + @sidebarBackends ||= {} + @sidebarItems = [] + sidebarBackends = App.Config.get('TicketCreateSidebar') + keys = _.keys(sidebarBackends).sort() + for key in keys + if !@sidebarBackends[key] || !@sidebarBackends[key].reload + @sidebarBackends[key] = new sidebarBackends[key]( + params: @params + query: @query + taskGet: @taskGet + ) + else + @sidebarBackends[key].reload( + params: @params + query: @query + ) + item = @sidebarBackends[key].sidebarItem() + if item + @sidebarItems.push item + + new App.Sidebar( + el: @el + sidebarState: @sidebarState + items: @sidebarItems + ) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_customer.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_customer.coffee new file mode 100644 index 000000000..cbfd4e7e9 --- /dev/null +++ b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_customer.coffee @@ -0,0 +1,38 @@ +class SidebarCustomer extends App.Controller + sidebarItem: => + return if !@permissionCheck('ticket.agent') + return if !@params.customer_id + { + head: 'Customer' + name: 'customer' + icon: 'person' + actions: [ + { + title: 'Edit Customer' + name: 'customer-edit' + callback: @editCustomer + }, + ] + callback: @showCustomer + } + + showCustomer: (el) => + @el = el + new App.WidgetUser( + el: @el + user_id: @params.customer_id + ) + + editCustomer: => + new App.ControllerGenericEdit( + id: @params.customer_id + genericObject: 'User' + screen: 'edit' + pageData: + title: 'Users' + object: 'User' + objects: 'Users' + container: @el.closest('.content') + ) + +App.Config.set('200-Customer', SidebarCustomer, 'TicketCreateSidebar') diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_organization.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_organization.coffee new file mode 100644 index 000000000..db2a9239d --- /dev/null +++ b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_organization.coffee @@ -0,0 +1,41 @@ +class SidebarOrganization extends App.Controller + sidebarItem: => + return if !@permissionCheck('ticket.agent') + return if !@params.customer_id + return if !App.User.exists(@params.customer_id) + customer = App.User.find(@params.customer_id) + @organization_id = customer.organization_id + return if !@organization_id + { + head: 'Organization' + name: 'organization' + icon: 'group' + actions: [ + { + title: 'Edit Organization' + name: 'organization-edit' + callback: @editOrganization + }, + ] + callback: @showOrganization + } + + showOrganization: (el) => + @el = el + new App.WidgetOrganization( + el: @el + organization_id: @organization_id + ) + + editOrganization: => + new App.ControllerGenericEdit( + id: @organization_id, + genericObject: 'Organization' + pageData: + title: 'Organizations' + object: 'Organization' + objects: 'Organizations' + container: @el.closest('.content') + ) + +App.Config.set('300-Organization', SidebarOrganization, 'TicketCreateSidebar') diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_template.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_template.coffee new file mode 100644 index 000000000..bb724bc83 --- /dev/null +++ b/app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_template.coffee @@ -0,0 +1,21 @@ +class SidebarTemplate extends App.Controller + sidebarItem: => + return if !@permissionCheck('ticket.agent') + { + head: 'Templates' + name: 'template' + icon: 'templates' + actions: [] + callback: @showTemplates + } + + showTemplates: (el) => + @el = el + + # show template UI + new App.WidgetTemplate( + el: el + #template_id: template['id'] + ) + +App.Config.set('100-Template', SidebarTemplate, 'TicketCreateSidebar') diff --git a/app/assets/javascripts/app/controllers/idoit_object_selector.coffee b/app/assets/javascripts/app/controllers/idoit_object_selector.coffee new file mode 100644 index 000000000..d2b7f2065 --- /dev/null +++ b/app/assets/javascripts/app/controllers/idoit_object_selector.coffee @@ -0,0 +1,100 @@ +class App.IdoitObjectSelector extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: true + head: 'i-doit' + + content: -> + @ajax( + id: 'idoit-object-selector' + type: 'POST' + url: "#{@apiPath}/integration/idoit" + data: JSON.stringify(method: 'cmdb.object_types') + success: (data, status, xhr) => + if data.result is 'failed' + @contentInline = data.message + @render() + return + + result = _.sortBy(data.response.result, 'title') + @contentInline = $(App.view('integration/idoit_object_selector')()) + + @contentInline.find('.js-typeSelect').html(@renderTypeSelector(result)) + + @contentInline.filter('.js-search').on('change', 'select, input', (e) => + params = @formParam(e.target) + @search(params) + ) + @contentInline.filter('.js-search').on('keyup', 'input', (e) => + params = @formParam(e.target) + @search(params) + ) + @render() + @$('.js-input').focus() + + error: (xhr, status, error) => + + # do not close window if request is aborted + return if status is 'abort' + + # show error message + @contentInline = 'Unable to load content' + @render() + ) + '' + + search: (filter) => + if _.isEmpty(filter.title) + delete filter.title + else + filter.title = "%#{filter.title}%" + @ajax( + id: 'idoit-object-selector' + type: 'POST' + url: "#{@apiPath}/integration/idoit" + data: JSON.stringify(method: 'cmdb.objects', filter: filter) + success: (data, status, xhr) => + @renderResult(data.response.result) + + error: (xhr, status, error) => + + # do not close window if request is aborted + return if status is 'abort' + + # show error message + @contentInline = 'Unable to load content' + @render() + ) + + renderResult: (items) => + table = App.view('integration/idoit_object_result')( + items: items + ) + @el.find('.js-result').html(table) + + renderTypeSelector: (result) -> + options = {} + for item in result + options[item.id] = item.title + return App.UiElement.searchable_select.render( + name: 'type' + multiple: false + limit: 100 + null: false + nulloption: false + options: options + ) + + onSubmit: (e) => + form = @el.find('.js-result') + params = @formParam(form) + return if _.isEmpty(params.object_id) + + if _.isArray(params.object_id) + object_ids = params.object_id + else + object_ids = [params.object_id] + + @formDisable(form) + @callback(object_ids, @) + diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index 7edf7bdcf..a08b201be 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -473,6 +473,7 @@ class App.TicketZoom extends App.Controller sidebarState: @sidebarState object_id: @ticket_id model: 'Ticket' + query: @query taskGet: @taskGet task_key: @task_key formMeta: @formMeta @@ -797,6 +798,9 @@ class App.TicketZoom extends App.Controller # reset form after save @reset() + if @sidebar + @sidebar.commit() + if taskAction is 'closeNextInOverview' if @overview_id current_position = 0 diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee index 874922998..1eb4d68ea 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee @@ -9,18 +9,32 @@ class App.TicketZoomSidebar extends App.ObserverController if backend && backend.reload backend.reload(args) + commit: (args) => + for key, backend of @sidebarBackends + if backend && backend.commit + backend.commit(args) + render: (ticket) => - @sidebarBackends = {} + @sidebarBackends ||= {} @sidebarItems = [] sidebarBackends = App.Config.get('TicketZoomSidebar') keys = _.keys(sidebarBackends).sort() for key in keys - @sidebarBackends[key] = new sidebarBackends[key]( - ticket: ticket - taskGet: @taskGet - formMeta: @formMeta - markForm: @markForm - ) + if !@sidebarBackends[key] || !@sidebarBackends[key].reload + @sidebarBackends[key] = new sidebarBackends[key]( + ticket: ticket + query: @query + taskGet: @taskGet + formMeta: @formMeta + markForm: @markForm + ) + else + @sidebarBackends[key].reload( + params: @params + query: @query + formMeta: @formMeta + markForm: @markForm + ) item = @sidebarBackends[key].sidebarItem() if item @sidebarItems.push item diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee new file mode 100644 index 000000000..5ebce7924 --- /dev/null +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_idoit.coffee @@ -0,0 +1,132 @@ +class SidebarIdoit extends App.Controller + sidebarItem: => + return if !@Config.get('idoit_integration') + { + head: 'i-doit' + name: 'idoit' + icon: 'printer' + actions: [ + { + title: 'Change Objects' + name: 'objects-change' + callback: @changeObjects + }, + ] + callback: @showObjects + } + + changeObjects: => + new App.IdoitObjectSelector( + task_key: @task_key + container: @el.closest('.content') + callback: (objectIds, objectSelectorUi) => + if @ticket && @ticket.id + @updateTicket(@ticket.id, objectIds, => + objectSelectorUi.close() + @showObjectsContent(objectIds) + ) + return + objectSelectorUi.close() + @showObjectsContent(objectIds) + ) + + showObjects: (el) => + @el = el + + # show placeholder + @objectIds ||= [] + if @ticket && @ticket.preferences && @ticket.preferences.idoit && @ticket.preferences.idoit.object_ids + @objectIds = @ticket.preferences.idoit.object_ids + queryParams = @queryParam() + if queryParams && queryParams.idoit_object_ids + @objectIds.push queryParams.idoit_object_ids + @showObjectsContent() + + showObjectsContent: (objectIds) => + if objectIds + @objectIds = @objectIds.concat(objectIds) + + # show placeholder + if _.isEmpty(@objectIds) + @html("
#{App.i18n.translateInline('none')}
") + return + + # ajax call to show items + @ajax( + id: "idoit-#{@task_key}" + type: 'POST' + url: "#{@apiPath}/integration/idoit" + data: JSON.stringify(method: 'cmdb.objects', filter: ids: @objectIds) + success: (data, status, xhr) => + if data.response + @showList(data.response.result) + return + @showError('Unable to load data...') + + error: (xhr, status, error) => + + # do not close window if request is aborted + return if status is 'abort' + + # show error message + @showError('Unable to load data...') + ) + + showList: (objects) => + list = $(App.view('ticket_zoom/sidebar_idoit')( + objects: objects + )) + list.delegate('.js-delete', 'click', (e) => + e.preventDefault() + objectId = $(e.currentTarget).attr 'data-object-id' + @delete(objectId) + ) + @html(list) + + showError: (message) => + @html App.i18n.translateInline(message) + + delete: (objectId) => + localObjects = [] + for localObjectId in @objectIds + if objectId.toString() isnt localObjectId.toString() + localObjects.push localObjectId + @objectIds = localObjects + if @ticket && @ticket.id + @updateTicket(@ticket.id, @objectIds) + @showObjectsContent() + + commit: (args) => + return if @ticket && @ticket.id + return if !@objectIds + return if _.isEmpty(@objectIds) + return if !args + return if !args.ticket_id + @updateTicket(args.ticket_id, @objectIds) + + updateTicket: (ticket_id, objectIds, callback) => + App.Ajax.request( + id: "idoit-update-#{ticket_id}" + type: 'POST' + url: "#{@apiPath}/integration/idoit_ticket_update" + data: JSON.stringify(ticket_id: ticket_id, object_ids: objectIds) + success: (data, status, xhr) -> + if callback + callback(objectIds) + + error: (xhr, status, details) => + + # do not close window if request is aborted + return if status is 'abort' + + # show error message + @log 'errors', details + @notify( + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!') + timeout: 6000 + ) + ) + +App.Config.set('500-Idoit', SidebarIdoit, 'TicketCreateSidebar') +App.Config.set('500-Idoit', SidebarIdoit, 'TicketZoomSidebar') diff --git a/app/assets/javascripts/app/views/integration/idoit.jst.eco b/app/assets/javascripts/app/views/integration/idoit.jst.eco new file mode 100644 index 000000000..deec7f27f --- /dev/null +++ b/app/assets/javascripts/app/views/integration/idoit.jst.eco @@ -0,0 +1,25 @@ +
+

<%- @T('Settings') %>

+
+ + + + + + + + + +
<%- @T('Name') %> + <%- @T('Value') %> +
<%- @T('API token') %> * + +
<%- @T('Endpoint') %> * + +
<%- @T('Client ID') %> + +
+
+ + +
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/integration/idoit_object_result.jst.eco b/app/assets/javascripts/app/views/integration/idoit_object_result.jst.eco new file mode 100644 index 000000000..e2d4dbe76 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/idoit_object_result.jst.eco @@ -0,0 +1,25 @@ +
+<% if _.isEmpty(@items): %> + <%- @T('none') %> +<% else: %> + + + + + + + + + + <% for item in @items: %> + + + + + + + + <% end %> + +
<%- @T('ID') %><%- @T('Name') %><%- @T('Status') %><%- @T('Link') %>
<%= item.id %><%= item.title %><%= item.cmdb_status_title %>i-doit
+<% end %> diff --git a/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco b/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco new file mode 100644 index 000000000..fa63a3604 --- /dev/null +++ b/app/assets/javascripts/app/views/integration/idoit_object_selector.jst.eco @@ -0,0 +1,5 @@ + +
diff --git a/app/assets/javascripts/app/views/ticket_zoom/sidebar_idoit.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/sidebar_idoit.jst.eco new file mode 100644 index 000000000..3b4dbbb22 --- /dev/null +++ b/app/assets/javascripts/app/views/ticket_zoom/sidebar_idoit.jst.eco @@ -0,0 +1,13 @@ +<% for object in @objects: %> + +<% end %> diff --git a/app/controllers/integration/idoit_controller.rb b/app/controllers/integration/idoit_controller.rb new file mode 100644 index 000000000..ee4960c5e --- /dev/null +++ b/app/controllers/integration/idoit_controller.rb @@ -0,0 +1,51 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class Integration::IdoitController < ApplicationController + prepend_before_action -> { authentication_check(permission: ['agent.integration.idoit', 'admin.integration.idoit']) }, except: [:verify] + prepend_before_action -> { authentication_check(permission: ['admin.integration.idoit']) }, only: [:verify] + + def verify + response = ::Idoit.verify(params[:api_token], params[:endpoint], params[:client_id]) + render json: { + result: 'ok', + response: response, + } + rescue => e + logger.error e + + render json: { + result: 'failed', + message: e.message, + } + end + + def query + response = ::Idoit.query(params[:method], params[:filter]) + render json: { + result: 'ok', + response: response, + } + rescue => e + logger.error e + + render json: { + result: 'failed', + message: e.message, + } + end + + def update + params[:object_ids] ||= [] + ticket = Ticket.find(params[:ticket_id]) + access!(ticket, 'read') + ticket.preferences[:idoit] ||= {} + ticket.preferences[:idoit][:object_ids] ||= [] + ticket.preferences[:idoit][:object_ids].concat(params[:object_ids]) + ticket.save! + + render json: { + result: 'ok', + } + end + +end diff --git a/config/routes/integration_idoit.rb b/config/routes/integration_idoit.rb new file mode 100644 index 000000000..455599307 --- /dev/null +++ b/config/routes/integration_idoit.rb @@ -0,0 +1,9 @@ +Zammad::Application.routes.draw do + api_path = Rails.configuration.api_path + + match api_path + '/integration/idoit', to: 'integration/idoit#query', via: :post + match api_path + '/integration/idoit', to: 'integration/idoit#query', via: :get + match api_path + '/integration/idoit/verify', to: 'integration/idoit#verify', via: :post + match api_path + '/integration/idoit_ticket_update', to: 'integration/idoit#update', via: :post + +end diff --git a/contrib/cleanup.sh b/contrib/cleanup.sh index 309e91587..10efac1e9 100755 --- a/contrib/cleanup.sh +++ b/contrib/cleanup.sh @@ -8,3 +8,4 @@ rm app/assets/javascripts/app/controllers/karma.coffee rm app/assets/javascripts/app/controllers/report.coffee rm app/assets/javascripts/app/controllers/report_profile.coffee rm app/assets/javascripts/app/controllers/_integration/check_mk.coffee +rm app/assets/javascripts/app/controllers/_integration/idoit.coffee diff --git a/db/migrate/20170816000001_idoit_support.rb b/db/migrate/20170816000001_idoit_support.rb new file mode 100644 index 000000000..8a8185b3e --- /dev/null +++ b/db/migrate/20170816000001_idoit_support.rb @@ -0,0 +1,49 @@ +class IdoitSupport < ActiveRecord::Migration + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + + Setting.create_if_not_exists( + title: 'i-doit integration', + name: 'idoit_integration', + area: 'Integration::Switch', + description: 'Defines if i-doit (http://www.i-doit) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'idoit_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + authentication: true, + permission: ['admin.integration'], + }, + frontend: true + ) + Setting.create_if_not_exists( + title: 'i-doit config', + name: 'idoit_config', + area: 'Integration::Idoit', + description: 'Defines the i-doit config.', + options: {}, + state: {}, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false, + ) + end + +end diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index de3b2d638..20e84d629 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -3038,6 +3038,46 @@ Setting.create_if_not_exists( }, frontend: false, ) +Setting.create_if_not_exists( + title: 'i-doit integration', + name: 'idoit_integration', + area: 'Integration::Switch', + description: 'Defines if i-doit (http://www.i-doit) is enabled or not.', + options: { + form: [ + { + display: '', + null: true, + name: 'idoit_integration', + tag: 'boolean', + options: { + true => 'yes', + false => 'no', + }, + }, + ], + }, + state: false, + preferences: { + prio: 1, + authentication: true, + permission: ['admin.integration'], + }, + frontend: true +) +Setting.create_if_not_exists( + title: 'i-doit config', + name: 'idoit_config', + area: 'Integration::Idoit', + description: 'Defines the i-doit config.', + options: {}, + state: {}, + preferences: { + prio: 2, + permission: ['admin.integration'], + }, + frontend: false, +) Setting.create_if_not_exists( title: 'Defines sync transaction backend.', name: '0100_trigger', diff --git a/lib/idoit.rb b/lib/idoit.rb new file mode 100644 index 000000000..f1fe7f379 --- /dev/null +++ b/lib/idoit.rb @@ -0,0 +1,149 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class Idoit + +=begin + +get list ob types + + result = Idoit.verify(api_token, endpoint, client_id) + +returns + + array with cmdb.object_types or an exeption if no data was able to retrive + +=end + + def self.verify(api_token, endpoint, _client_id = nil) + raise 'api_token required' if api_token.blank? + raise 'endpoint required' if endpoint.blank? + + params = { + apikey: api_token, + } + + _query('cmdb.object_types', params, _url_cleanup(endpoint)) + end + +=begin + +get list ob types + + result = Idoit.query(method, filter) + + result = Idoit.query(method, { type: '59' }) + +returns + + result = [ + { + "id": "1", + "title": "System service", + "container": "0", + "const": "C__OBJTYPE__SERVICE", + "color": "987384", + "image": "https://demo.panic.at/i-doit/images/objecttypes/service.jpg", + "icon": "images/icons/silk/application_osx_terminal.png", + "cats": "4", + "tree_group": "1", + "status": "2", + "type_group": "1", + "type_group_title": "Software" + }, + { + "id": "2", + "title": "Application", + "container": "0", + "const": "C__OBJTYPE__APPLICATION", + "color": "E4B9D7", + "image": "https://demo.panic.at/i-doit/images/objecttypes/application.jpg", + "icon": "images/icons/silk/application_xp.png", + "cats": "20", + "tree_group": "1", + "status": "2", + "type_group": "1", + "type_group_title": "Software" + }, + ] + +or with filter: + + "result": [ + { + "id": "26", + "title": "demo.panic.at", + "sysid": "SYSID_1485512390", + "type": "59", + "created": "2017-01-27 11:19:24", + "updated": "2017-01-27 11:19:49", + "type_title": "Virtual server", + "type_group_title": "Infrastructure", + "status": "2", + "cmdb_status": "6", + "cmdb_status_title": "in operation", + "image": "https://demo.panic.at/i-doit/images/objecttypes/empty.png" + }, + ], + +=end + + def self.query(method, filter = {}) + setting = Setting.get('idoit_config') + raise 'Unable for find api_token in config' if setting[:api_token].blank? + raise 'Unable for find endpoint in config' if setting[:endpoint].blank? + + #translator_key = Setting.get('translator_key') + params = { + apikey: setting[:api_token], + } + if filter.present? + params[:filter] = filter + end + _query(method, params, _url_cleanup(setting[:endpoint])) + end + + def self._query(method, params, url) + result = UserAgent.post( + url, + { + method: method, + params: params, + version: '2.0', + }, + { + json: true, + open_timeout: 6, + read_timeout: 16, + log: { + facility: 'idoit', + }, + }, + ) + + raise "Can't fetch objects from #{url}: Unable to parse response from server. Invalid JSON response." if !result.success? && result.error =~ /JSON::ParserError:.+?\s+unexpected\s+token\s+at\s+'<\!DOCTYPE\s+html/i + raise "Can't fetch objects from #{url}: #{result.error}" if !result.success? + + # add link to idoit + if result.data['result'].class == Array + result.data['result'].each { |item| + next if !item['id'] + item['link'] = "#{_url_cleanup_baseurl(url)}/?objID=#{item['id']}" + item['link'].gsub!(%r{([^:])//+}, '\\1/') + } + end + result.data + end + + def self._url_cleanup(url) + raise "Invalid endpoint '#{url}', need to start with http:// or https://" if url !~ %r{^http(s|)://}i + url = _url_cleanup_baseurl(url) + url = "#{url}/src/jsonrpc.php" + url.gsub(%r{([^:])//+}, '\\1/') + end + + def self._url_cleanup_baseurl(url) + raise "Invalid endpoint '#{url}', need to start with http:// or https://" if url !~ %r{^http(s|)://}i + url.gsub!(%r{src/jsonrpc.php}, '') + url.gsub(%r{([^:])//+}, '\\1/') + end +end diff --git a/test/browser/agent_ticket_update_and_reload_test.rb b/test/browser/agent_ticket_update_and_reload_test.rb index 823477cf1..27a5583d1 100644 --- a/test/browser/agent_ticket_update_and_reload_test.rb +++ b/test/browser/agent_ticket_update_and_reload_test.rb @@ -25,6 +25,7 @@ class AgentTicketUpdateAndReloadTest < TestCase sleep 6 # check if customer is shown in sidebar + click(css: '.active .tabsSidebar-tab[data-tab="customer"]') match( css: '.active .sidebar[data-tab="customer"]', value: 'nicole', @@ -46,6 +47,7 @@ class AgentTicketUpdateAndReloadTest < TestCase reload() # check if customer is still shown in sidebar + click(css: '.active .tabsSidebar-tab[data-tab="customer"]') watch_for( css: '.active .sidebar[data-tab="customer"]', value: 'nicole',