diff --git a/Gemfile b/Gemfile
index 537856bf0..2474d3fcd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -55,6 +55,7 @@ gem 'net-ldap'
gem 'writeexcel'
gem 'icalendar'
+gem 'browser'
# event machine
gem 'eventmachine'
diff --git a/app/assets/javascripts/app/controllers/_profile/devices.js.coffee b/app/assets/javascripts/app/controllers/_profile/devices.js.coffee
new file mode 100644
index 000000000..b5fcba939
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_profile/devices.js.coffee
@@ -0,0 +1,51 @@
+class Index extends App.Controller
+ events:
+ 'click [data-type=delete]': 'delete'
+
+ constructor: ->
+ super
+ return if !@authenticate()
+ @title 'Devices', true
+
+ @load()
+ @interval(
+ =>
+ @load()
+ 62000
+ )
+
+ # fetch data, render view
+ load: =>
+ @ajax(
+ id: 'user_devices'
+ type: 'GET'
+ url: @apiPath + '/user_devices'
+ success: (data) =>
+ @render(data)
+ )
+
+ render: (data) =>
+ @html App.view('profile/devices')( devices: data )
+
+ delete: (e) =>
+ e.preventDefault()
+ id = $(e.target).closest('a').data('device-id')
+ console.log('ID', id)
+ # get data
+ @ajax(
+ id: 'user_devices_delete'
+ type: 'DELETE'
+ url: "#{@apiPath}/user_devices/#{id}"
+ processData: true
+ success: @load
+ error: @error
+ )
+
+ error: (xhr, status, error) =>
+ data = JSON.parse( xhr.responseText )
+ @notify(
+ type: 'error'
+ msg: App.i18n.translateContent( data.message )
+ )
+
+App.Config.set( 'Devices', { prio: 3100, name: 'Devices', parent: '#profile', target: '#profile/devices', controller: Index }, 'NavBarProfile' )
diff --git a/app/assets/javascripts/app/views/profile/devices.jst.eco b/app/assets/javascripts/app/views/profile/devices.jst.eco
new file mode 100644
index 000000000..c99ad563d
--- /dev/null
+++ b/app/assets/javascripts/app/views/profile/devices.jst.eco
@@ -0,0 +1,30 @@
+
+
+
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 691b1197f..26b5029df 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base
:model_index_render
skip_before_action :verify_authenticity_token
- before_action :set_user, :session_update
+ before_action :set_user, :session_update, :check_user_device
before_action :cors_preflight_check
after_action :set_access_control_headers
@@ -95,6 +95,26 @@ class ApplicationController < ActionController::Base
session[:user_agent] = request.env['HTTP_USER_AGENT']
end
+ # check user device
+ def check_user_device
+
+ # only if user_id exists
+ return if !session[:user_id]
+
+ # only if write action
+ return if request.method == 'GET' || request.method == 'OPTIONS'
+
+ # only update if needed
+ return if session[:check_user_device_at] && session[:check_user_device_at] < Time.zone.now - 10.minutes
+ session[:check_user_device_at] = Time.zone.now
+
+ UserDevice.add(
+ session[:user_agent],
+ session[:remote_id],
+ session[:user_id],
+ )
+ end
+
def authentication_check_only(auth_param)
logger.debug 'authentication_check'
diff --git a/app/models/user_device.rb b/app/models/user_device.rb
new file mode 100644
index 000000000..6c3ea4d97
--- /dev/null
+++ b/app/models/user_device.rb
@@ -0,0 +1,69 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class UserDevice < ApplicationModel
+ store :device_details
+ store :location_details
+ validates :name, presence: true
+
+=begin
+
+store device for user
+
+ UserDevice.add(
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36',
+ '172.0.0.1',
+ user.id,
+ )
+
+=end
+
+ def self.add(user_agent, ip, user_id)
+
+ # get browser details
+ browser = Browser.new(:ua => user_agent, :accept_language => 'en-us')
+ browser = {
+ plattform: browser.platform.to_s.camelize,
+ name: browser.name,
+ version: browser.version,
+ full_version: browser.full_version,
+ }
+
+ # generate device name
+ name = browser[:plattform] || ''
+ if browser[:name]
+ if name
+ name += ', '
+ end
+ name += browser[:name]
+ end
+
+ # get location info
+ location = Service::GeoIp.location(ip)
+ country = location['country_name']
+
+ # check if exists
+ exists = self.find_by(
+ :user_id => user_id,
+ os: browser[:plattform],
+ browser: browser[:name],
+ country: country,
+ )
+
+ if exists
+ exists.touch
+ return exists
+ end
+
+ # create new device
+ self.create(
+ user_id: user_id,
+ name: name,
+ os: browser[:plattform],
+ browser: browser[:name],
+ country: country,
+ device_details: browser,
+ location_details: location,
+ )
+ end
+
+end
diff --git a/config/routes/user_devices.rb b/config/routes/user_devices.rb
new file mode 100644
index 000000000..ba44cdbba
--- /dev/null
+++ b/config/routes/user_devices.rb
@@ -0,0 +1,8 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ # jobs
+ match api_path + '/user_devices', to: 'user_devices#index', via: :get
+ match api_path + '/user_devices/:id', to: 'user_devices#destroy', via: :delete
+
+end
diff --git a/db/migrate/20150817000001_create_user_devices.rb b/db/migrate/20150817000001_create_user_devices.rb
new file mode 100644
index 000000000..013fb1d08
--- /dev/null
+++ b/db/migrate/20150817000001_create_user_devices.rb
@@ -0,0 +1,22 @@
+
+class CreateUserDevices < ActiveRecord::Migration
+ def up
+ create_table :user_devices do |t|
+ t.references :user, null: false
+ t.string :name, limit: 250, null: false
+ t.string :os, limit: 150, null: true
+ t.string :browser, limit: 250, null: true
+ t.string :country, limit: 150, null: true
+ t.string :device_details, limit: 2500, null: true
+ t.string :location_details, limit: 2500, null: true
+ t.timestamps
+ end
+ add_index :user_devices, [:user_id]
+ add_index :user_devices, [:os, :browser, :country]
+ add_index :user_devices, [:updated_at]
+ end
+
+ def down
+ drop_table :user_devices
+ end
+end
diff --git a/lib/models.rb b/lib/models.rb
index 4158450e3..c798a9aaa 100644
--- a/lib/models.rb
+++ b/lib/models.rb
@@ -35,6 +35,9 @@ returns
model_class = load_adapter(entry)
next if !model_class
next if !model_class.respond_to? :new
+ next if !model_class.respond_to? :table_name
+ table_name = model_class.table_name # handle models where not table exists, pending migrations
+ next if !ActiveRecord::Base.connection.tables.include?(table_name)
model_object = model_class.new
next if !model_object.respond_to? :attributes
all[model_class] = {}