Rewrite of channel api.

This commit is contained in:
Martin Edenhofer 2015-08-28 02:53:14 +02:00
parent 89546a09a2
commit 6a733396f9
38 changed files with 1570 additions and 613 deletions

View file

@ -7,19 +7,9 @@ class App.ChannelEmail extends App.ControllerTabs
@tabs = [ @tabs = [
{ {
name: 'Inbound', name: 'Email Accounts',
target: 'c-inbound', target: 'c-channel',
controller: App.ChannelEmailInbound, controller: App.ChannelEmailAccountOverview,
},
{
name: 'Outbound',
target: 'c-outbound',
controller: App.ChannelEmailOutbound,
},
{
name: 'Adresses',
target: 'c-address',
controller: App.ChannelEmailAddress,
}, },
{ {
name: 'Signatures', name: 'Signatures',
@ -132,100 +122,6 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
@hide() @hide()
) )
class App.ChannelEmailAddress extends App.Controller
events:
'click [data-type=new]': 'new'
constructor: ->
super
App.EmailAddress.subscribe( @render, initFetch: true )
render: =>
data = App.EmailAddress.search( sortBy: 'realname' )
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn--success">' + App.i18n.translateContent('New') + '</a></div>' )
new App.ControllerTable(
el: template.find('.overview')
model: App.EmailAddress
objects: data
bindRow:
events:
'click': @edit
)
@html template
new: (e) =>
e.preventDefault()
new App.ChannelEmailAddressEdit(
container: @el.closest('.content')
)
edit: (id, e) =>
e.preventDefault()
item = App.EmailAddress.find(id)
new App.ChannelEmailAddressEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailAddressEdit extends App.ControllerModal
constructor: ->
super
@head = 'Email-Address'
@button = true
@close = true
@cancel = true
if @object
@form = new App.ControllerForm(
model: App.EmailAddress
params: @object
autofocus: true
)
else
@form = new App.ControllerForm(
model: App.EmailAddress,
autofocus: true,
)
@content = @form.form
@show()
onSubmit: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
object = @object || new App.EmailAddress
object.load(params)
# validate form
errors = @form.validate( params )
# show errors in form
if errors
@log 'error', errors
@formValidate( form: e.target, errors: errors )
return false
# disable form
@formDisable(e)
# save object
object.save(
done: =>
@hide()
fail: =>
@hide()
)
class App.ChannelEmailSignature extends App.Controller class App.ChannelEmailSignature extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type=new]': 'new'
@ -317,193 +213,575 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
@hide() @hide()
) )
class App.ChannelEmailInbound extends App.Controller class App.ChannelEmailAccountOverview extends App.Controller
events: events:
'click [data-type=new]': 'new' 'click [data-type="new"]': 'wizard'
'click [data-type="delete"]': 'delete'
'click [data-type="edit-inbound"]': 'edit_inbound'
'click [data-type="edit-outbound"]': 'edit_outbound'
'click [data-type="email-address-new"]': 'email_address_new'
'click [data-type="email-address-edit"]': 'email_address_edit'
'click [data-type="edit-notification-outbound"]': 'edit_notification_outbound'
constructor: -> constructor: ->
super super
App.Channel.subscribe( @render, initFetch: true ) @interval(@load, 20000)
render: => load: =>
channels = App.Channel.search( filter: { area: 'Email::Inbound' } ) @ajax(
id: 'email_index'
type: 'GET'
url: @apiPath + '/channels/email_index'
processData: true
success: (data, status, xhr) =>
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn--success">' + App.i18n.translateContent('New') + '</a></div>' ) # load assets
App.Collection.loadAssets( data.assets )
new App.ControllerTable( @render()
el: template.find('.overview')
model: App.Channel
objects: channels
bindRow:
events:
'click': @edit
) )
@html template
new: (e) =>
e.preventDefault()
new App.ChannelEmailInboundEdit(
container: @el.closest('.content')
)
edit: (id, e) =>
e.preventDefault()
item = App.Channel.find(id)
new App.ChannelEmailInboundEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailInboundEdit extends App.ControllerModal
constructor: ->
super
@head = 'Email Channel'
@button = true
@close = true
@cancel = true
if @object
@form = new App.ControllerForm(
model: App.Channel
params: @object
autofocus: true
)
else
@form = new App.ControllerForm(
model: App.Channel
autofocus: true
)
@content = @form.form
@show()
onSubmit: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
params['area'] = 'Email::Inbound'
object = @object || new App.Channel
object.load(params)
# validate form
errors = @form.validate( params )
# show errors in form
if errors
@log 'error', errors
@formValidate( form: e.target, errors: errors )
return false
# disable form
@formDisable(e)
# save object
object.save(
done: =>
@hide()
fail: =>
@hide()
)
class App.ChannelEmailOutbound extends App.Controller
events:
'change [name="adapter"]': 'toggle'
'submit #mail_adapter': 'update'
constructor: ->
super
App.Channel.subscribe( @render, initFetch: true )
render: => render: =>
@html App.view('channel/email_outbound')() # get channels
channels = App.Channel.search( filter: { area: 'Email::Account' } )
# get current Email::Outbound channel
channels = App.Channel.all()
adapters = {}
adapter_used = undefined
channel_used = undefined
for channel in channels for channel in channels
if channel.area is 'Email::Outbound' email_addresses = App.EmailAddress.search( filter: { channel_id: channel.id } )
channel.email_addresses = email_addresses
adapters[channel.adapter] = channel.adapter # get all unlinked email addresses
if @adapter_used email_addresses_all = App.EmailAddress.all()
if @adapter_used is channel.adapter email_addresses_not_used = []
adapter_used = channel.adapter for email_address in email_addresses_all
channel_used = channel if !email_address.channel_id || !App.Channel.exists(email_address.channel_id)
else if channel.active is true email_addresses_not_used.push email_address
adapter_used = channel.adapter
channel_used = channel
configure_attributes = [ # get channels
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used }, channel = App.Channel.search( filter: { area: 'Email::Notification', active: true } )[0]
@html App.view('channel/email_account_overview')(
channels: channels
email_addresses_not_used: email_addresses_not_used
channel: channel
)
wizard: (e) =>
e.preventDefault()
new App.ChannelEmailAccountWizard(
container: @el.closest('.content')
callback: @load
)
edit_inbound: (e) =>
e.preventDefault()
id = $(e.target).closest('tr').data('id')
channel = App.Channel.find(id)
slide = 'js-inbound'
new App.ChannelEmailAccountWizard(
container: @el.closest('.content')
slide: slide
channel: channel
callback: @load
)
edit_outbound: (e) =>
e.preventDefault()
id = $(e.target).closest('tr').data('id')
channel = App.Channel.find(id)
slide = 'js-outbound'
new App.ChannelEmailAccountWizard(
container: @el.closest('.content')
slide: slide
channel: channel
callback: @load
)
delete: (e) =>
e.preventDefault()
id = $(e.target).closest('tr').data('id')
item = App.Channel.find(id)
new App.ControllerGenericDestroyConfirm(
item: item
container: @el.closest('.content')
callback: @load
)
email_address_new: (e) =>
e.preventDefault()
channel_id = $(e.target).closest('tr').data('id')
new App.ControllerGenericNew(
pageData:
object: 'Email Address'
genericObject: 'EmailAddress'
container: @el.closest('.content')
item:
channel_id: channel_id
callback: @load
)
email_address_edit: (e) =>
e.preventDefault()
id = $(e.target).closest('li').data('id')
new App.ControllerGenericEdit(
pageData:
object: 'Email Address'
genericObject: 'EmailAddress'
container: @el.closest('.content')
id: id
callback: @load
)
edit_notification_outbound: (e) =>
e.preventDefault()
id = $(e.target).closest('tr').data('id')
channel = App.Channel.find(id)
slide = 'js-outbound'
new App.ChannelEmailNotificationWizard(
container: @el.closest('.content')
channel: channel
callback: @load
)
class App.ChannelEmailAccountWizard extends App.Controller
elements:
'.modal-body': 'body'
className: 'modal fade'
events:
'submit .js-intro': 'probeBasedOnIntro'
'submit .js-inbound': 'probeInbound'
'change .js-outbound [name=adapter]': 'toggleOutboundAdapter'
'submit .js-outbound': 'probleOutbound'
'click .js-back': 'goToSlide'
constructor: ->
super
# store account settings
@account =
inbound:
adapter: undefined
options: undefined
outbound:
adapter: undefined
options: undefined
meta: {}
if @channel
@account =
inbound: @channel.options.inbound
outbound: @channel.options.outbound
meta: {}
if @container
@el.addClass('modal--local')
@render()
@el.modal
keyboard: true
show: true
backdrop: true
container: @container
.on
'show.bs.modal': @onShow
'shown.bs.modal': @onComplete
'hidden.bs.modal': =>
if @callback
@callback()
$('.modal').remove()
if @slide
@showSlide(@slide)
render: =>
@html App.view('channel/email_account_wizard')()
@showSlide('js-intro')
# outbound
adapters =
sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup'
smtp: 'SMTP - configure your own outgoing SMTP settings'
configureAttributesOutbound = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters },
] ]
new App.ControllerForm( new App.ControllerForm(
el: @el.find('#form-email-adapter'), el: @$('.base-outbound-type')
model: { configure_attributes: configure_attributes, className: '' }, model:
autofocus: true, configure_attributes: configureAttributesOutbound
className: ''
params:
adapter: @account.outbound.adapter || 'sendmail'
)
@toggleOutboundAdapter()
# inbound
configureAttributesInbound = [
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { imap: 'IMAP', pop3: 'POP3' } },
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, single: true },
]
new App.ControllerForm(
el: @$('.base-inbound-settings'),
model:
configure_attributes: configureAttributesInbound
className: ''
params: @account.inbound
) )
# if adapter_used is 'Sendmail' toggleOutboundAdapter: =>
# # some form
if adapter_used is 'SMTP' # fill user / password based on intro info
configure_attributes = [ channel_used = { options: {} }
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) }, if @account['meta']
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) }, channel_used['options']['user'] = @account['meta']['email']
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) }, channel_used['options']['password'] = @account['meta']['password']
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) },
{ name: 'port', display: 'Port', tag: 'input', type: 'text', limit: 5, null: false, class: 'span1', autocapitalize: false, default: ((channel_used['options']&&channel_used['options']['port']) || 25) }, # show used backend
@el.find('.base-outbound-settings').html('')
adapter = @$('.js-outbound [name=adapter]').val()
if adapter is 'smtp'
configureAttributesOutbound = [
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true },
] ]
@form = new App.ControllerForm( @form = new App.ControllerForm(
el: @el.find('#form-email-adapter-settings'), el: @$('.base-outbound-settings')
model: { configure_attributes: configure_attributes, className: '' }, model:
autofocus: true, configure_attributes: configureAttributesOutbound
className: ''
params: @account.outbound
) )
toggle: (e) => probeBasedOnIntro: (e) =>
e.preventDefault()
params = @formParam(e.target)
# remember account settings
@account.meta = params
# let backend know about the channel
if @channel
params.channel_id = @channel.id
@disable(e)
@$('.js-probe .js-email').text( params.email )
@showSlide('js-probe')
@ajax(
id: 'email_probe'
type: 'POST'
url: @apiPath + '/channels/email_probe'
data: JSON.stringify( params )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
if data.setting
for key, value of data.setting
@account[key] = value
@verify(@account)
else if data.result is 'duplicate'
@showSlide('js-intro')
@showAlert('js-intro', 'Account already exists!' )
else
@showSlide('js-inbound')
@showAlert('js-inbound', 'Unable to detect your server settings. Manual configuration needed.' )
@$('.js-inbound [name="options::user"]').val( @account['meta']['email'] )
@$('.js-inbound [name="options::password"]').val( @account['meta']['password'] )
@enable(e)
fail: =>
@enable(e)
@showSlide('js-intro')
)
probeInbound: (e) =>
e.preventDefault()
# get params # get params
params = @formParam(e.target) params = @formParam(e.target)
# render page with new selected adapter # let backend know about the channel
if @adapter_used isnt params['adapter'] params.channel_id = @channel.id
# set selected adapter @disable(e)
@adapter_used = params['adapter']
@render() @showSlide('js-test')
update: (e) => @ajax(
id: 'email_inbound'
type: 'POST'
url: @apiPath + '/channels/email_inbound'
data: JSON.stringify( params )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
# remember account settings
@account.inbound = params
@showSlide('js-outbound')
if !@channel
@$('.js-outbound [name="options::user"]').val( @account['meta']['email'] )
@$('.js-outbound [name="options::password"]').val( @account['meta']['password'] )
else
@showSlide('js-inbound')
@showAlert('js-inbound', data.message_human || data.message )
@enable(e)
fail: =>
@showSlide('js-inbound')
@showAlert('js-inbound', data.message_human || data.message )
@enable(e)
)
probleOutbound: (e) =>
e.preventDefault() e.preventDefault()
params = @formParam(e.target)
# errors = @form.validate( params ) # get params
params = @formParam(e.target)
params['email'] = @account['meta']['email']
# update Email::Outbound adapter if !params['email'] && @channel
channels = App.Channel.all() email_addresses = App.EmailAddress.search( filter: { channel_id: @channel.id } )
for channel in channels if email_addresses && email_addresses[0]
if channel.area is 'Email::Outbound' && channel.adapter is params['adapter'] params['email'] = email_addresses[0].email
channel.updateAttributes(
options: {
host: params['host'],
user: params['user'],
password: params['password'],
ssl: params['ssl'],
port: params['port'],
},
active: true,
)
# set all other Email::Outbound adapters to inactive # let backend know about the channel
channels = App.Channel.all() params.channel_id = @channel.id
for channel in channels
if channel.area is 'Email::Outbound' && channel.adapter isnt params['adapter']
channel.updateAttributes( active: false )
@disable(e)
@showSlide('js-test')
@ajax(
id: 'email_outbound'
type: 'POST'
url: @apiPath + '/channels/email_outbound'
data: JSON.stringify( params )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
# remember account settings
@account.outbound = params
@verify(@account)
else
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
fail: =>
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
)
verify: (account, count = 0) =>
@showSlide('js-verify')
# let backend know about the channel
if @channel
account.channel_id = @channel.id
if !account.email && @channel
email_addresses = App.EmailAddress.search( filter: { channel_id: @channel.id } )
if email_addresses && email_addresses[0]
account.email = email_addresses[0].email
@ajax(
id: 'email_verify'
type: 'POST'
url: @apiPath + '/channels/email_verify'
data: JSON.stringify( account )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
@el.remove()
else
if count is 2
@showAlert('js-verify', data.message_human || data.message )
@delay(
=>
@showSlide('js-intro')
@showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' )
2300
)
else
if data.subject && @account
@account.subject = data.subject
@verify( @account, count + 1 )
fail: =>
@showSlide('js-intro')
@showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' )
)
goToSlide: (e) =>
e.preventDefault()
slide = $(e.target).data('slide')
@showSlide(slide)
showSlide: (name) =>
@hideAlert(name)
@$('.setup.wizard').addClass('hide')
@$(".setup.wizard.#{name}").removeClass('hide')
@$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus()
showAlert: (screen, message) =>
@$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) )
hideAlert: (screen) =>
@$(".#{screen}").find('.alert').addClass('hide')
disable: (e) =>
@formDisable(e)
@$('.wizard-controls .btn').attr('disabled', true)
enable: (e) =>
@formEnable(e)
@$('.wizard-controls .btn').attr('disabled', false)
class App.ChannelEmailNotificationWizard extends App.Controller
elements:
'.modal-body': 'body'
className: 'modal fade'
events:
'change .js-outbound [name=adapter]': 'toggleOutboundAdapter'
'submit .js-outbound': 'probleOutbound'
constructor: ->
super
# store account settings
@account =
inbound:
adapter: undefined
options: undefined
outbound:
adapter: undefined
options: undefined
meta: {}
if @channel
@account =
inbound: @channel.options.inbound
outbound: @channel.options.outbound
if @container
@el.addClass('modal--local')
@render()
@el.modal
keyboard: true
show: true
backdrop: true
container: @container
.on
'show.bs.modal': @onShow
'shown.bs.modal': @onComplete
'hidden.bs.modal': =>
if @callback
@callback()
$('.modal').remove()
if @slide
@showSlide(@slide)
render: =>
@html App.view('channel/email_notification_wizard')()
@showSlide('js-outbound')
# outbound
adapters =
sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup'
smtp: 'SMTP - configure your own outgoing SMTP settings'
configureAttributesOutbound = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters },
]
new App.ControllerForm(
el: @$('.base-outbound-type')
model:
configure_attributes: configureAttributesOutbound
className: ''
params:
adapter: @account.outbound.adapter || 'sendmail'
)
@toggleOutboundAdapter()
toggleOutboundAdapter: =>
# show used backend
@el.find('.base-outbound-settings').html('')
adapter = @$('.js-outbound [name=adapter]').val()
if adapter is 'smtp'
configureAttributesOutbound = [
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true },
]
@form = new App.ControllerForm(
el: @$('.base-outbound-settings')
model:
configure_attributes: configureAttributesOutbound
className: ''
params: @account.outbound
)
probleOutbound: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
# let backend know about the channel
params.channel_id = @channel.id
@disable(e)
@showSlide('js-test')
@ajax(
id: 'email_outbound'
type: 'POST'
url: @apiPath + '/channels/email_notification'
data: JSON.stringify( params )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
@el.remove()
else
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
fail: =>
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
)
showSlide: (name) =>
@hideAlert(name)
@$('.setup.wizard').addClass('hide')
@$(".setup.wizard.#{name}").removeClass('hide')
@$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus()
showAlert: (screen, message) =>
@$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) )
hideAlert: (screen) =>
@$(".#{screen}").find('.alert').addClass('hide')
disable: (e) =>
@formDisable(e)
@$('.wizard-controls .btn').attr('disabled', true)
enable: (e) =>
@formEnable(e)
@$('.wizard-controls .btn').attr('disabled', false)

View file

@ -364,7 +364,7 @@ class Base extends App.ControllerContent
if App.Config.get('system_online_service') if App.Config.get('system_online_service')
@navigate 'getting_started/channel/email_pre_configured' @navigate 'getting_started/channel/email_pre_configured'
else else
@navigate 'getting_started/channel' @navigate 'getting_started/email_notification'
else else
for key, value of data.messages for key, value of data.messages
@showAlert( key, value ) @showAlert( key, value )
@ -394,6 +394,151 @@ class Base extends App.ControllerContent
App.Config.set( 'getting_started/base', Base, 'Routes' ) App.Config.set( 'getting_started/base', Base, 'Routes' )
class EmailNotification extends App.ControllerContent
className: 'getstarted fit'
events:
'change .js-outbound [name=adapter]': 'toggleOutboundAdapter'
'submit .js-outbound': 'submit'
constructor: ->
super
# redirect if we are not admin
if !@authenticate(true)
@navigate '#'
return
# set title
@title 'Email Notifications'
@adapters = [
{
name: 'Email'
class: 'email'
link: '#getting_started/channel/email'
},
]
@fetch()
release: =>
@el.removeClass('fit getstarted')
fetch: ->
# get data
@ajax(
id: 'getting_started',
type: 'GET',
url: @apiPath + '/getting_started',
processData: true,
success: (data, status, xhr) =>
# check if import is active
if data.import_mode == true
@navigate '#import/' + data.import_backend
return
# render page
@render()
)
render: ->
@html App.view('getting_started/email_notification')()
adapters =
sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup'
smtp: 'SMTP - configure your own outgoing SMTP settings'
adapter_used = 'sendmail'
configureAttributesOutbound = [
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used },
]
new App.ControllerForm(
el: @$('.base-outbound-type'),
model: { configure_attributes: configureAttributesOutbound, className: '' },
)
@toggleOutboundAdapter()
toggleOutboundAdapter: =>
# show used backend
channel_used = { options: {} }
adapter = @$('.js-outbound [name=adapter]').val()
if adapter is 'smtp'
configureAttributesOutbound = [
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true, default: (channel_used['options']&&channel_used['options']['host']) },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true, default: (channel_used['options']&&channel_used['options']['password']) },
]
@form = new App.ControllerForm(
el: @$('.base-outbound-settings')
model: { configure_attributes: configureAttributesOutbound, className: '' }
)
else
@el.find('.base-outbound-settings').html('')
submit: (e) =>
e.preventDefault()
# get params
params = @formParam(e.target)
params['email'] = 'me@localhost'
@disable(e)
@showSlide('js-test')
@ajax(
id: 'email_notification'
type: 'POST'
url: @apiPath + '/channels/email_notification'
data: JSON.stringify( params )
processData: true
success: (data, status, xhr) =>
if data.result is 'ok'
for key, value of data.settings
App.Config.set( key, value )
if App.Config.get('system_online_service')
@navigate 'getting_started/channel/email_pre_configured'
else
@navigate 'getting_started/channel'
else
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
fail: =>
@showSlide('js-outbound')
@showAlert('js-outbound', data.message_human || data.message )
@enable(e)
)
goToSlide: (e) =>
e.preventDefault()
slide = $(e.target).data('slide')
@showSlide(slide)
showSlide: (name) =>
@hideAlert(name)
@$('.setup.wizard').addClass('hide')
@$(".setup.wizard.#{name}").removeClass('hide')
@$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus()
showAlert: (screen, message) =>
@$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) )
hideAlert: (screen) =>
@$(".#{screen}").find('.alert').addClass('hide')
disable: (e) =>
@formDisable(e)
@$('.wizard-controls .btn').attr('disabled', true)
enable: (e) =>
@formEnable(e)
@$('.wizard-controls .btn').attr('disabled', false)
App.Config.set( 'getting_started/email_notification', EmailNotification, 'Routes' )
class Channel extends App.ControllerContent class Channel extends App.ControllerContent
className: 'getstarted fit' className: 'getstarted fit'
@ -610,7 +755,7 @@ class ChannelEmail extends App.ControllerContent
@ajax( @ajax(
id: 'email_probe' id: 'email_probe'
type: 'POST' type: 'POST'
url: @apiPath + '/getting_started/email_probe' url: @apiPath + '/channels/email_probe'
data: JSON.stringify( params ) data: JSON.stringify( params )
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@ -643,7 +788,7 @@ class ChannelEmail extends App.ControllerContent
@ajax( @ajax(
id: 'email_inbound' id: 'email_inbound'
type: 'POST' type: 'POST'
url: @apiPath + '/getting_started/email_inbound' url: @apiPath + '/channels/email_inbound'
data: JSON.stringify( params ) data: JSON.stringify( params )
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@ -679,7 +824,7 @@ class ChannelEmail extends App.ControllerContent
@ajax( @ajax(
id: 'email_outbound' id: 'email_outbound'
type: 'POST' type: 'POST'
url: @apiPath + '/getting_started/email_outbound' url: @apiPath + '/channels/email_outbound'
data: JSON.stringify( params ) data: JSON.stringify( params )
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@ -705,7 +850,7 @@ class ChannelEmail extends App.ControllerContent
@ajax( @ajax(
id: 'email_verify' id: 'email_verify'
type: 'POST' type: 'POST'
url: @apiPath + '/getting_started/email_verify' url: @apiPath + '/channels/email_verify'
data: JSON.stringify( account ) data: JSON.stringify( account )
processData: true processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>

View file

@ -2,18 +2,20 @@ class App.Channel extends App.Model
@configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at' @configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/channels' @url: @apiPath + '/channels'
@configure_delete = true
@configure_attributes = [ displayName: ->
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } }, name = ''
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, if @options
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, if @options.inbound
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false }, name += "#{@options.inbound.options.user}@#{@options.inbound.options.host} (#{@options.inbound.adapter})"
{ name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: true, options: { true: 'yes', false: 'no' }, translate: true, default: true}, if @options.outbound
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, if @options.outbound
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' }, if name != ''
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true }, name += ' / '
] if @options.outbound.options
@configure_overview = [ name += "#{@options.outbound.options.host} (#{@options.outbound.adapter})"
'adapter', 'options::host', 'options::user', 'group' else
] name += " (#{@options.outbound.adapter})"
if name == ''
name = '???'
name

View file

@ -1,11 +1,20 @@
class App.EmailAddress extends App.Model class App.EmailAddress extends App.Model
@configure 'EmailAddress', 'realname', 'email', 'note', 'active', 'updated_at' @configure 'EmailAddress', 'realname', 'email', 'channel_id', 'note', 'active', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/email_addresses' @url: @apiPath + '/email_addresses'
@filterChannel: (options, type) =>
return options if type isnt 'collection'
_.filter(
options
(channel) ->
return channel if channel && channel.area is 'Email::Account'
)
@configure_attributes = [ @configure_attributes = [
{ name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false }, { name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false },
{ name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, null: false }, { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 250, null: false },
{ name: 'channel_id', display: 'Channel', tag: 'select', multiple: false, null: false, relation: 'Channel', nulloption: true, filter: @filterChannel },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'active', display: 'Active', tag: 'active', default: true },

View file

@ -0,0 +1,99 @@
<h1><%- @T('Email Accounts') %></h1>
<% if !_.isEmpty(@email_addresses_not_used): %>
<h2><%- @T('Not linked email addresses') %></h2>
<ul>
<% for email_address in @email_addresses_not_used: %>
<li data-id="<%= email_address.id %>"><a href="" data-type="email-address-edit"><%= email_address.email %></a>
<% end %>
</ul>
<% end %>
<table class="table table-hover user-list">
<thead>
<tr>
<th><%- @T('Inbound') %></th>
<th><%- @T('Outbound') %></th>
<th><%- @T('Email Adresses') %></th>
<th><%- @T('Action') %></th>
</tr>
</thead>
<tbody>
<% for channel in @channels: %>
<tr data-id="<%- channel.id %>">
<td class="<% if channel.status_in is 'ok': %>success<% else if channel.status_in is 'error': %>danger<% else: %>warning<% end %>">
<%- @T('State') %>: <%- @T(channel.status_in || 'unknown') %><br>
<%= channel.options.inbound.options.user %><br>
<a href="#" data-type="edit-inbound"><%= channel.options.inbound.options.host %> (<%= channel.options.inbound.adapter %>)</a>
</td>
<td class="<% if channel.status_out is 'ok': %>success<% else if channel.status_out is 'error': %>danger<% else: %>warning<% end %>">
<%- @T('State') %>: <%- @T(channel.status_out || 'unknown') %><br>
<% if channel.options.outbound && channel.options.outbound.options: %>
<%= channel.options.outbound.options.user %><br>
<a href="#" data-type="edit-outbound"><%= channel.options.outbound.options.host %>
<% end %>
(<%= channel.options.outbound.adapter %>)</a>
</td>
<td>
<ul>
<% if !_.isEmpty(channel.email_addresses): %>
<% for email_address in channel.email_addresses: %>
<li data-id="<%= email_address.id %>"><a href="" data-type="email-address-edit"><%= email_address.email %></a>
<% end %>
<% else: %>
<li><%- @T('none') %></li>
<% end %>
</ul>
<a href="#" data-type="email-address-new" title="<%- @Ti('New Email Address') %>"><svg class="icon-trash"><use xlink:href="#icon-plus"></use></svg></a>
</td>
<td>
<a href="#" data-type="delete" title="<%- @Ti('Delete') %>"><svg class="icon-trash"><use xlink:href="#icon-trash"></use></svg></a>
</td>
</tr>
<% if channel.status_in is 'error': %>
<tr>
<td colspan="4"><%= channel.last_log_in %></td>
</tr>
<% end %>
<% if channel.status_out is 'error': %>
<tr>
<td colspan="4"><%= channel.last_log_out %></td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<a data-type="new" class="btn btn--success"><%- @T('New') %></a>
<h1><%- @T('Notification Account') %></h1>
<table class="table table-hover user-list">
<thead>
<tr>
<th><%- @T('Outbound') %></th>
</tr>
</thead>
<tbody>
<tr data-id="<%- @channel.id %>">
<td class="<% if @channel.status_out is 'ok': %>success<% else if @channel.status_out is 'error': %>danger<% else: %>warning<% end %>">
<%- @T('State') %>: <%- @T(@channel.status_out || 'unknown') %><br>
<% if @channel.options.outbound && @channel.options.outbound.options: %>
<%= @channel.options.outbound.options.user %><br>
<a href="#" data-type="edit-notification-outbound"><%= @channel.options.outbound.options.host %>
<% end %>
(<%= @channel.options.outbound.adapter %>)</a>
</td>
</tr>
<% if @channel.status_in is 'error': %>
<tr>
<td colspan="1"><%= @channel.last_log_in %></td>
</tr>
<% end %>
<% if @channel.status_out is 'error': %>
<tr>
<td colspan="1"><%= @channel.last_log_out %></td>
</tr>
<% end %>
</tbody>
</table>

View file

@ -0,0 +1,92 @@
<div class="modal-dialog">
<form class="setup wizard js-intro">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<fieldset>
<div class="form-group">
<label><%- @T('Full Name') %></label>
<input type="text" class="form-control" value="" name="realname" placeholder="<%- @Ti('Organization Support') %>" required>
</div>
<div class="form-group">
<label><%- @T('Email') %></label>
<input type="email" class="form-control" value="" name="email" placeholder="<%- @Ti('support@example.com') %>" required>
</div>
<div class="form-group">
<label><%- @T('Password') %></label>
<input type="password" class="form-control" name="password" value="" required>
</div>
</fieldset>
</div>
<div class="wizard-controls center">
<button class="btn btn--primary align-right"><%- @T('Connect') %></button>
</div>
</div>
</form>
<form class="setup wizard hide js-probe">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Testing') %> <span class="js-email"></span>
</p>
</div>
</div>
</form>
<form class="setup wizard hide js-test">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Verifying...') %>
</p>
</div>
</div>
</form>
<form class="setup wizard hide js-verify">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Verify sending and receiving') %>
</p>
</div>
</div>
</form>
<form class="setup wizard hide js-inbound">
<div class="wizard-slide">
<h2><%- @T('Email Inbound') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<div class="base-inbound-settings"></div>
</div>
<div class="wizard-controls center">
<a class="btn btn--text btn--secondary js-back" data-slide="js-intro"><%- @T('Go Back') %></a>
<button class="btn btn--primary align-right"><%- @T( 'Continue' ) %></button>
</div>
</div>
</form>
<form class="setup wizard hide js-outbound">
<div class="wizard-slide">
<h2><%- @T('Email Outbound') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<div class="base-outbound-type"></div>
<div class="base-outbound-settings"></div>
</div>
<div class="wizard-controls center">
<a class="btn btn--text btn--secondary js-back" data-slide="js-inbound"><%- @T('Go Back') %></a>
<button class="btn btn--primary align-right"><%- @T( 'Continue' ) %></button>
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,39 @@
<div class="modal-dialog">
<form class="setup wizard hide js-probe">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Testing') %> <span class="js-email"></span>
</p>
</div>
</div>
</form>
<form class="setup wizard hide js-test">
<div class="wizard-slide">
<h2><%- @T('Email Account') %></h2>
<div class="wizard-body vertical justified">
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Verifying...') %>
</p>
</div>
</div>
</form>
<form class="setup wizard js-outbound">
<div class="wizard-slide">
<h2><%- @T('Email Outbound') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<div class="base-outbound-type"></div>
<div class="base-outbound-settings"></div>
</div>
<div class="wizard-controls center">
<button class="btn btn--primary align-right"><%- @T( 'Continue' ) %></button>
</div>
</div>
</form>
</div>

View file

@ -1,5 +0,0 @@
<form id="mail_adapter">
<div id="form-email-adapter"></div>
<div id="form-email-adapter-settings"></div>
<button data-type="" type="submit" class="btn"><%- @T( 'Submit' ) %></botton>
</form>

View file

@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div class="wizard-controls center"> <div class="wizard-controls center">
<a class="btn btn--text btn--secondary" href="#getting_started/base"><%- @T('Go Back') %></a> <a class="btn btn--text btn--secondary" href="#getting_started/email_notification"><%- @T('Go Back') %></a>
<a class="btn align-right" href="#getting_started/finish"><%- @T( 'Skip' ) %></a> <a class="btn align-right" href="#getting_started/finish"><%- @T( 'Skip' ) %></a>
</div> </div>
</div> </div>

View file

@ -64,7 +64,7 @@
<form class="setup wizard hide js-inbound"> <form class="setup wizard hide js-inbound">
<div class="wizard-slide"> <div class="wizard-slide">
<h2><%- @T('E-Mail Inbound') %></h2> <h2><%- @T('Email Inbound') %></h2>
<div class="wizard-body vertical justified"> <div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div> <div class="alert alert--danger hide" role="alert"></div>
<div class="base-inbound-settings"></div> <div class="base-inbound-settings"></div>
@ -78,7 +78,7 @@
<form class="setup wizard hide js-outbound"> <form class="setup wizard hide js-outbound">
<div class="wizard-slide"> <div class="wizard-slide">
<h2><%- @T('E-Mail Outbound') %></h2> <h2><%- @T('Email Outbound') %></h2>
<div class="wizard-body vertical justified"> <div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div> <div class="alert alert--danger hide" role="alert"></div>
<div class="base-outbound-type"></div> <div class="base-outbound-type"></div>

View file

@ -0,0 +1,30 @@
<div class="main flex vertical centered darkBackground">
<svg class="wizard-logo icon-full-logo"><use xlink:href="#icon-full-logo" /></svg>
<form class="setup wizard js-outbound">
<div class="wizard-slide">
<h2><%- @T('Email Notification') %></h2>
<div class="wizard-body vertical justified">
<div class="alert alert--danger hide" role="alert"></div>
<div class="base-outbound-type"></div>
<div class="base-outbound-settings"></div>
</div>
<div class="wizard-controls center">
<a class="btn btn--text btn--secondary" href="#getting_started/base"><%- @T('Go Back') %></a>
<button class="btn btn--primary align-right"><%- @T( 'Continue' ) %></button>
</div>
</div>
</form>
<form class="setup wizard hide js-test">
<div class="wizard-slide">
<h2><%- @T('Email Notification') %></h2>
<div class="wizard-body vertical justified">
<p class="wizard-loadingText">
<span class="loading icon"></span> <%- @T('Verifying...') %>
</p>
</div>
</div>
</form>
</div>

View file

@ -11,14 +11,25 @@ JSON
Example: Example:
{ {
"id":1, "id":1,
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
"group_id:": 1, "group_id:": 1,
"options":{ "options":{
"host":"mail.example.com", "inbound": {
"user":"some_user", "adapter":"IMAP",
"password":"some_password", "options": {
"ssl":true "host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"ssl":true
},
"outbound":{
"adapter":"SMTP",
"options": {
"host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"start_tls":true
}
}, },
"active":true, "active":true,
"updated_at":"2012-09-14T17:51:53Z", "updated_at":"2012-09-14T17:51:53Z",
@ -29,10 +40,10 @@ Example:
{ {
"id":1, "id":1,
"area":"Twitter::Inbound", "area":"Twitter::Account",
"adapter":"Twitter",
"group_id:": 1, "group_id:": 1,
"options":{ "options":{
"adapter":"Twitter",
"auth": { "auth": {
"consumer_key":"PJ4c3dYYRtSZZZdOKo8ow", "consumer_key":"PJ4c3dYYRtSZZZdOKo8ow",
"consumer_secret":"ggAdnJE2Al1Vv0cwwvX5bdvKOieFs0vjCIh5M8Dxk", "consumer_secret":"ggAdnJE2Al1Vv0cwwvX5bdvKOieFs0vjCIh5M8Dxk",
@ -84,14 +95,12 @@ Response:
[ [
{ {
"id": 1, "id": 1,
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
... ...
}, },
{ {
"id": 2, "id": 2,
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
... ...
} }
] ]
@ -114,8 +123,7 @@ GET /api/v1/channels/#{id}.json
Response: Response:
{ {
"id": 1, "id": 1,
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
... ...
} }
@ -136,22 +144,33 @@ POST /api/v1/channels.json
Payload: Payload:
{ {
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
"group_id:": 1, "group_id:": 1,
"options":{ "options":{
"host":"mail.example.com", "inbound":
"user":"some_user", "adapter":"IMAP",
"password":"some_password", "options":{
"ssl":true "host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"ssl":true
},
},
"outbound":{
"adapter":"SMTP",
"options": {
"host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"start_tls":true
}
}, },
"active":true, "active":true,
} }
Response: Response:
{ {
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
... ...
} }
@ -173,14 +192,26 @@ PUT /api/v1/channels/{id}.json
Payload: Payload:
{ {
"id":1, "id":1,
"area":"Email::Inbound", "area":"Email::Account",
"adapter":"IMAP",
"group_id:": 1, "group_id:": 1,
"options":{ "options":{
"host":"mail.example.com", "inbound":
"user":"some_user", "adapter":"IMAP",
"password":"some_password", "options":{
"ssl":true "host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"ssl":true
},
},
"outbound":{
"adapter":"SMTP",
"options": {
"host":"mail.example.com",
"user":"some_user",
"password":"some_password",
"start_tls":true
}
}, },
"active":true, "active":true,
} }
@ -219,4 +250,207 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)
model_destory_render(Channel, params) model_destory_render(Channel, params)
end end
def email_index
return if deny_if_not_role(Z_ROLENAME_ADMIN)
assets = {}
Channel.all.each {|channel|
assets = channel.assets(assets)
}
EmailAddress.all.each {|email_address|
assets = email_address.assets(assets)
}
render json: {
assets: assets
}
end
def email_probe
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# probe settings based on email and password
result = EmailHelper::Probe.full(
email: params[:email],
password: params[:password],
)
# verify if user+host already exists
if result[:result] == 'ok'
return if email_account_duplicate?(result)
end
render json: result
end
def email_outbound
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# connection test
render json: EmailHelper::Probe.outbound(params, params[:email])
end
def email_inbound
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# connection test
result = EmailHelper::Probe.inbound(params)
# check account duplicate
return if email_account_duplicate?({ setting: { inbound: params } }, params[:channel_id])
render json: result
end
def email_verify
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
email = params[:email] || params[:meta][:email]
email = email.downcase
channel_id = params[:channel_id]
# check account duplicate
return if email_account_duplicate?({ setting: { inbound: params[:inbound] } }, channel_id)
# check delivery for 30 sek.
result = EmailHelper::Verify.email(
outbound: params[:outbound],
inbound: params[:inbound],
sender: email,
subject: params[:subject],
)
if result[:result] != 'ok'
render json: result
return
end
# update account
if channel_id
channel = Channel.find(channel_id)
channel.update_attributes(
options: {
inbound: params[:inbound],
outbound: params[:outbound],
},
last_log_in: '',
last_log_out: '',
status_in: nil,
status_out: nil,
)
render json: {
result: 'ok',
}
return
end
# create new account
channel = Channel.create(
area: 'Email::Account',
options: {
inbound: params[:inbound],
outbound: params[:outbound],
},
active: true,
group_id: Group.first.id,
)
# remember address && set channel for email address
address = EmailAddress.find_by(email: email)
# if we are on initial setup, use already exisiting dummy email address
if Channel.count == 1
address = EmailAddress.first
end
if address
address.update_attributes(
realname: params[:meta][:realname],
email: email,
active: true,
channel_id: channel.id,
)
else
address = EmailAddress.create(
realname: params[:meta][:realname],
email: email,
active: true,
channel_id: channel.id,
)
end
render json: {
result: 'ok',
}
end
def email_notification
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
adapter = params[:adapter].downcase
email = Setting.get('notification_sender')
# connection test
result = EmailHelper::Probe.outbound(params, email)
# save settings
if result[:result] == 'ok'
# validate adapter
if adapter !~ /^(smtp|sendmail)$/
render json: {
result: 'failed',
message: "Unknown adapter '#{adapter}'",
}
return
end
Channel.where(area: 'Email::Notification').each {|channel|
active = false
if adapter =~ /^#{channel.options[:outbound][:adapter]}$/i
active = true
channel.options = {
outbound: {
adapter: adapter,
options: params[:options],
},
}
end
channel.active = active
channel.save
}
end
render json: result
end
private
def email_account_duplicate?(result, channel_id = nil)
Channel.where(area: 'Email::Account').each {|channel|
next if !channel.options
next if !channel.options[:inbound]
next if !channel.options[:inbound][:adapter]
next if channel.options[:inbound][:adapter] != result[:setting][:inbound][:adapter]
next if channel.options[:inbound][:options][:host] != result[:setting][:inbound][:options][:host]
next if channel.options[:inbound][:options][:user] != result[:setting][:inbound][:options][:user]
next if channel.id.to_s == channel_id.to_s
render json: {
result: 'duplicate',
message: 'Account already exists!',
}
return true
}
false
end
end end

View file

@ -188,119 +188,6 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password}
} }
end end
def email_probe
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# probe settings based on email and password
render json: EmailHelper::Probe.full(
email: params[:email],
password: params[:password],
)
end
def email_outbound
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# connection test
render json: EmailHelper::Probe.outbound(params, params[:email])
end
def email_inbound
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# connection test
render json: EmailHelper::Probe.inbound(params)
end
def email_verify
# check admin permissions
return if deny_if_not_role(Z_ROLENAME_ADMIN)
# send verify email to inbox
if !params[:subject]
subject = '#' + rand(99_999_999_999).to_s
else
subject = params[:subject]
end
result = EmailHelper::Verify.email(
outbound: params[:outbound],
inbound: params[:inbound],
sender: params[:meta][:email],
subject: subject,
)
# check delivery for 30 sek.
if result[:result] != 'ok'
render json: result
return
end
# remember address
address = EmailAddress.where( email: params[:meta][:email] ).first
if !address
address = EmailAddress.first
end
if address
address.update_attributes(
realname: params[:meta][:realname],
email: params[:meta][:email],
active: 1,
updated_by_id: 1,
created_by_id: 1,
)
else
EmailAddress.create(
realname: params[:meta][:realname],
email: params[:meta][:email],
active: 1,
updated_by_id: 1,
created_by_id: 1,
)
end
# store mailbox
Channel.create(
area: 'Email::Inbound',
adapter: params[:inbound][:adapter],
options: params[:inbound][:options],
group_id: 1,
active: 1,
updated_by_id: 1,
created_by_id: 1,
)
# save settings
if params[:outbound][:adapter] =~ /^smtp$/i
smtp = Channel.where( adapter: 'SMTP', area: 'Email::Outbound' ).first
smtp.options = params[:outbound][:options]
smtp.active = true
smtp.save!
sendmail = Channel.where( adapter: 'Sendmail' ).first
sendmail.active = false
sendmail.save!
else
sendmail = Channel.where( adapter: 'Sendmail', area: 'Email::Outbound' ).first
sendmail.options = {}
sendmail.active = true
sendmail.save!
smtp = Channel.where( adapter: 'SMTP' ).first
smtp.active = false
smtp.save
end
render json: {
result: 'ok',
}
end
private private
def auto_wizard_enabled_response def auto_wizard_enabled_response

View file

@ -3,24 +3,107 @@
class Channel < ApplicationModel class Channel < ApplicationModel
store :options store :options
def self.fetch =begin
channels = Channel.where( 'active = ? AND area LIKE ?', true, '%::Inbound' )
channels.each { |channel|
begin
# we need to require each channel backend individually otherwise we get a
# 'warning: toplevel constant Twitter referenced by Channel::Twitter' error e.g.
# so we have to convert the channel name to the filename via Rails String.underscore
# http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html
require "channel/#{channel[:adapter].to_filename}"
channel_object = Object.const_get("Channel::#{channel[:adapter].to_classname}") fetch all accounts
channel_instance = channel_object.new
channel_instance.fetch(channel) Channel.fetch
rescue => e
logger.error "Can't use Channel::#{channel[:adapter].to_classname}" =end
logger.error e.inspect
logger.error e.backtrace def self.fetch
end channels = Channel.where('active = ? AND area LIKE ?', true, '%::Account')
} channels.each(&:fetch)
end end
=begin
fetch one account
channel = Channel.where(area: 'Email::Account').first
channel.fetch
=end
def fetch
adapter = options[:adapter]
adapter_options = options
if options[:options]
adapter_options = options[:options]
elsif options[:inbound] && options[:inbound][:adapter]
adapter = options[:inbound][:adapter]
adapter_options = options[:inbound][:options]
end
begin
# we need to require each channel backend individually otherwise we get a
# 'warning: toplevel constant Twitter referenced by Channel::Driver::Twitter' error e.g.
# so we have to convert the channel name to the filename via Rails String.underscore
# http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
driver_instance.fetch(adapter_options, self)
self.status_in = 'ok'
self.last_log_in = ''
save
rescue => e
error = "Can't use Channel::Driver::#{adapter.to_classname}: #{e.inspect}"
logger.error error
logger.error e.backtrace
self.status_in = 'error'
self.last_log_in = error
save
end
end
=begin
send via account
channel = Channel.where(area: 'Email::Account').first
channel.deliver(mail_params, notification)
=end
def deliver(mail_params, notification = false)
adapter = options[:adapter]
adapter_options = options
if options[:options]
adapter_options = options[:options]
elsif options[:outbound] && options[:outbound][:adapter]
adapter = options[:outbound][:adapter]
adapter_options = options[:outbound][:options]
end
begin
# we need to require each channel backend individually otherwise we get a
# 'warning: toplevel constant Twitter referenced by Channel::Driver::Twitter' error e.g.
# so we have to convert the channel name to the filename via Rails String.underscore
# http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
driver_instance.send(adapter_options, mail_params, notification)
self.status_out = 'ok'
self.last_log_out = ''
save
rescue => e
error = "Can't use Channel::Driver::#{adapter.to_classname}: #{e.inspect}"
logger.error error
logger.error e.backtrace
self.status_out = 'error'
self.last_log_out = error
save
end
end
end end

View file

@ -0,0 +1,4 @@
class Channel
class Driver
end
end

View file

@ -2,9 +2,9 @@
require 'facebook' require 'facebook'
class Channel::Facebook class Channel::Driver::Facebook
def fetch (channel) def fetch (adapter_options, channel)
@channel = channel @channel = channel
@facebook = Facebook.new( @channel[:options] ) @facebook = Facebook.new( @channel[:options] )

View file

@ -2,17 +2,17 @@
require 'net/imap' require 'net/imap'
class Channel::Imap < Channel::EmailParser class Channel::Driver::Imap < Channel::EmailParser
def fetch (channel, check_type = '', verify_string = '') def fetch (options, channel, check_type = '', verify_string = '')
ssl = true ssl = true
port = 993 port = 993
if channel[:options].key?(:ssl) && channel[:options][:ssl].to_s == 'false' if options.key?(:ssl) && options[:ssl].to_s == 'false'
ssl = false ssl = false
port = 143 port = 143
end end
Rails.logger.info "fetching imap (#{channel[:options][:host]}/#{channel[:options][:user]} port=#{port},ssl=#{ssl})" Rails.logger.info "fetching imap (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})"
# on check, reduce open_timeout to have faster probing # on check, reduce open_timeout to have faster probing
timeout = 12 timeout = 12
@ -22,24 +22,24 @@ class Channel::Imap < Channel::EmailParser
Timeout.timeout(timeout) do Timeout.timeout(timeout) do
@imap = Net::IMAP.new( channel[:options][:host], port, ssl, nil, false ) @imap = Net::IMAP.new( options[:host], port, ssl, nil, false )
end end
# try LOGIN, if not - try plain # try LOGIN, if not - try plain
begin begin
@imap.authenticate( 'LOGIN', channel[:options][:user], channel[:options][:password] ) @imap.authenticate( 'LOGIN', options[:user], options[:password] )
rescue => e rescue => e
if e.to_s !~ /(unsupported\s(authenticate|authentication)\smechanism|not\ssupported)/i if e.to_s !~ /(unsupported\s(authenticate|authentication)\smechanism|not\ssupported)/i
raise e raise e
end end
@imap.login( channel[:options][:user], channel[:options][:password] ) @imap.login( options[:user], options[:password] )
end end
if !channel[:options][:folder] || channel[:options][:folder].empty? if !options[:folder] || options[:folder].empty?
@imap.select('INBOX') @imap.select('INBOX')
else else
@imap.select( channel[:options][:folder] ) @imap.select( options[:folder] )
end end
if check_type == 'check' if check_type == 'check'
Rails.logger.info 'check only mode, fetch no emails' Rails.logger.info 'check only mode, fetch no emails'

View file

@ -1,6 +1,6 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::MailStdin < Channel::EmailParser class Channel::Driver::MailStdin < Channel::EmailParser
def initialize def initialize
Rails.logger.info 'read main from STDIN' Rails.logger.info 'read main from STDIN'

View file

@ -2,19 +2,19 @@
require 'net/pop' require 'net/pop'
class Channel::Pop3 < Channel::EmailParser class Channel::Driver::Pop3 < Channel::EmailParser
def fetch (channel, check_type = '', verify_string = '') def fetch (options, channel, check_type = '', verify_string = '')
ssl = true ssl = true
port = 995 port = 995
if channel[:options].key?(:ssl) && channel[:options][:ssl].to_s == 'false' if options.key?(:ssl) && options[:ssl].to_s == 'false'
ssl = false ssl = false
port = 110 port = 110
end end
Rails.logger.info "fetching pop3 (#{channel[:options][:host]}/#{channel[:options][:user]} port=#{port},ssl=#{ssl})" Rails.logger.info "fetching pop3 (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})"
@pop = Net::POP3.new( channel[:options][:host], port ) @pop = Net::POP3.new( options[:host], port )
# on check, reduce open_timeout to have faster probing # on check, reduce open_timeout to have faster probing
if check_type == 'check' if check_type == 'check'
@ -25,7 +25,7 @@ class Channel::Pop3 < Channel::EmailParser
if ssl if ssl
@pop.enable_ssl(OpenSSL::SSL::VERIFY_NONE) @pop.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
end end
@pop.start( channel[:options][:user], channel[:options][:password] ) @pop.start( options[:user], options[:password] )
if check_type == 'check' if check_type == 'check'
Rails.logger.info 'check only mode, fetch no emails' Rails.logger.info 'check only mode, fetch no emails'
disconnect disconnect

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::Sendmail class Channel::Driver::Sendmail
def send(attr, _channel, notification = false) def send(_options, attr, notification = false)
# return if we run import mode # return if we run import mode
return if Setting.get('import_mode') return if Setting.get('import_mode')

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Channel::SMTP class Channel::Driver::Smtp
def send(attr, channel, notification = false) def send(options, attr, notification = false)
# return if we run import mode # return if we run import mode
return if Setting.get('import_mode') return if Setting.get('import_mode')
@ -9,11 +9,11 @@ class Channel::SMTP
mail = Channel::EmailBuild.build(attr, notification) mail = Channel::EmailBuild.build(attr, notification)
mail.delivery_method :smtp, { mail.delivery_method :smtp, {
openssl_verify_mode: 'none', openssl_verify_mode: 'none',
address: channel[:options][:host], address: options[:host],
port: channel[:options][:port] || 25, port: options[:port] || 25,
domain: channel[:options][:host], domain: options[:host],
user_name: channel[:options][:user], user_name: options[:user],
password: channel[:options][:password], password: options[:password],
enable_starttls_auto: true, enable_starttls_auto: true,
} }
mail.deliver mail.deliver

View file

@ -1,8 +1,8 @@
# Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/
class Channel::Twitter class Channel::Driver::Twitter
def fetch (channel) def fetch (adapter_options, channel)
@channel = channel @channel = channel
@tweet = Tweet.new( @channel[:options][:auth] ) @tweet = Tweet.new( @channel[:options][:auth] )
@ -21,7 +21,7 @@ class Channel::Twitter
def send(article, _notification = false) def send(article, _notification = false)
@channel = Channel.find_by( area: 'Twitter::Inbound', active: true ) @channel = Channel.find_by( area: 'Twitter::Account', active: true )
@tweet = Tweet.new( @channel[:options][:auth] ) @tweet = Tweet.new( @channel[:options][:auth] )
tweet = @tweet.from_article(article) tweet = @tweet.from_article(article)

View file

@ -1,28 +0,0 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
require 'net/imap'
module Channel::EmailSend
def self.send(article, notification = false)
channel = Channel.find_by( area: 'Email::Outbound', active: true )
begin
# we need to require the channel backend individually otherwise we get a
# 'warning: toplevel constant Twitter referenced by Channel::Twitter' error e.g.
# so we have to convert the channel name to the filename via Rails String.underscore
# http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html
require "channel/#{channel[:adapter].underscore}"
channel_object = Object.const_get("Channel::#{channel[:adapter]}")
channel_instance = channel_object.new
result = channel_instance.send(article, channel, notification)
channel_instance.disconnect
rescue => e
Rails.logger.error "Can't use Channel::#{channel[:adapter]}"
Rails.logger.error e.inspect
Rails.logger.error e.backtrace
end
result
end
end

View file

@ -2,6 +2,7 @@
class EmailAddress < ApplicationModel class EmailAddress < ApplicationModel
has_many :groups, after_add: :cache_update, after_remove: :cache_update has_many :groups, after_add: :cache_update, after_remove: :cache_update
belongs_to :channel
validates :realname, presence: true validates :realname, presence: true
validates :email, presence: true validates :email, presence: true

View file

@ -11,7 +11,15 @@ class Observer::Ticket::Article::CommunicateEmail::BackgroundJob
subject = ticket.subject_build( record.subject ) subject = ticket.subject_build( record.subject )
# send email # send email
message = Channel::EmailSend.send( if !ticket.group.email_address_id
fail "Can't send email, no email address definde for group id '#{ticket.group.id}'"
elsif !ticket.group.email_address.channel_id
fail "Can't send email, no channel definde for email_address id '#{ticket.group.email_address_id}'"
end
channel = ticket.group.email_address.channel
# get linked channel and send
message = channel.deliver(
{ {
message_id: record.message_id, message_id: record.message_id,
in_reply_to: record.in_reply_to, in_reply_to: record.in_reply_to,

View file

@ -1,6 +1,6 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
require 'channel/facebook' require 'channel/driver/facebook'
class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer
observe 'ticket::_article' observe 'ticket::_article'
@ -19,7 +19,7 @@ class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer
type = Ticket::Article::Type.lookup( id: record.type_id ) type = Ticket::Article::Type.lookup( id: record.type_id )
return if type['name'] !~ /\Afacebook/ return if type['name'] !~ /\Afacebook/
facebook = Channel::Facebook.new facebook = Channel::Driver::Facebook.new
post = facebook.send({ post = facebook.send({
type: type['name'], type: type['name'],
to: record.to, to: record.to,

View file

@ -1,7 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
# http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html
require 'channel/twitter' require 'channel/driver/twitter'
class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer
observe 'ticket::_article' observe 'ticket::_article'
@ -20,7 +20,7 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer
type = Ticket::Article::Type.lookup( id: record.type_id ) type = Ticket::Article::Type.lookup( id: record.type_id )
return if type['name'] !~ /\Atwitter/ return if type['name'] !~ /\Atwitter/
twitter = Channel::Twitter.new twitter = Channel::Driver::Twitter.new
tweet = twitter.send({ tweet = twitter.send({
type: type['name'], type: type['name'],
to: record.to, to: record.to,

View file

@ -1,6 +1,14 @@
Zammad::Application.routes.draw do Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path api_path = Rails.configuration.api_path
# email helper
match api_path + '/channels/email_index', to: 'channels#email_index', via: :get
match api_path + '/channels/email_probe', to: 'channels#email_probe', via: :post
match api_path + '/channels/email_outbound', to: 'channels#email_outbound', via: :post
match api_path + '/channels/email_inbound', to: 'channels#email_inbound', via: :post
match api_path + '/channels/email_verify', to: 'channels#email_verify', via: :post
match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post
# channels # channels
match api_path + '/channels', to: 'channels#index', via: :get match api_path + '/channels', to: 'channels#index', via: :get
match api_path + '/channels/:id', to: 'channels#show', via: :get match api_path + '/channels/:id', to: 'channels#show', via: :get

View file

@ -2,13 +2,9 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path api_path = Rails.configuration.api_path
# getting_started # getting_started
match api_path + '/getting_started', to: 'getting_started#index', via: :get match api_path + '/getting_started', to: 'getting_started#index', via: :get
match api_path + '/getting_started/auto_wizard/:token', to: 'getting_started#auto_wizard_admin', via: :get match api_path + '/getting_started/auto_wizard/:token', to: 'getting_started#auto_wizard_admin', via: :get
match api_path + '/getting_started/auto_wizard', to: 'getting_started#auto_wizard_admin', via: :get match api_path + '/getting_started/auto_wizard', to: 'getting_started#auto_wizard_admin', via: :get
match api_path + '/getting_started/base', to: 'getting_started#base', via: :post match api_path + '/getting_started/base', to: 'getting_started#base', via: :post
match api_path + '/getting_started/email_probe', to: 'getting_started#email_probe', via: :post
match api_path + '/getting_started/email_outbound', to: 'getting_started#email_outbound', via: :post
match api_path + '/getting_started/email_inbound', to: 'getting_started#email_inbound', via: :post
match api_path + '/getting_started/email_verify', to: 'getting_started#email_verify', via: :post
end end

View file

@ -0,0 +1,57 @@
class UpdateChannel < ActiveRecord::Migration
def up
add_column :email_addresses, :channel_id, :integer, null: true
EmailAddress.reset_column_information
channel = Channel.find_by(area: 'Email::Inbound')
EmailAddress.all.each {|email_address|
email_address.channel_id = channel.id
email_address.save
}
add_column :channels, :last_log_in, :text, limit: 500.kilobytes + 1, null: true
add_column :channels, :last_log_out, :text, limit: 500.kilobytes + 1, null: true
add_column :channels, :status_in, :string, limit: 100, null: true
add_column :channels, :status_out, :string, limit: 100, null: true
Channel.reset_column_information
channel_outbound = Channel.find_by(area: 'Email::Outbound', active: true)
Channel.all.each {|channel|
if channel.area == 'Email::Inbound'
channel.area = 'Email::Account'
options = {
inbound: {
adapter: channel.adapter.downcase,
options: channel.options,
},
outbound: {
adapter: channel_outbound.adapter.downcase,
options: channel_outbound.options,
},
}
channel.options = options
channel.save
elsif channel.area == 'Email::Outbound'
channel.area = 'Email::Notification'
options = {
outbound: {
adapter: channel.adapter,
options: channel.options,
},
}
channel.options = options
channel.save
elsif channel.area == 'Twitter::Inbound'
channel.area = 'Twitter::Account'
channel.options[:adapter] = channel.adapter
channel.save
end
}
remove_column :channels, :adapter
Channel.reset_column_information
end
end

View file

@ -1743,21 +1743,28 @@ Overview.create_if_not_exists(
) )
Channel.create_if_not_exists( Channel.create_if_not_exists(
adapter: 'SMTP', area: 'Email::Notification',
area: 'Email::Outbound',
options: { options: {
host: 'host.example.com', outbound: {
user: '', adapter: 'smtp',
password: '', options: {
ssl: true, host: 'host.example.com',
user: '',
password: '',
ssl: true,
},
},
}, },
group_id: 1, group_id: 1,
active: false, active: false,
) )
Channel.create_if_not_exists( Channel.create_if_not_exists(
adapter: 'Sendmail', area: 'Email::Notification',
area: 'Email::Outbound', options: {
options: {}, outbound: {
adapter: 'sendmail',
},
},
active: true, active: true,
) )

View file

@ -14,26 +14,28 @@ returns on success
{ {
result: 'ok', result: 'ok',
inbound: { settings: {
adapter: 'imap', inbound: {
options: { adapter: 'imap',
host: 'imap.gmail.com', options: {
port: 993, host: 'imap.gmail.com',
ssl: true, port: 993,
user: 'some@example.com', ssl: true,
password: 'password', user: 'some@example.com',
password: 'password',
},
}, },
}, outbound: {
outbound: { adapter: 'smtp',
adapter: 'smtp', options: {
options: { host: 'smtp.gmail.com',
host: 'smtp.gmail.com', port: 25,
port: 25, ssl: true,
ssl: true, user: 'some@example.com',
user: 'some@example.com', password: 'password',
password: 'password', },
}, },
}, }
} }
returns on fail returns on fail
@ -150,7 +152,7 @@ get result of inbound probe
result = EmailHelper::Probe.inbound( result = EmailHelper::Probe.inbound(
adapter: 'imap', adapter: 'imap',
options: { settings: {
host: 'imap.gmail.com', host: 'imap.gmail.com',
port: 993, port: 993,
ssl: true, ssl: true,
@ -184,24 +186,22 @@ returns on fail
def self.inbound(params) def self.inbound(params)
# validate params adapter = params[:adapter].downcase
if !params[:adapter]
result = {
result: 'invalid',
message: 'Invalid, need adapter!',
}
return result
end
# connection test # connection test
begin begin
if params[:adapter] =~ /^imap$/i
Channel::Imap.new.fetch( { options: params[:options] }, 'check' ) # validate adapter
elsif params[:adapter] =~ /^pop3$/i if adapter !~ /^(imap|pop3)$/
Channel::Pop3.new.fetch( { options: params[:options] }, 'check' ) fail "Unknown adapter '#{adapter}'"
else
fail "Invalid adapter '#{params[:adapter]}'"
end end
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
driver_instance.fetch(params[:options], nil, 'check')
rescue => e rescue => e
message_human = '' message_human = ''
translations.each {|key, message| translations.each {|key, message|
@ -238,7 +238,8 @@ get result of outbound probe
password: 'password', password: 'password',
} }
}, },
'sender@example.com', 'sender_and_recipient_of_test_email@example.com',
'subject of probe email',
) )
returns on success returns on success
@ -266,15 +267,9 @@ returns on fail
def self.outbound(params, email, subject = nil) def self.outbound(params, email, subject = nil)
# validate params adapter = params[:adapter].downcase
if !params[:adapter]
result = {
result: 'invalid',
message: 'Invalid, need adapter!',
}
return result
end
# prepare test email
if subject if subject
mail = { mail = {
:from => email, :from => email,
@ -295,29 +290,29 @@ returns on fail
# test connection # test connection
begin begin
if params[:adapter] =~ /^smtp$/i # validate adapter
if adapter !~ /^(smtp|sendmail)$/
fail "Unknown adapter '#{adapter}'"
end
# in case, fill missing params # set smtp defaults
if adapter =~ /^smtp$/
if !params[:options].key?(:port) if !params[:options].key?(:port)
params[:options][:port] = 25 params[:options][:port] = 25
end end
if !params[:options].key?(:ssl) if !params[:options].key?(:ssl)
params[:options][:ssl] = true params[:options][:ssl] = true
end end
Channel::SMTP.new.send(
mail,
{
options: params[:options]
}
)
elsif params[:adapter] =~ /^sendmail$/i
Channel::Sendmail.new.send(
mail,
nil
)
else
fail "Invalid adapter '#{params[:adapter]}'"
end end
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
driver_instance.send(
params[:options],
mail,
)
rescue => e rescue => e
# check if sending email was ok, but mailserver rejected # check if sending email was ok, but mailserver rejected

View file

@ -26,7 +26,7 @@ get result of inbound probe
password: 'password', password: 'password',
}, },
}, },
sender: 'sender@example.com', sender: 'sender_and_recipient_of_verify_email@example.com',
) )
returns on success returns on success
@ -56,12 +56,13 @@ or
def self.email(params) def self.email(params)
# send verify email # send verify email
if !params[:subject] if !params[:subject] || params[:subject].empty?
subject = '#' + rand(99_999_999_999).to_s subject = '#' + rand(99_999_999_999).to_s
else else
subject = params[:subject] subject = params[:subject]
end end
result = EmailHelper::Probe.outbound( params[:outbound], params[:sender], subject ) puts "VERIFY #{subject.inspect}/#{params[:sender]}"
result = EmailHelper::Probe.outbound(params[:outbound], params[:sender], subject)
# looking for verify email # looking for verify email
(1..5).each { (1..5).each {
@ -72,9 +73,9 @@ or
begin begin
if params[:inbound][:adapter] =~ /^imap$/i if params[:inbound][:adapter] =~ /^imap$/i
found = Channel::Imap.new.fetch( { options: params[:inbound][:options] }, 'verify', subject ) found = Channel::Driver::Imap.new.fetch(params[:inbound][:options], self, 'verify', subject)
else else
found = Channel::Pop3.new.fetch( { options: params[:inbound][:options] }, 'verify', subject ) found = Channel::Driver::Pop3.new.fetch(params[:inbound][:options], self, 'verify', subject)
end end
rescue => e rescue => e
result = { result = {

View file

@ -111,7 +111,9 @@ module NotificationFactory
content_type = data[:content_type] content_type = data[:content_type]
end end
Channel::EmailSend.send( # get active Email::Outbound Channel and send
channel = Channel.find_by(area: 'Email::Notification', active: true)
channel.deliver(
{ {
# in_reply_to: in_reply_to, # in_reply_to: in_reply_to,
from: sender, from: sender,

View file

@ -370,6 +370,19 @@ test( "table test 2", function() {
created_at: '2014-06-10T11:17:34.000Z', created_at: '2014-06-10T11:17:34.000Z',
}, },
]) ])
App.Channel.configure_delete = true
App.Channel.configure_attributes = [
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } },
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false },
{ name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: true, options: { true: 'yes', false: 'no' }, translate: true, default: true},
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' },
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true },
]
App.Channel.refresh( [ App.Channel.refresh( [
{ {
id: 1, id: 1,

View file

@ -44,13 +44,9 @@ class AaaGettingStartedTest < TestCase
value: 'test1234äöüß', value: 'test1234äöüß',
) )
click( css: '.js-admin .btn--success' ) click( css: '.js-admin .btn--success' )
watch_for(
css: '.js-base h2',
value: 'Organization',
)
# getting started - base # getting started - base
match( watch_for(
css: '.js-base h2', css: '.js-base h2',
value: 'Organization', value: 'Organization',
) )
@ -76,19 +72,27 @@ class AaaGettingStartedTest < TestCase
click( click(
css: '.js-base .btn--primary', css: '.js-base .btn--primary',
) )
# getting started - email notification
watch_for( watch_for(
css: 'body', css: '.js-outbound h2',
value: 'channel', value: 'Email Notification',
) )
location_check( location_check(
url: '#getting_started/channel', url: '#getting_started/email_notification',
)
click(
css: '.js-outbound .btn--primary',
) )
# getting started - create email account # getting started - create email account
match( watch_for(
css: '.js-channel h2', css: '.js-channel h2',
value: 'Connect Channels', value: 'Connect Channels',
) )
location_check(
url: '#getting_started/channel',
)
click( click(
css: '.js-channel .email .provider_name', css: '.js-channel .email .provider_name',
) )
@ -117,14 +121,10 @@ class AaaGettingStartedTest < TestCase
value: 'invite', value: 'invite',
timeout: 100, timeout: 100,
) )
location_check(
url: '#getting_started/agents',
)
# invite agent1 # invite agent1
match( location_check(
css: 'body', url: '#getting_started/agents',
value: 'Invite',
) )
set( set(
css: '.js-agent input[name="firstname"]', css: '.js-agent input[name="firstname"]',

View file

@ -20,6 +20,7 @@ class FacebookTest < ActiveSupport::TestCase
provider_page_name = 'Hansi Merkurs Hutfabrik' provider_page_name = 'Hansi Merkurs Hutfabrik'
provider_options = { provider_options = {
adapter: 'facebook',
auth: { auth: {
access_token: provider_key access_token: provider_key
}, },
@ -34,8 +35,7 @@ class FacebookTest < ActiveSupport::TestCase
current = Channel.where( adapter: 'Facebook' ) current = Channel.where( adapter: 'Facebook' )
current.each(&:destroy) current.each(&:destroy)
Channel.create( Channel.create(
adapter: 'Facebook', area: 'Facebook::Account',
area: 'Facebook::Inbound',
options: provider_options, options: provider_options,
active: true, active: true,
created_by_id: 1, created_by_id: 1,

View file

@ -28,12 +28,12 @@ class TwitterTest < ActiveSupport::TestCase
me_bauer_token_secret = 'T8ph5afeSDjGDA9X1ZBlzEvoSiXfN266ZZUMj5UaY' me_bauer_token_secret = 'T8ph5afeSDjGDA9X1ZBlzEvoSiXfN266ZZUMj5UaY'
# add channel # add channel
current = Channel.where( adapter: 'Twitter' ) current = Channel.where(area: 'Twitter::Account')
current.each(&:destroy) current.each(&:destroy)
Channel.create( Channel.create(
adapter: 'Twitter', area: 'Twitter::Account',
area: 'Twitter::Inbound',
options: { options: {
adapter: 'twitter',
auth: { auth: {
consumer_key: consumer_key, consumer_key: consumer_key,
consumer_secret: consumer_secret, consumer_secret: consumer_secret,