Merge branch 'develop' into private-pull-request-1238
This commit is contained in:
commit
3a2a34e064
369 changed files with 13601 additions and 1831 deletions
|
@ -127,6 +127,17 @@ test:integration:email_deliver:
|
|||
- ruby -I test/ test/integration/email_deliver_test.rb
|
||||
- rake db:drop
|
||||
|
||||
test:integration:email_keep_on_server:
|
||||
stage: test
|
||||
tags:
|
||||
- core
|
||||
script:
|
||||
- export RAILS_ENV=test
|
||||
- rake db:create
|
||||
- rake db:migrate
|
||||
- ruby -I test/ test/integration/email_keep_on_server_test.rb
|
||||
- rake db:drop
|
||||
|
||||
test:integration:twitter:
|
||||
stage: test
|
||||
tags:
|
||||
|
@ -332,7 +343,7 @@ test:integration:otrs_5_mysql:
|
|||
- mysql
|
||||
script:
|
||||
- export RAILS_ENV=test
|
||||
- export IMPORT_OTRS_ENDPOINT="http://vz599.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||
- rake db:create
|
||||
- rake db:migrate
|
||||
- ruby -I test/ test/integration/otrs_import_test.rb
|
||||
|
@ -345,7 +356,7 @@ test:integration:otrs_5_postgresql:
|
|||
- postgresql
|
||||
script:
|
||||
- export RAILS_ENV=test
|
||||
- export IMPORT_OTRS_ENDPOINT="http://vz599.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||
- export IMPORT_OTRS_ENDPOINT="http://vz1109.demo.znuny.com/otrs/public.pl?Action=ZammadMigrator"
|
||||
- rake db:create
|
||||
- rake db:migrate
|
||||
- ruby -I test/ test/integration/otrs_import_test.rb
|
||||
|
|
|
@ -5,19 +5,23 @@ notifications: false
|
|||
targets:
|
||||
centos-7:
|
||||
dependencies:
|
||||
- elasticsearch
|
||||
- nginx
|
||||
- postgresql-server
|
||||
- which
|
||||
debian-8:
|
||||
dependencies:
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
ubuntu-16.04:
|
||||
dependencies:
|
||||
- elasticsearch
|
||||
- nginx|apache2
|
||||
- postgresql|mysql-server|mariadb-server|sqlite
|
||||
sles-12:
|
||||
dependencies:
|
||||
- elasticsearch
|
||||
- nginx
|
||||
- postgresql-server
|
||||
before:
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.3.1
|
||||
2.4.1
|
||||
|
|
|
@ -7,7 +7,6 @@ notifications:
|
|||
env:
|
||||
- DB=mysql
|
||||
- DB=postgresql
|
||||
- BUNDLE_JOBS=8
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
apt:
|
||||
|
@ -20,7 +19,7 @@ services:
|
|||
- mysql
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.3.1
|
||||
- 2.4.1
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y curl git-core patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev sqlite3 libsqlite3-dev autotools-dev libxslt1-dev libyaml-0-2 autoconf automake libreadline6-dev libyaml-dev libtool libgmp-dev libgdbm-dev libncurses5-dev pkg-config libffi-dev libmysqlclient-dev postfix
|
||||
|
@ -56,3 +55,4 @@ script:
|
|||
- ruby -I test/ test/integration/user_device_controller_test.rb
|
||||
- ruby -I test/ test/integration/sipgate_controller_test.rb
|
||||
- rake db:drop
|
||||
after_success: contrib/travis-ci.org/trigger-docker-compose-build.sh
|
||||
|
|
9
Gemfile
9
Gemfile
|
@ -1,8 +1,8 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.3.1'
|
||||
ruby '2.4.1'
|
||||
|
||||
gem 'rails', '4.2.8'
|
||||
gem 'rails', '4.2.9'
|
||||
gem 'rails-observers'
|
||||
gem 'activerecord-session_store'
|
||||
|
||||
|
@ -40,6 +40,7 @@ gem 'omniauth-gitlab'
|
|||
gem 'omniauth-google-oauth2'
|
||||
gem 'omniauth-linkedin-oauth2'
|
||||
gem 'omniauth-twitter'
|
||||
gem 'omniauth-microsoft-office365'
|
||||
|
||||
gem 'twitter'
|
||||
gem 'telegramAPI'
|
||||
|
@ -73,12 +74,16 @@ gem 'argon2'
|
|||
|
||||
gem 'writeexcel'
|
||||
gem 'icalendar'
|
||||
gem 'icalendar-recurrence'
|
||||
gem 'browser'
|
||||
|
||||
# integrations
|
||||
gem 'slack-notifier'
|
||||
gem 'clearbit'
|
||||
gem 'zendesk_api'
|
||||
gem 'viewpoint'
|
||||
gem 'rubyntlm', git: 'https://github.com/wimm/rubyntlm.git'
|
||||
gem 'autodiscover', git: 'https://github.com/thorsteneckel/autodiscover.git'
|
||||
|
||||
# event machine
|
||||
gem 'eventmachine'
|
||||
|
|
224
Gemfile.lock
224
Gemfile.lock
|
@ -1,44 +1,60 @@
|
|||
GIT
|
||||
remote: https://github.com/thorsteneckel/autodiscover.git
|
||||
revision: 29d713ee0c8c25fcf74c4292ff13fe1fa4d0d827
|
||||
specs:
|
||||
autodiscover (1.0.2)
|
||||
httpclient
|
||||
logging
|
||||
nokogiri
|
||||
nori
|
||||
|
||||
GIT
|
||||
remote: https://github.com/wimm/rubyntlm.git
|
||||
revision: 53969639b87b9e5d5fef560f19cf0d977259591c
|
||||
specs:
|
||||
rubyntlm (0.1.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
actionmailer (4.2.9)
|
||||
actionpack (= 4.2.9)
|
||||
actionview (= 4.2.9)
|
||||
activejob (= 4.2.9)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
actionpack (4.2.9)
|
||||
actionview (= 4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
actionview (4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
activejob (4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
activemodel (4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
activerecord (4.2.9)
|
||||
activemodel (= 4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
arel (~> 6.0)
|
||||
activerecord-nulldb-adapter (0.3.6)
|
||||
activerecord-nulldb-adapter (0.3.7)
|
||||
activerecord (>= 2.0.0)
|
||||
activerecord-session_store (1.0.0)
|
||||
actionpack (>= 4.0, < 5.1)
|
||||
activerecord (>= 4.0, < 5.1)
|
||||
activerecord-session_store (1.1.0)
|
||||
actionpack (>= 4.0, < 5.2)
|
||||
activerecord (>= 4.0, < 5.2)
|
||||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 1.5.2, < 3)
|
||||
railties (>= 4.0, < 5.1)
|
||||
activesupport (4.2.8)
|
||||
railties (>= 4.0, < 5.2)
|
||||
activesupport (4.2.9)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
|
@ -49,9 +65,9 @@ GEM
|
|||
ffi (~> 1.9)
|
||||
ffi-compiler (~> 0.1)
|
||||
ast (2.3.0)
|
||||
autoprefixer-rails (6.4.1.1)
|
||||
autoprefixer-rails (7.1.2.4)
|
||||
execjs
|
||||
biz (1.6.0)
|
||||
biz (1.7.0)
|
||||
clavius (~> 1.0)
|
||||
tzinfo
|
||||
browser (2.2.0)
|
||||
|
@ -60,7 +76,7 @@ GEM
|
|||
childprocess (0.5.9)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
clavius (1.0.2)
|
||||
clearbit (0.2.5)
|
||||
clearbit (0.2.7)
|
||||
nestful (~> 1.1.0)
|
||||
coderay (1.1.1)
|
||||
coffee-rails (4.2.1)
|
||||
|
@ -69,27 +85,27 @@ GEM
|
|||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
coffee-script-source (1.12.2)
|
||||
coffeelint (1.14.0)
|
||||
coffee-script
|
||||
execjs
|
||||
json
|
||||
composite_primary_keys (8.1.5)
|
||||
composite_primary_keys (8.1.6)
|
||||
activerecord (~> 4.2.0)
|
||||
concurrent-ruby (1.0.5)
|
||||
coveralls (0.8.16)
|
||||
coveralls (0.8.21)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov (~> 0.12.0)
|
||||
term-ansicolor (~> 1.3.0)
|
||||
thor (~> 0.19.1)
|
||||
tins (>= 1.6.0, < 2)
|
||||
simplecov (~> 0.14.1)
|
||||
term-ansicolor (~> 1.3)
|
||||
thor (~> 0.19.4)
|
||||
tins (~> 1.6)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
daemons (1.2.4)
|
||||
delayed_job (4.1.2)
|
||||
activesupport (>= 3.0, < 5.1)
|
||||
delayed_job_active_record (4.1.1)
|
||||
activerecord (>= 3.0, < 5.1)
|
||||
delayed_job (4.1.3)
|
||||
activesupport (>= 3.0, < 5.2)
|
||||
delayed_job_active_record (4.1.2)
|
||||
activerecord (>= 3.0, < 5.2)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
diff-lcs (1.2.5)
|
||||
diffy (3.1.0)
|
||||
|
@ -151,10 +167,11 @@ GEM
|
|||
guard (~> 2.8)
|
||||
guard-compat (~> 1.0)
|
||||
multi_json (~> 1.8)
|
||||
guard-symlink (0.1.0)
|
||||
guard-symlink (0.1.1)
|
||||
guard
|
||||
guard-compat (~> 1.1)
|
||||
hashdiff (0.3.2)
|
||||
hashie (3.4.4)
|
||||
hashdiff (0.3.5)
|
||||
hashie (3.5.6)
|
||||
htmlentities (4.3.4)
|
||||
http (1.0.4)
|
||||
addressable (~> 2.3)
|
||||
|
@ -165,21 +182,30 @@ GEM
|
|||
domain_name (~> 0.5)
|
||||
http-form_data (1.0.3)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.8.4)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.8.6)
|
||||
icalendar (2.4.1)
|
||||
icalendar-recurrence (1.1.2)
|
||||
icalendar (~> 2.0)
|
||||
ice_cube (~> 0.13)
|
||||
ice_cube (0.16.2)
|
||||
inflection (1.0.0)
|
||||
json (1.8.6)
|
||||
jwt (1.5.4)
|
||||
jwt (1.5.6)
|
||||
kgio (2.11.0)
|
||||
koala (2.4.0)
|
||||
addressable
|
||||
faraday
|
||||
multi_json (>= 1.3.0)
|
||||
libv8 (3.16.14.15)
|
||||
libv8 (3.16.14.19)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
little-plugger (1.1.4)
|
||||
logging (2.2.2)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.0.10)
|
||||
|
@ -192,9 +218,9 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_portile2 (2.2.0)
|
||||
minitest (5.10.2)
|
||||
minitest (5.10.3)
|
||||
multi_json (1.12.1)
|
||||
multi_xml (0.5.5)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
mysql2 (0.4.6)
|
||||
naught (1.1.0)
|
||||
|
@ -204,37 +230,41 @@ GEM
|
|||
netrc (0.11.0)
|
||||
nokogiri (1.8.0)
|
||||
mini_portile2 (~> 2.2.0)
|
||||
nori (2.6.0)
|
||||
notiffany (0.1.1)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
oauth (0.5.1)
|
||||
oauth2 (1.2.0)
|
||||
faraday (>= 0.8, < 0.10)
|
||||
oauth2 (1.4.0)
|
||||
faraday (>= 0.8, < 0.13)
|
||||
jwt (~> 1.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
octokit (4.4.1)
|
||||
sawyer (~> 0.7.0, >= 0.5.3)
|
||||
omniauth (1.3.1)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (>= 1.0, < 3)
|
||||
omniauth (1.6.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (4.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github (1.1.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-github (1.3.0)
|
||||
omniauth (~> 1.5)
|
||||
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||
omniauth-gitlab (1.0.2)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.0)
|
||||
omniauth-google-oauth2 (0.4.1)
|
||||
jwt (~> 1.5.2)
|
||||
omniauth-google-oauth2 (0.5.0)
|
||||
jwt (~> 1.5)
|
||||
multi_json (~> 1.3)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.3.1)
|
||||
omniauth-linkedin-oauth2 (0.1.5)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2
|
||||
omniauth-microsoft-office365 (0.0.7)
|
||||
omniauth
|
||||
omniauth-oauth2
|
||||
omniauth-oauth (1.1.0)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
|
@ -246,32 +276,32 @@ GEM
|
|||
omniauth-oauth (~> 1.1)
|
||||
parser (2.3.1.2)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pluginator (1.3.0)
|
||||
pg (0.20.0)
|
||||
pluginator (1.5.0)
|
||||
power_assert (0.3.1)
|
||||
powerpack (0.1.1)
|
||||
pre-commit (0.28.0)
|
||||
pluginator (~> 1.1)
|
||||
pre-commit (0.35.0)
|
||||
pluginator (~> 1.5)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
puma (3.6.0)
|
||||
puma (3.9.1)
|
||||
rack (1.6.8)
|
||||
rack-livereload (0.3.16)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.8)
|
||||
actionmailer (= 4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
actionview (= 4.2.8)
|
||||
activejob (= 4.2.8)
|
||||
activemodel (= 4.2.8)
|
||||
activerecord (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rails (4.2.9)
|
||||
actionmailer (= 4.2.9)
|
||||
actionpack (= 4.2.9)
|
||||
actionview (= 4.2.9)
|
||||
activejob (= 4.2.9)
|
||||
activemodel (= 4.2.9)
|
||||
activerecord (= 4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.8)
|
||||
railties (= 4.2.9)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
|
@ -281,15 +311,16 @@ GEM
|
|||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
railties (4.2.8)
|
||||
actionpack (= 4.2.8)
|
||||
activesupport (= 4.2.8)
|
||||
rails-observers (0.1.5)
|
||||
activemodel (>= 4.0)
|
||||
railties (4.2.9)
|
||||
actionpack (= 4.2.9)
|
||||
activesupport (= 4.2.9)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.1.0)
|
||||
raindrops (0.17.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
raindrops (0.19.0)
|
||||
rake (12.0.0)
|
||||
rb-fsevent (0.9.7)
|
||||
rb-inotify (0.9.7)
|
||||
|
@ -337,7 +368,6 @@ GEM
|
|||
sawyer (0.7.0)
|
||||
addressable (>= 2.3.5, < 2.5)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
scrub_rb (1.0.1)
|
||||
selenium-webdriver (2.53.4)
|
||||
childprocess (~> 0.5)
|
||||
rubyzip (~> 1.0)
|
||||
|
@ -345,16 +375,17 @@ GEM
|
|||
shellany (0.0.1)
|
||||
simple-rss (1.3.1)
|
||||
simple_oauth (0.3.1)
|
||||
simplecov (0.12.0)
|
||||
simplecov (0.14.1)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
simplecov-html (0.10.1)
|
||||
simplecov-rcov (0.2.3)
|
||||
simplecov (>= 0.4.1)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
spring (1.7.2)
|
||||
spring (2.0.2)
|
||||
activesupport (>= 4.2)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
sprockets (3.7.1)
|
||||
|
@ -367,17 +398,17 @@ GEM
|
|||
sqlite3 (1.3.11)
|
||||
telegramAPI (1.2.2)
|
||||
rest-client (~> 2.0, >= 1.7.3)
|
||||
term-ansicolor (1.3.2)
|
||||
term-ansicolor (1.6.0)
|
||||
tins (~> 1.0)
|
||||
test-unit (3.2.1)
|
||||
power_assert
|
||||
therubyracer (0.12.2)
|
||||
libv8 (~> 3.16.14.0)
|
||||
therubyracer (0.12.3)
|
||||
libv8 (~> 3.16.14.15)
|
||||
ref
|
||||
thor (0.19.4)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.5)
|
||||
tins (1.13.0)
|
||||
tins (1.15.0)
|
||||
twitter (5.17.0)
|
||||
addressable (~> 2.3)
|
||||
buftok (~> 0.2.0)
|
||||
|
@ -397,25 +428,29 @@ GEM
|
|||
unf_ext
|
||||
unf_ext (0.0.7.4)
|
||||
unicode-display_width (1.1.1)
|
||||
unicorn (5.2.0)
|
||||
unicorn (5.3.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
valid_email2 (1.2.17)
|
||||
valid_email2 (2.0.0)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
webmock (2.3.2)
|
||||
viewpoint (1.1.0)
|
||||
httpclient
|
||||
logging
|
||||
nokogiri
|
||||
rubyntlm
|
||||
webmock (3.0.1)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
websocket (1.2.3)
|
||||
writeexcel (1.0.5)
|
||||
zendesk_api (1.14.0)
|
||||
zendesk_api (1.14.4)
|
||||
faraday (~> 0.9)
|
||||
hashie (>= 1.2, < 4.0, != 3.3.0)
|
||||
hashie (>= 3.5.2, < 4.0.0)
|
||||
inflection
|
||||
mime-types
|
||||
multipart-post (~> 2.0)
|
||||
scrub_rb (~> 1.0.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -424,6 +459,7 @@ DEPENDENCIES
|
|||
activerecord-nulldb-adapter
|
||||
activerecord-session_store
|
||||
argon2
|
||||
autodiscover!
|
||||
autoprefixer-rails
|
||||
biz
|
||||
browser
|
||||
|
@ -449,6 +485,7 @@ DEPENDENCIES
|
|||
guard-symlink
|
||||
htmlentities
|
||||
icalendar
|
||||
icalendar-recurrence
|
||||
json
|
||||
koala
|
||||
libv8
|
||||
|
@ -463,17 +500,19 @@ DEPENDENCIES
|
|||
omniauth-gitlab
|
||||
omniauth-google-oauth2
|
||||
omniauth-linkedin-oauth2
|
||||
omniauth-microsoft-office365
|
||||
omniauth-oauth2
|
||||
omniauth-twitter
|
||||
pg
|
||||
pre-commit
|
||||
puma
|
||||
rack-livereload
|
||||
rails (= 4.2.8)
|
||||
rails (= 4.2.9)
|
||||
rails-observers
|
||||
rb-fsevent
|
||||
rspec-rails
|
||||
rubocop
|
||||
rubyntlm!
|
||||
sass-rails
|
||||
selenium-webdriver
|
||||
simple-rss
|
||||
|
@ -491,12 +530,13 @@ DEPENDENCIES
|
|||
uglifier
|
||||
unicorn
|
||||
valid_email2
|
||||
viewpoint
|
||||
webmock
|
||||
writeexcel
|
||||
zendesk_api
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.3.1p112
|
||||
ruby 2.4.1p111
|
||||
|
||||
BUNDLED WITH
|
||||
1.13.7
|
||||
1.15.3
|
||||
|
|
|
@ -61,13 +61,13 @@ class App.Controller extends Spine.Controller
|
|||
clearDelay: (delay_id) =>
|
||||
App.Delay.clear(delay_id, @controllerId)
|
||||
|
||||
delay: (callback, timeout, delay_id, queue = true) =>
|
||||
delay: (callback, timeout, delay_id, queue = false) =>
|
||||
App.Delay.set(callback, timeout, delay_id, @controllerId, queue)
|
||||
|
||||
clearInterval: (interval_id) =>
|
||||
App.Interval.clear(interval_id, @controllerId)
|
||||
|
||||
interval: (callback, interval, interval_id, queue = true) =>
|
||||
interval: (callback, interval, interval_id, queue = false) =>
|
||||
App.Interval.set(callback, interval, interval_id, @controllerId, queue)
|
||||
|
||||
releaseController: =>
|
||||
|
@ -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) ->
|
||||
#
|
||||
|
||||
|
@ -344,7 +355,10 @@ class App.Controller extends Spine.Controller
|
|||
title: ->
|
||||
userId = $(@).data('id')
|
||||
user = App.User.find(userId)
|
||||
App.Utils.htmlEscape(user.displayName())
|
||||
headline = App.Utils.htmlEscape(user.displayName())
|
||||
if user.isOutOfOffice()
|
||||
headline += " (#{App.Utils.htmlEscape(user.outOfOfficeText())})"
|
||||
headline
|
||||
content: ->
|
||||
userId = $(@).data('id')
|
||||
user = App.User.fullLocal(userId)
|
||||
|
|
|
@ -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
|
||||
|
@ -659,8 +676,9 @@ class App.Sidebar extends App.Controller
|
|||
|
||||
render: =>
|
||||
localEl = $(App.view('generic/sidebar_tabs')(
|
||||
items: @items
|
||||
items: @items
|
||||
scrollbarWidth: App.Utils.getScrollBarWidth()
|
||||
dir: App.i18n.dir()
|
||||
))
|
||||
|
||||
# init content callback
|
||||
|
|
|
@ -21,7 +21,6 @@ class App.ChannelChat extends App.ControllerSubContent
|
|||
'.js-chat-welcome': 'chatWelcome'
|
||||
'.js-testurl-input': 'urlInput'
|
||||
'.js-backgroundColor': 'chatBackground'
|
||||
'.js-paramsBlock': 'paramsBlock'
|
||||
'.js-code': 'code'
|
||||
'.js-palette': 'palette'
|
||||
'.js-color': 'colorField'
|
||||
|
@ -361,7 +360,7 @@ class App.ChannelChat extends App.ControllerSubContent
|
|||
@$('.js-modal-params').html(paramString)
|
||||
|
||||
# highlight
|
||||
@paramsBlock.each (i, block) ->
|
||||
@code.each (i, block) ->
|
||||
hljs.highlightBlock block
|
||||
|
||||
App.Config.set('Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, permission: ['admin.chat'] }, 'NavBarAdmin')
|
||||
|
|
|
@ -108,7 +108,7 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
|||
# show errors in form
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate( form: e.target, errors: errors )
|
||||
@formValidate(form: e.target, errors: errors)
|
||||
return false
|
||||
|
||||
# disable form
|
||||
|
@ -118,8 +118,10 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
|||
object.save(
|
||||
done: =>
|
||||
@close()
|
||||
fail: =>
|
||||
@close()
|
||||
fail: (settings, details) =>
|
||||
@log 'errors', details
|
||||
@formEnable(e)
|
||||
@form.showAlert(details.error_human || details.error || 'Unable to create object!')
|
||||
)
|
||||
|
||||
class App.ChannelEmailSignature extends App.Controller
|
||||
|
@ -201,7 +203,7 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
|||
# show errors in form
|
||||
if errors
|
||||
@log 'error', errors
|
||||
@formValidate( form: e.target, errors: errors )
|
||||
@formValidate(form: e.target, errors: errors)
|
||||
return false
|
||||
|
||||
# disable form
|
||||
|
@ -211,8 +213,10 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
|||
object.save(
|
||||
done: =>
|
||||
@close()
|
||||
fail: =>
|
||||
fail: (settings, details) =>
|
||||
@log 'errors', details
|
||||
@formEnable(e)
|
||||
@form.showAlert(details.error_human || details.error || 'Unable to create object!')
|
||||
)
|
||||
|
||||
class App.ChannelEmailAccountOverview extends App.Controller
|
||||
|
@ -560,21 +564,24 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
|||
|
||||
# inbound
|
||||
configureAttributesInbound = [
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||
{ 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, autocomplete: 'off', },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||
{ 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, autocomplete: 'off' },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' },
|
||||
]
|
||||
|
||||
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
if params.adapter is 'imap'
|
||||
ui.show('options::folder')
|
||||
ui.show('options::keep_on_server')
|
||||
return
|
||||
ui.hide('options::folder')
|
||||
ui.hide('options::keep_on_server')
|
||||
|
||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
|
@ -606,9 +613,10 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
|||
# fill user / password based on intro info
|
||||
channel_used = { options: {} }
|
||||
if @account['meta']
|
||||
channel_used['options']['user'] = @account['meta']['email']
|
||||
channel_used['options']['password'] = @account['meta']['password']
|
||||
channel_used['options']['folder'] = @account['meta']['folder']
|
||||
channel_used['options']['user'] = @account['meta']['email']
|
||||
channel_used['options']['password'] = @account['meta']['password']
|
||||
channel_used['options']['folder'] = @account['meta']['folder']
|
||||
channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server']
|
||||
|
||||
# show used backend
|
||||
@$('.base-outbound-settings').html('')
|
||||
|
@ -670,7 +678,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
|||
for key, value of data.setting
|
||||
@account[key] = value
|
||||
|
||||
if data.content_messages && data.content_messages > 0
|
||||
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
||||
|
@ -724,7 +732,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
|
|||
# remember account settings
|
||||
@account.inbound = params
|
||||
|
||||
if data.content_messages && data.content_messages > 0
|
||||
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
||||
|
|
|
@ -9,7 +9,7 @@ class App.ChannelForm extends App.ControllerSubContent
|
|||
'change .js-paramsSetting select': 'updateGroup'
|
||||
|
||||
elements:
|
||||
'.js-paramsBlock': 'paramsBlock'
|
||||
'.js-code': 'code'
|
||||
'.js-paramsSetting': 'paramsSetting'
|
||||
'.js-formSetting input': 'formSetting'
|
||||
|
||||
|
@ -43,7 +43,7 @@ class App.ChannelForm extends App.ControllerSubContent
|
|||
|
||||
@html element
|
||||
|
||||
@paramsBlock.each (i, block) ->
|
||||
@code.each (i, block) ->
|
||||
hljs.highlightBlock block
|
||||
|
||||
@updateParamsDesigner()
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
class Index extends App.ControllerIntegrationBase
|
||||
featureIntegration: 'check_mk_integration'
|
||||
featureName: 'Check_MK'
|
||||
featureConfig: 'check_mk_config'
|
||||
description: [
|
||||
['This service receives http requests from %s and creates tickets with host and service.', 'Check_MK']
|
||||
['If the host and service is recovered again, the ticket will be closed automatically.']
|
||||
]
|
||||
|
||||
render: =>
|
||||
super
|
||||
new App.SettingsForm(
|
||||
area: 'Integration::CheckMK'
|
||||
el: @$('.js-form')
|
||||
)
|
||||
|
||||
new App.ScriptSnipped(
|
||||
el: @$('.js-scriptSnipped')
|
||||
facility: 'check_mk'
|
||||
style: 'bash'
|
||||
content: "#!/bin/bash\n\ncurl -X POST -F 'event_id=123' -F 'host=host1' -F 'service=http' -F 'state=down' #{App.Config.get('http_type')}://#{App.Config.get('fqdn')}/api/v1/integration/check_mk/#{App.Setting.get('check_mk_token')}"
|
||||
description: [
|
||||
['To enable %s for sending http requests to %s, you need create "%s" in the admin interface if %s.', 'Check_MK', 'Zammad', 'Event Actions', 'Check_MK']
|
||||
]
|
||||
)
|
||||
|
||||
new App.HttpLog(
|
||||
el: @$('.js-log')
|
||||
facility: 'check_mk'
|
||||
)
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('check_mk_integration')
|
||||
|
||||
App.Config.set(
|
||||
'IntegrationCheckMk'
|
||||
{
|
||||
name: 'Check_MK'
|
||||
target: '#system/integration/check_mk'
|
||||
description: 'An open source monitoring tool.'
|
||||
controller: Index
|
||||
state: State
|
||||
}
|
||||
'NavBarIntegrations'
|
||||
)
|
|
@ -0,0 +1,528 @@
|
|||
class Index extends App.ControllerIntegrationBase
|
||||
featureIntegration: 'exchange_integration'
|
||||
featureName: 'Exchange'
|
||||
featureConfig: 'exchange_config'
|
||||
description: [
|
||||
['This service enables Zammad to connect with your Exchange server.']
|
||||
]
|
||||
events:
|
||||
'change .js-switch input': 'switch'
|
||||
|
||||
render: =>
|
||||
super
|
||||
new Form(
|
||||
el: @$('.js-form')
|
||||
)
|
||||
|
||||
#new App.ImportJob(
|
||||
# el: @$('.js-importJob')
|
||||
# facility: 'exchange'
|
||||
#)
|
||||
|
||||
new App.HttpLog(
|
||||
el: @$('.js-log')
|
||||
facility: 'exchange'
|
||||
)
|
||||
|
||||
switch: =>
|
||||
super
|
||||
active = @$('.js-switch input').prop('checked')
|
||||
if active
|
||||
job_start = =>
|
||||
@ajax(
|
||||
id: 'jobs_config'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/job_start"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(true)
|
||||
)
|
||||
|
||||
App.Delay.set(
|
||||
job_start,
|
||||
600,
|
||||
'job_start',
|
||||
)
|
||||
|
||||
class Form extends App.Controller
|
||||
elements:
|
||||
'.js-lastImport': 'lastImport'
|
||||
'.js-wizard': 'wizardButton'
|
||||
events:
|
||||
'click .js-wizard': 'startWizard'
|
||||
'click .js-start-sync': 'startSync'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
@lastResult()
|
||||
@activeDryRun()
|
||||
|
||||
currentConfig: ->
|
||||
App.Setting.get('exchange_config') || {}
|
||||
|
||||
setConfig: (value) =>
|
||||
App.Setting.set('exchange_config', value, {notify: true})
|
||||
@startSync()
|
||||
|
||||
render: (top = false) =>
|
||||
@config = @currentConfig()
|
||||
|
||||
folders = []
|
||||
if !_.isEmpty(@config.folders)
|
||||
for folder_id in @config.folders
|
||||
folders.push @config.wizardData.backend_folders[folder_id]
|
||||
|
||||
@html App.view('integration/exchange')(
|
||||
config: @config,
|
||||
folders: folders
|
||||
)
|
||||
if _.isEmpty(@config)
|
||||
@$('.js-notConfigured').removeClass('hide')
|
||||
@$('.js-summary').addClass('hide')
|
||||
else
|
||||
@$('.js-notConfigured').addClass('hide')
|
||||
@$('.js-summary').removeClass('hide')
|
||||
|
||||
if top
|
||||
a = =>
|
||||
@scrollToIfNeeded($('.content.active .page-header'))
|
||||
@delay(a, 500)
|
||||
|
||||
startSync: =>
|
||||
@ajax(
|
||||
id: 'jobs_config'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/job_start"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(true)
|
||||
@lastResult()
|
||||
)
|
||||
|
||||
startWizard: (e) =>
|
||||
e.preventDefault()
|
||||
new ConnectionWizard(
|
||||
container: @el.closest('.content')
|
||||
config: @config
|
||||
callback: (config) =>
|
||||
@setConfig(config)
|
||||
)
|
||||
|
||||
lastResult: =>
|
||||
@ajax(
|
||||
id: 'jobs_start_index'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/integration/exchange/job_start"
|
||||
processData: true
|
||||
success: (job, status, xhr) =>
|
||||
if !_.isEmpty(job)
|
||||
if !@lastResultShowJob || @lastResultShowJob.updated_at != job.updated_at
|
||||
@lastResultShowJob = job
|
||||
@lastResultShow(job)
|
||||
if job.finished_at
|
||||
@wizardButton.attr('disabled', false)
|
||||
else
|
||||
@wizardButton.attr('disabled', true)
|
||||
@delay(@lastResult, 5000)
|
||||
)
|
||||
|
||||
lastResultShow: (job) =>
|
||||
if _.isEmpty(job)
|
||||
@lastImport.html('')
|
||||
return
|
||||
countDone = job.result.created + job.result.updated + job.result.unchanged + job.result.skipped + job.result.failed
|
||||
if !job.result.roles
|
||||
job.result.roles = {}
|
||||
for role_id, statistic of job.result.role_ids
|
||||
role = App.Role.find(role_id)
|
||||
job.result.roles[role.displayName()] = statistic
|
||||
el = $(App.view('integration/exchange_last_import')(job: job, countDone: countDone))
|
||||
@lastImport.html(el)
|
||||
|
||||
activeDryRun: =>
|
||||
@ajax(
|
||||
id: 'jobs_try_index'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/integration/exchange/job_try"
|
||||
data:
|
||||
finished: false
|
||||
processData: true
|
||||
success: (job, status, xhr) =>
|
||||
return if _.isEmpty(job)
|
||||
|
||||
# show analyzing
|
||||
new ConnectionWizard(
|
||||
container: @el.closest('.content')
|
||||
config: job.payload
|
||||
start: 'tryLoop'
|
||||
callback: (config) =>
|
||||
@wizardButton.attr('disabled', false)
|
||||
@setConfig(config)
|
||||
)
|
||||
@wizardButton.attr('disabled', true)
|
||||
)
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('exchange_integration')
|
||||
|
||||
class ConnectionWizard extends App.WizardModal
|
||||
wizardConfig: {}
|
||||
slideMethod:
|
||||
'js-folders': 'foldersShow'
|
||||
'js-mapping': 'mappingShow'
|
||||
|
||||
events:
|
||||
'submit form.js-discover': 'discover'
|
||||
'submit form.js-bind': 'folders'
|
||||
'submit form.js-folders': 'mapping'
|
||||
'click .js-mapping .js-submitTry': 'mappingChange'
|
||||
'click .js-try .js-submitSave': 'save'
|
||||
'click .js-close': 'hide'
|
||||
'click .js-remove': 'removeRow'
|
||||
'click .js-userMappingForm .js-add': 'addUserMapping'
|
||||
'click .js-goToSlide': 'goToSlide'
|
||||
|
||||
elements:
|
||||
'.modal-body': 'body'
|
||||
'.js-foldersSelect': 'foldersSelect'
|
||||
'.js-folders .js-submitTry': 'foldersSelectSubmit'
|
||||
'.js-userMappingForm': 'userMappingForm'
|
||||
'.js-expertForm': 'expertForm'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
if !_.isEmpty(@config)
|
||||
@wizardConfig = @config
|
||||
|
||||
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': @onShown
|
||||
'hidden.bs.modal': =>
|
||||
@el.remove()
|
||||
|
||||
if @slide
|
||||
@showSlide(@slide)
|
||||
else
|
||||
@showDiscoverDetails()
|
||||
|
||||
if @start
|
||||
@[@start]()
|
||||
|
||||
render: =>
|
||||
@html App.view('integration/exchange_wizard')()
|
||||
|
||||
save: (e) =>
|
||||
e.preventDefault()
|
||||
@callback(@wizardConfig)
|
||||
@hide(e)
|
||||
|
||||
showSlide: (slide) =>
|
||||
method = @slideMethod[slide]
|
||||
if method && @[method]
|
||||
@[method](true)
|
||||
super
|
||||
|
||||
showDiscoverDetails: =>
|
||||
@$('.js-discover input[name="user"]').val(@wizardConfig.user)
|
||||
@$('.js-discover input[name="password"]').val(@wizardConfig.password)
|
||||
|
||||
showBindDetails: =>
|
||||
@$('.js-bind input[name="endpoint"]').val(@wizardConfig.endpoint)
|
||||
@$('.js-bind input[name="user"]').val(@wizardConfig.user)
|
||||
@$('.js-bind input[name="password"]').val(@wizardConfig.password)
|
||||
|
||||
discover: (e) =>
|
||||
e.preventDefault()
|
||||
@showSlide('js-connect')
|
||||
params = @formParam(e.target)
|
||||
@ajax(
|
||||
id: 'exchange_discover'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/autodiscover"
|
||||
data: JSON.stringify(params)
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
if data.result isnt 'ok'
|
||||
@showSlide('js-discover')
|
||||
@showAlert('js-discover', data.message)
|
||||
return
|
||||
|
||||
@wizardConfig.endpoint = data.endpoint
|
||||
@wizardConfig.user = params.user
|
||||
@wizardConfig.password = params.password
|
||||
|
||||
@showSlide('js-bind')
|
||||
@showBindDetails()
|
||||
|
||||
error: (xhr, statusText, error) =>
|
||||
detailsRaw = xhr.responseText
|
||||
details = {}
|
||||
if !_.isEmpty(detailsRaw)
|
||||
details = JSON.parse(detailsRaw)
|
||||
@showSlide('js-discover')
|
||||
@showAlert('js-discover', details.error || 'Unable to perform backend.')
|
||||
)
|
||||
|
||||
folders: (e) =>
|
||||
e.preventDefault()
|
||||
@showSlide('js-analyze')
|
||||
params = @formParam(e.target)
|
||||
@ajax(
|
||||
id: 'exchange_folders'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/folders"
|
||||
data: JSON.stringify(params)
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
if data.result isnt 'ok'
|
||||
@showSlide('js-bind')
|
||||
@showAlert('js-bind', data.message)
|
||||
return
|
||||
|
||||
@wizardConfig.endpoint = params.endpoint
|
||||
@wizardConfig.user = params.user
|
||||
@wizardConfig.password = params.password
|
||||
|
||||
# update wizard data
|
||||
@wizardConfig.wizardData = {}
|
||||
@wizardConfig.wizardData.backend_folders = data.folders
|
||||
|
||||
@foldersShow()
|
||||
|
||||
error: (xhr, statusText, error) =>
|
||||
detailsRaw = xhr.responseText
|
||||
details = {}
|
||||
if !_.isEmpty(detailsRaw)
|
||||
details = JSON.parse(detailsRaw)
|
||||
@showSlide('js-bind')
|
||||
@showAlert('js-bind', details.error || 'Unable to perform backend.')
|
||||
)
|
||||
|
||||
foldersShow: (alreadyShown) =>
|
||||
@showSlide('js-folders') if !alreadyShown
|
||||
@foldersSelect.html(@createColumnSelection('folders', @wizardConfig.wizardData.backend_folders, @wizardConfig.folders))
|
||||
|
||||
createColumnSelection: (name, options, selected) ->
|
||||
return App.UiElement.column_select.render(
|
||||
name: name
|
||||
null: false
|
||||
nulloption: false
|
||||
options: options
|
||||
value: selected
|
||||
onChange: (val) =>
|
||||
if val && val.length > 0
|
||||
@foldersSelectSubmit.removeClass('is-disabled')
|
||||
else
|
||||
@foldersSelectSubmit.addClass('is-disabled')
|
||||
)
|
||||
|
||||
mapping: (e) =>
|
||||
e.preventDefault()
|
||||
@showSlide('js-analyze')
|
||||
params = @formParam(e.target)
|
||||
|
||||
# folders might be a single selection so we
|
||||
# have to ensure that is an Array so the
|
||||
# backend and frontend can handle it properly
|
||||
if typeof params.folders is 'string'
|
||||
params.folders = [ params.folders ]
|
||||
|
||||
# add login params
|
||||
params.endpoint = @wizardConfig.endpoint
|
||||
params.user = @wizardConfig.user
|
||||
params.password = @wizardConfig.password
|
||||
|
||||
@ajax(
|
||||
id: 'exchange_mapping'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/mapping"
|
||||
data: JSON.stringify(params)
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
if data.result isnt 'ok'
|
||||
@showSlide('js-folders')
|
||||
@showAlert('js-folders', data.message)
|
||||
return
|
||||
|
||||
attributes = {}
|
||||
for key, value of App.User.attributesGet()
|
||||
continue if key == 'login'
|
||||
if (value.tag is 'input' || value.tag is 'richtext' || value.tag is 'textarea') && value.type isnt 'password'
|
||||
attributes[key] = value.display || key
|
||||
|
||||
@wizardConfig.wizardData.attributes = attributes
|
||||
@wizardConfig.folders = params.folders
|
||||
@wizardConfig.wizardData.backend_attributes = data.attributes
|
||||
|
||||
@mappingShow()
|
||||
|
||||
error: (xhr, statusText, error) =>
|
||||
detailsRaw = xhr.responseText
|
||||
details = {}
|
||||
if !_.isEmpty(detailsRaw)
|
||||
details = JSON.parse(detailsRaw)
|
||||
@showSlide('js-folders')
|
||||
@showAlert('js-folders', details.error || 'Unable to perform backend.')
|
||||
)
|
||||
|
||||
mappingShow: (alreadyShown) =>
|
||||
@showSlide('js-mapping') if !alreadyShown
|
||||
user_attribute_map = @wizardConfig.attributes
|
||||
|
||||
if _.isEmpty(user_attribute_map)
|
||||
user_attribute_map =
|
||||
given_name: 'firstname'
|
||||
surname: 'lastname'
|
||||
'email_addresses.emailaddress1': 'email'
|
||||
'phone_numbers.businessphone': 'phone'
|
||||
|
||||
@userMappingForm.find('tbody tr.js-entry').remove()
|
||||
@userMappingForm.find('tbody tr').before(@buildRowsUserMap(user_attribute_map))
|
||||
|
||||
mappingChange: (e) =>
|
||||
e.preventDefault()
|
||||
|
||||
# user map
|
||||
attributes = @formParam(@userMappingForm)
|
||||
for key in ['source', 'dest']
|
||||
if !_.isArray(attributes[key])
|
||||
attributes[key] = [attributes[key]]
|
||||
attributes_local =
|
||||
item_id: 'login'
|
||||
length = attributes.source.length-1
|
||||
for count in [0..length]
|
||||
if attributes.source[count] && attributes.dest[count]
|
||||
attributes_local[attributes.source[count]] = attributes.dest[count]
|
||||
@wizardConfig.attributes = attributes_local
|
||||
|
||||
@tryShow()
|
||||
|
||||
buildRowsUserMap: (user_attribute_map) =>
|
||||
|
||||
# show static login row
|
||||
userUidDisplayValue = @wizardConfig.wizardData.backend_attributes['item_id']
|
||||
el = [
|
||||
$(App.view('integration/ldap_user_attribute_row_read_only')(
|
||||
key: userUidDisplayValue,
|
||||
value: 'Login'
|
||||
))
|
||||
]
|
||||
|
||||
for source, dest of user_attribute_map
|
||||
continue if source == 'item_id'
|
||||
continue if !(source of @wizardConfig.wizardData.backend_attributes)
|
||||
el.push @buildRowUserAttribute(source, dest)
|
||||
el
|
||||
|
||||
buildRowUserAttribute: (source, dest) =>
|
||||
el = $(App.view('integration/exchange_user_attribute_row')())
|
||||
el.find('.js-exchangeAttribute').html(@createSelection('source', @wizardConfig.wizardData.backend_attributes, source))
|
||||
el.find('.js-userAttribute').html(@createSelection('dest', @wizardConfig.wizardData.attributes, dest))
|
||||
el
|
||||
|
||||
createSelection: (name, options, selected, unknown) ->
|
||||
return App.UiElement.searchable_select.render(
|
||||
name: name
|
||||
multiple: false
|
||||
limit: 100
|
||||
null: false
|
||||
nulloption: false
|
||||
options: options
|
||||
value: selected
|
||||
unknown: unknown
|
||||
class: 'form-control--small'
|
||||
)
|
||||
|
||||
removeRow: (e) ->
|
||||
e.preventDefault()
|
||||
$(e.target).closest('tr').remove()
|
||||
|
||||
addUserMapping: (e) =>
|
||||
e.preventDefault()
|
||||
@userMappingForm.find('tbody tr').last().before(@buildRowUserAttribute())
|
||||
|
||||
tryShow: (e) =>
|
||||
if e
|
||||
e.preventDefault()
|
||||
@showSlide('js-analyze')
|
||||
|
||||
# create import job
|
||||
@ajax(
|
||||
id: 'exchange_try'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/exchange/job_try"
|
||||
data: JSON.stringify(@wizardConfig)
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@tryLoop()
|
||||
)
|
||||
|
||||
tryLoop: =>
|
||||
@showSlide('js-dry')
|
||||
@ajax(
|
||||
id: 'jobs_try_index'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/integration/exchange/job_try"
|
||||
data:
|
||||
finished: true
|
||||
processData: true
|
||||
success: (job, status, xhr) =>
|
||||
if job.result && (job.result.error || job.result.info)
|
||||
@showSlide('js-error')
|
||||
@showAlert('js-error', (job.result.error || job.result.info))
|
||||
return
|
||||
|
||||
total = 0
|
||||
if job.result && _.keys(job.result).length > 0
|
||||
@$('.js-preprogress').addClass('hide')
|
||||
@$('.js-analyzing').removeClass('hide')
|
||||
|
||||
analized = 0
|
||||
total = job.result.sum
|
||||
for action, sum of job.result
|
||||
continue if action == 'folders'
|
||||
continue if action == 'sum'
|
||||
analized += sum
|
||||
|
||||
@$('.js-progress progress').attr('value', analized)
|
||||
@$('.js-progress progress').attr('max', total)
|
||||
|
||||
if job.finished_at
|
||||
# reset initial state in case the back button is used
|
||||
@$('.js-preprogress').removeClass('hide')
|
||||
@$('.js-analyzing').addClass('hide')
|
||||
|
||||
@tryResult(job, total)
|
||||
else
|
||||
@delay(@tryLoop, 4000)
|
||||
)
|
||||
|
||||
tryResult: (job, total) =>
|
||||
@showSlide('js-try')
|
||||
el = $(App.view('integration/exchange_summary')(job: job, countDone: total))
|
||||
@el.find('.js-summary').html(el)
|
||||
|
||||
App.Config.set(
|
||||
'IntegrationExchange'
|
||||
{
|
||||
name: 'Exchange'
|
||||
target: '#system/integration/exchange'
|
||||
description: 'Exchange integration for contacts management.'
|
||||
controller: Index
|
||||
state: State
|
||||
}
|
||||
'NavBarIntegrations'
|
||||
)
|
|
@ -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'
|
||||
)
|
|
@ -28,13 +28,20 @@ class Index extends App.ControllerIntegrationBase
|
|||
super
|
||||
active = @$('.js-switch input').prop('checked')
|
||||
if active
|
||||
@ajax(
|
||||
id: 'jobs_config'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/ldap/job_start"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(true)
|
||||
job_start = =>
|
||||
@ajax(
|
||||
id: 'jobs_config'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/integration/ldap/job_start"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(true)
|
||||
)
|
||||
|
||||
App.Delay.set(
|
||||
job_start,
|
||||
600,
|
||||
'job_start',
|
||||
)
|
||||
|
||||
class Form extends App.Controller
|
||||
|
@ -91,6 +98,7 @@ class Form extends App.Controller
|
|||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(true)
|
||||
@lastResult()
|
||||
)
|
||||
|
||||
startWizard: (e) =>
|
||||
|
|
|
@ -9,43 +9,7 @@ class Index extends App.ControllerSubContent
|
|||
@render()
|
||||
|
||||
render: =>
|
||||
auth_provider_all = {
|
||||
facebook: {
|
||||
url: '/auth/facebook'
|
||||
name: 'Facebook'
|
||||
config: 'auth_facebook'
|
||||
},
|
||||
twitter: {
|
||||
url: '/auth/twitter'
|
||||
name: 'Twitter'
|
||||
config: 'auth_twitter'
|
||||
},
|
||||
linkedin: {
|
||||
url: '/auth/linkedin'
|
||||
name: 'LinkedIn'
|
||||
config: 'auth_linkedin'
|
||||
},
|
||||
github: {
|
||||
url: '/auth/github'
|
||||
name: 'GitHub'
|
||||
config: 'auth_github'
|
||||
},
|
||||
gitlab: {
|
||||
url: '/auth/gitlab'
|
||||
name: 'GitLab'
|
||||
config: 'auth_gitlab'
|
||||
},
|
||||
google_oauth2: {
|
||||
url: '/auth/google_oauth2'
|
||||
name: 'Google'
|
||||
config: 'auth_google_oauth2'
|
||||
},
|
||||
oauth2: {
|
||||
url: '/auth/oauth2'
|
||||
name: 'OAuth2'
|
||||
config: 'auth_oauth2'
|
||||
},
|
||||
}
|
||||
auth_provider_all = App.Config.get('auth_provider_all')
|
||||
auth_providers = {}
|
||||
for key, provider of auth_provider_all
|
||||
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
||||
|
@ -90,3 +54,45 @@ class Index extends App.ControllerSubContent
|
|||
)
|
||||
|
||||
App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index, permission: ['user_preferences.linked_accounts'] }, 'NavBarProfile')
|
||||
App.Config.set('auth_provider_all', {
|
||||
facebook:
|
||||
url: '/auth/facebook'
|
||||
name: 'Facebook'
|
||||
config: 'auth_facebook'
|
||||
class: 'facebook'
|
||||
twitter:
|
||||
url: '/auth/twitter'
|
||||
name: 'Twitter'
|
||||
config: 'auth_twitter'
|
||||
class: 'twitter'
|
||||
linkedin:
|
||||
url: '/auth/linkedin'
|
||||
name: 'LinkedIn'
|
||||
config: 'auth_linkedin'
|
||||
class: 'linkedin'
|
||||
github:
|
||||
url: '/auth/github'
|
||||
name: 'GitHub'
|
||||
config: 'auth_github'
|
||||
class: 'github'
|
||||
gitlab:
|
||||
url: '/auth/gitlab'
|
||||
name: 'GitLab'
|
||||
config: 'auth_gitlab'
|
||||
class: 'gitlab'
|
||||
microsoft_office365:
|
||||
url: '/auth/microsoft_office365'
|
||||
name: 'Office 365'
|
||||
config: 'auth_microsoft_office365'
|
||||
class: 'office365'
|
||||
google_oauth2:
|
||||
url: '/auth/google_oauth2'
|
||||
name: 'Google'
|
||||
config: 'auth_google_oauth2'
|
||||
class: 'google'
|
||||
oauth2:
|
||||
url: '/auth/oauth2'
|
||||
name: 'OAuth2'
|
||||
config: 'auth_oauth2'
|
||||
class: 'oauth2'
|
||||
})
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
class Index extends App.ControllerSubContent
|
||||
requiredPermission: 'user_preferences.out_of_office+ticket.agent'
|
||||
header: 'Out of Office'
|
||||
events:
|
||||
'submit form': 'submit'
|
||||
'click .js-disabled': 'disable'
|
||||
'click .js-enable': 'enable'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
|
||||
render: =>
|
||||
user = @Session.get()
|
||||
if !@localData
|
||||
@localData =
|
||||
out_of_office: user.out_of_office
|
||||
out_of_office_start_at: user.out_of_office_start_at
|
||||
out_of_office_end_at: user.out_of_office_end_at
|
||||
out_of_office_replacement_id: user.out_of_office_replacement_id
|
||||
out_of_office_replacement_id_completion: user.preferences.out_of_office_replacement_id_completion
|
||||
out_of_office_text: user.preferences.out_of_office_text
|
||||
form = $(App.view('profile/out_of_office')(
|
||||
user: user
|
||||
localData: @localData
|
||||
placeholder: App.User.outOfOfficeTextPlaceholder()
|
||||
))
|
||||
|
||||
dateStart = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_start_at'
|
||||
display: ''
|
||||
tag: 'date'
|
||||
past: false
|
||||
future: true
|
||||
null: false
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-startDate').html(dateStart.form)
|
||||
|
||||
dateEnd = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_end_at'
|
||||
display: ''
|
||||
tag: 'date'
|
||||
past: false
|
||||
future: true
|
||||
null: false
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-endDate').html(dateEnd.form)
|
||||
|
||||
agentList = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_replacement_id'
|
||||
display: ''
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
autocapitalize: false
|
||||
multiple: false
|
||||
limit: 30
|
||||
minLengt: 2
|
||||
placeholder: 'Enter Person or Organization/Company'
|
||||
null: false
|
||||
translate: false
|
||||
disableCreateObject: true
|
||||
value: @localData
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-recipientDropdown').html(agentList.form)
|
||||
if @localData.out_of_office is true
|
||||
form.find('.js-disabled').removeClass('is-disabled')
|
||||
#form.find('.js-enable').addClass('is-disabled')
|
||||
else
|
||||
form.find('.js-disabled').addClass('is-disabled')
|
||||
#form.find('.js-enable').removeClass('is-disabled')
|
||||
@html(form)
|
||||
|
||||
enable: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
params.out_of_office = true
|
||||
@store(e, params)
|
||||
|
||||
disable: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
params.out_of_office = false
|
||||
@store(e, params)
|
||||
|
||||
submit: (e, params) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
@store(e, params)
|
||||
|
||||
store: (e, params) =>
|
||||
@formDisable(e)
|
||||
for key, value of params
|
||||
@localData[key] = value
|
||||
App.Ajax.request(
|
||||
id: 'user_out_of_office'
|
||||
type: 'PUT'
|
||||
url: "#{@apiPath}/users/out_of_office"
|
||||
data: JSON.stringify(params)
|
||||
processData: true
|
||||
success: @success
|
||||
error: @error
|
||||
)
|
||||
|
||||
success: (data) =>
|
||||
if data.message is 'ok'
|
||||
@render()
|
||||
@notify(
|
||||
type: 'success'
|
||||
msg: App.i18n.translateContent('Successfully!')
|
||||
timeout: 1000
|
||||
)
|
||||
else
|
||||
if data.notice
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(data.notice[0], data.notice[1])
|
||||
removeAll: true
|
||||
else
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: 'Please contact your administrator.'
|
||||
removeAll: true
|
||||
@formEnable( @$('form') )
|
||||
|
||||
error: (xhr, status, error) =>
|
||||
@formEnable( @$('form') )
|
||||
|
||||
# do not close window if request is aborted
|
||||
return if status is 'abort'
|
||||
data = JSON.parse(xhr.responseText)
|
||||
|
||||
# show error message
|
||||
if xhr.status is 401 || error is 'Unauthorized'
|
||||
message = '» ' + App.i18n.translateInline('Unauthorized') + ' «'
|
||||
else if xhr.status is 404 || error is 'Not Found'
|
||||
message = '» ' + App.i18n.translateInline('Not Found') + ' «'
|
||||
else if data.error
|
||||
message = App.i18n.translateInline(data.error)
|
||||
else
|
||||
message = '» ' + App.i18n.translateInline('Error') + ' «'
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(message)
|
||||
removeAll: true
|
||||
|
||||
App.Config.set('OutOfOffice', { prio: 2800, name: 'Out of Office', parent: '#profile', target: '#profile/out_of_office', permission: ['user_preferences.out_of_office+ticket.agent'], controller: Index }, 'NavBarProfile')
|
|
@ -2,7 +2,7 @@ class App.SettingsAreaProxy extends App.Controller
|
|||
events:
|
||||
'submit form': 'update'
|
||||
'click .js-submit': 'update'
|
||||
'click .js-test': 'test2'
|
||||
'click .js-test': 'testConnection'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -14,20 +14,21 @@ class App.SettingsAreaProxy extends App.Controller
|
|||
proxy: App.Setting.get('proxy')
|
||||
proxy_username: App.Setting.get('proxy_username')
|
||||
proxy_password: App.Setting.get('proxy_password')
|
||||
proxy_no: App.Setting.get('proxy_no')
|
||||
)
|
||||
|
||||
update: (e) =>
|
||||
e.preventDefault()
|
||||
@formDisable(e)
|
||||
params = @formParam(e)
|
||||
console.log('params', params)
|
||||
App.Setting.set('proxy', params.proxy)
|
||||
App.Setting.set('proxy_username', params.proxy_username)
|
||||
App.Setting.set('proxy_password', params.proxy_password)
|
||||
App.Setting.set('proxy_no', params.proxy_no)
|
||||
@formEnable(e)
|
||||
@render()
|
||||
|
||||
test2: (e) =>
|
||||
testConnection: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e)
|
||||
@ajax(
|
||||
|
|
|
@ -82,7 +82,7 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
|||
view:
|
||||
shown: true
|
||||
invite_customer:
|
||||
show: false
|
||||
shown: false
|
||||
required: false
|
||||
'admin.user':
|
||||
create:
|
||||
|
@ -94,10 +94,10 @@ class App.UiElement.object_manager_attribute extends App.UiElement.ApplicationUi
|
|||
view:
|
||||
shown: true
|
||||
invite_agent:
|
||||
show: false
|
||||
shown: false
|
||||
required: false
|
||||
invite_customer:
|
||||
show: false
|
||||
shown: false
|
||||
required: false
|
||||
Organization:
|
||||
'ticket.customer':
|
||||
|
|
|
@ -20,7 +20,7 @@ class App.UiElement.postmaster_set
|
|||
name: 'Customer'
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
}
|
||||
{
|
||||
value: 'group_id'
|
||||
|
@ -32,7 +32,7 @@ class App.UiElement.postmaster_set
|
|||
name: 'Owner'
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
}
|
||||
]
|
||||
article:
|
||||
|
|
|
@ -33,14 +33,15 @@ class App.UiElement.ticket_perform_action
|
|||
elements["#{groupKey}.#{config.name}"] = config
|
||||
|
||||
# add ticket deletion action
|
||||
elements['ticket.action'] =
|
||||
name: 'action'
|
||||
display: 'Action'
|
||||
tag: 'select'
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
delete: 'delete'
|
||||
if attribute.ticket_delete
|
||||
elements['ticket.action'] =
|
||||
name: 'action'
|
||||
display: 'Action'
|
||||
tag: 'select'
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
delete: 'Delete'
|
||||
|
||||
[defaults, groups, elements]
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ class App.UiElement.ticket_selector
|
|||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||
return if !groupAndAttribute
|
||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute, false)
|
||||
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||
)
|
||||
|
||||
# bind for preview
|
||||
|
@ -244,9 +244,9 @@ class App.UiElement.ticket_selector
|
|||
if groupAndAttribute
|
||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||
|
||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute, true)
|
||||
@buildOperator(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute, buildValue) ->
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
|
||||
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||
|
@ -284,9 +284,9 @@ class App.UiElement.ticket_selector
|
|||
|
||||
elementRow.find('.js-operator select').replaceWith(selection)
|
||||
|
||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute, buildValue)
|
||||
@buildPreCondition(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
|
||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig, buildValue = true) ->
|
||||
@buildPreCondition: (elementFull, elementRow, groupAndAttribute, elements, meta, attributeConfig) ->
|
||||
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||
currentPreCondition = elementRow.find('.js-preCondition option:selected').attr('value')
|
||||
|
||||
|
@ -318,7 +318,6 @@ class App.UiElement.ticket_selector
|
|||
if !preCondition
|
||||
elementRow.find('.js-preCondition select').html('')
|
||||
elementRow.find('.js-preCondition').addClass('hide')
|
||||
return if !buildValue
|
||||
toggleValue()
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
return
|
||||
|
@ -351,7 +350,6 @@ class App.UiElement.ticket_selector
|
|||
toggleValue()
|
||||
)
|
||||
|
||||
return if !buildValue
|
||||
@buildValue(elementFull, elementRow, groupAndAttribute, elements, meta, attribute)
|
||||
toggleValue()
|
||||
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
class App.UiElement.user_autocompletion_search
|
||||
@render: (attributeOrig, params = {}) ->
|
||||
attribute = _.clone(attributeOrig)
|
||||
attribute.disableCreateUser = true
|
||||
attribute.disableCreateObject = true
|
||||
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
|
||||
|
|
|
@ -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: =>
|
||||
|
@ -228,7 +231,7 @@ class App.TicketCreate extends App.Controller
|
|||
type = @$('[name="formSenderType"]').val()
|
||||
|
||||
if signature isnt undefined && signature.body && type is 'email-out'
|
||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get() })
|
||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), config: App.Config.all() })
|
||||
|
||||
body = @$('[data-name=body]')
|
||||
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
||||
|
@ -330,27 +333,47 @@ class App.TicketCreate extends App.Controller
|
|||
# show text module UI
|
||||
@textModule = new App.WidgetTextModule(
|
||||
el: @$('[data-name="body"]').parent()
|
||||
)
|
||||
|
||||
new Sidebar(
|
||||
el: @sidebar
|
||||
params: @formDefault
|
||||
textModule: @textModule
|
||||
data:
|
||||
config: App.Config.all()
|
||||
user: App.Session.get()
|
||||
)
|
||||
|
||||
$('#tags').tokenfield()
|
||||
|
||||
@sidebarWidget = new App.TicketCreateSidebar(
|
||||
el: @sidebar
|
||||
params: @formDefault
|
||||
sidebarState: @sidebarState
|
||||
task_key: @task_key
|
||||
query: @query
|
||||
)
|
||||
|
||||
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) ->
|
||||
|
@ -475,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()
|
||||
|
@ -495,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) ->
|
||||
|
@ -618,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
|
||||
|
@ -628,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(
|
||||
|
@ -643,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')
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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')
|
|
@ -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')
|
|
@ -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')
|
|
@ -98,9 +98,13 @@ class App.TicketMerge extends App.ControllerModal
|
|||
type: 'error'
|
||||
msg: App.i18n.translateContent(data['message'])
|
||||
timeout: 6000
|
||||
|
||||
@formEnable(e)
|
||||
|
||||
error: =>
|
||||
error: (data) =>
|
||||
details = data.responseJSON || {}
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to merge!')
|
||||
timeout: 6000
|
||||
@formEnable(e)
|
||||
)
|
||||
|
|
|
@ -462,6 +462,14 @@ class ChatWindow extends App.Controller
|
|||
)
|
||||
)
|
||||
|
||||
# show text module UI
|
||||
new App.WidgetTextModule(
|
||||
el: @input
|
||||
data:
|
||||
user: App.Session.get()
|
||||
config: App.Config.all()
|
||||
)
|
||||
|
||||
focus: =>
|
||||
@input.focus()
|
||||
|
||||
|
@ -473,7 +481,7 @@ class ChatWindow extends App.Controller
|
|||
if event.data and event.data.callback
|
||||
event.data.callback()
|
||||
|
||||
@$('.js-customerChatInput').ce({
|
||||
@input.ce({
|
||||
mode: 'richtext'
|
||||
multiline: true
|
||||
maxlength: 40000
|
||||
|
@ -522,7 +530,7 @@ class ChatWindow extends App.Controller
|
|||
|
||||
switch event.keyCode
|
||||
when TABKEY
|
||||
allChatInputs = $('.js-customerChatInput').not('[disabled="disabled"]')
|
||||
allChatInputs = @input.not('[disabled="disabled"]')
|
||||
chatCount = allChatInputs.size()
|
||||
index = allChatInputs.index(@input)
|
||||
|
||||
|
@ -542,7 +550,7 @@ class ChatWindow extends App.Controller
|
|||
allChatInputs.eq(chatCount-1).focus()
|
||||
|
||||
when ENTERKEY
|
||||
if !event.shiftKey
|
||||
if !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey
|
||||
event.preventDefault()
|
||||
@sendMessage()
|
||||
|
||||
|
@ -587,7 +595,7 @@ class ChatWindow extends App.Controller
|
|||
@sounds.message.play()
|
||||
@notifyDesktop(
|
||||
title: @name
|
||||
body: message
|
||||
body: App.Utils.html2text(message)
|
||||
url: '#customer_chat'
|
||||
callback: =>
|
||||
App.Event.trigger('chat_focus', { session_id: @session.session_id })
|
||||
|
|
|
@ -450,8 +450,8 @@ class EmailNotification extends App.WizardFullScreen
|
|||
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, autocomplete: 'new-password' },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off' },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||
]
|
||||
@form = new App.ControllerForm(
|
||||
|
@ -671,20 +671,24 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
|
||||
# inbound
|
||||
configureAttributesInbound = [
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||
{ 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, autocomplete: 'new-password', },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound },
|
||||
{ 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, autocomplete: 'off', },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', single: true },
|
||||
{ name: 'options::ssl', display: 'SSL', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' },
|
||||
{ name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' },
|
||||
]
|
||||
|
||||
showHideFolder = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
if params.adapter is 'imap'
|
||||
ui.show('options::folder')
|
||||
ui.show('options::keep_on_server')
|
||||
return
|
||||
ui.hide('options::folder')
|
||||
ui.hide('options::keep_on_server')
|
||||
|
||||
handlePort = (params, attribute, attributes, classname, form, ui) ->
|
||||
return if !params
|
||||
|
@ -700,7 +704,7 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
return
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @$('.base-inbound-settings'),
|
||||
el: @$('.base-inbound-settings')
|
||||
model:
|
||||
configure_attributes: configureAttributesInbound
|
||||
className: ''
|
||||
|
@ -716,8 +720,10 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
# fill user / password based on intro info
|
||||
channel_used = { options: {} }
|
||||
if @account['meta']
|
||||
channel_used['options']['user'] = @account['meta']['email']
|
||||
channel_used['options']['password'] = @account['meta']['password']
|
||||
channel_used['options']['user'] = @account['meta']['email']
|
||||
channel_used['options']['password'] = @account['meta']['password']
|
||||
channel_used['options']['folder'] = @account['meta']['folder']
|
||||
channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server']
|
||||
|
||||
# show used backend
|
||||
@$('.base-outbound-settings').html('')
|
||||
|
@ -725,8 +731,8 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
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, autocomplete: 'new-password', },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'new-password', single: true },
|
||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true },
|
||||
{ name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false },
|
||||
]
|
||||
@form = new App.ControllerForm(
|
||||
|
@ -745,7 +751,7 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
@account.meta = params
|
||||
|
||||
@disable(e)
|
||||
@$('.js-probe .js-email').text( params.email )
|
||||
@$('.js-probe .js-email').text(params.email)
|
||||
@showSlide('js-probe')
|
||||
|
||||
@ajax(
|
||||
|
@ -760,7 +766,7 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
for key, value of data.setting
|
||||
@account[key] = value
|
||||
|
||||
if data.content_messages && data.content_messages > 0
|
||||
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||
message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro')
|
||||
|
@ -809,7 +815,7 @@ class ChannelEmail extends App.WizardFullScreen
|
|||
# remember account settings
|
||||
@account.inbound = params
|
||||
|
||||
if data.content_messages && data.content_messages > 0
|
||||
if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true)
|
||||
message = App.i18n.translateContent('We have already found %s emails in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages)
|
||||
@$('.js-inbound-acknowledge .js-message').html(message)
|
||||
@$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound')
|
||||
|
|
|
@ -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, @)
|
||||
|
|
@ -10,6 +10,7 @@ class Index extends App.ControllerContent
|
|||
'.zendesk-api-token-error': 'apiTokenErrorMessage'
|
||||
'#zendesk-email': 'zendeskEmail'
|
||||
'#zendesk-api-token': 'zendeskApiToken'
|
||||
'.js-ticket-count-info': 'ticketCountInfo'
|
||||
updateMigrationDisplayLoop: 0
|
||||
|
||||
events:
|
||||
|
@ -116,7 +117,8 @@ class Index extends App.ControllerContent
|
|||
showCredentials: (e) =>
|
||||
e.preventDefault()
|
||||
@urlStatus.attr('data-state', '')
|
||||
@zendeskUrlApiToken.attr('href', @zendeskUrl.val() + 'agent/admin/api')
|
||||
url = @zendeskUrl.val() + '/agent/admin/api'
|
||||
@zendeskUrlApiToken.attr('href', url.replace(/([^:])\/\/+/g, '$1/'))
|
||||
@zendeskUrlApiToken.val('HERE')
|
||||
@$('[data-slide=zendesk-url]').toggleClass('hide')
|
||||
@$('[data-slide=zendesk-credentials]').toggleClass('hide')
|
||||
|
@ -171,6 +173,10 @@ class Index extends App.ControllerContent
|
|||
for key, item of data.data
|
||||
if item.done > item.total
|
||||
item.done = item.total
|
||||
|
||||
if key == 'Ticket' && item.total >= 1000
|
||||
@ticketCountInfo.removeClass('hide')
|
||||
|
||||
element = @$('.js-' + key.toLowerCase() )
|
||||
element.find('.js-done').text(item.done)
|
||||
element.find('.js-total').text(item.total)
|
||||
|
|
|
@ -1499,7 +1499,7 @@ class InputsRef extends App.ControllerContent
|
|||
null: false
|
||||
relation: 'User'
|
||||
autocapitalize: false
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
multiple: true
|
||||
|
||||
@$('.userOrganizationAutocompletePlaceholder').replaceWith( userOrganizationAutocomplete.element() )
|
||||
|
|
|
@ -38,50 +38,7 @@ class Index extends App.ControllerContent
|
|||
)
|
||||
|
||||
render: (data = {}) ->
|
||||
auth_provider_all = {
|
||||
facebook: {
|
||||
url: '/auth/facebook',
|
||||
name: 'Facebook',
|
||||
config: 'auth_facebook',
|
||||
class: 'facebook'
|
||||
},
|
||||
twitter: {
|
||||
url: '/auth/twitter'
|
||||
name: 'Twitter'
|
||||
config: 'auth_twitter'
|
||||
class: 'twitter'
|
||||
},
|
||||
linkedin: {
|
||||
url: '/auth/linkedin'
|
||||
name: 'LinkedIn'
|
||||
config: 'auth_linkedin'
|
||||
class: 'linkedin'
|
||||
},
|
||||
github: {
|
||||
url: '/auth/github'
|
||||
name: 'GitHub'
|
||||
config: 'auth_github'
|
||||
class: 'github'
|
||||
},
|
||||
gitlab: {
|
||||
url: '/auth/gitlab'
|
||||
name: 'GitLab'
|
||||
config: 'auth_gitlab'
|
||||
class: 'gitlab'
|
||||
},
|
||||
google_oauth2: {
|
||||
url: '/auth/google_oauth2'
|
||||
name: 'Google'
|
||||
config: 'auth_google_oauth2'
|
||||
class: 'google'
|
||||
},
|
||||
oauth2: {
|
||||
url: '/auth/oauth2'
|
||||
name: 'OAuth2'
|
||||
config: 'auth_oauth2'
|
||||
class: 'oauth2'
|
||||
},
|
||||
}
|
||||
auth_provider_all = App.Config.get('auth_provider_all')
|
||||
auth_providers = []
|
||||
for key, provider of auth_provider_all
|
||||
if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true'
|
||||
|
|
|
@ -6,7 +6,7 @@ class App.TicketCustomer extends App.ControllerModal
|
|||
|
||||
content: ->
|
||||
configure_attributes = [
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateUser: true },
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: true },
|
||||
]
|
||||
controller = new App.ControllerForm(
|
||||
model:
|
||||
|
|
|
@ -221,7 +221,7 @@ class App.TicketOverview extends App.Controller
|
|||
if @batchCountIndex == @batchCount
|
||||
App.Event.trigger('overview:fetch')
|
||||
)
|
||||
return
|
||||
return
|
||||
|
||||
if action is 'group_assign'
|
||||
@batchCount = items.length
|
||||
|
|
|
@ -401,10 +401,11 @@ class App.TicketZoom extends App.Controller
|
|||
nav: @nav
|
||||
isCustomer: @permissionCheck('ticket.customer')
|
||||
scrollbarWidth: App.Utils.getScrollBarWidth()
|
||||
dir: App.i18n.dir()
|
||||
)
|
||||
|
||||
new App.TicketZoomOverviewNavigator(
|
||||
el: elLocal.find('.overview-navigator')
|
||||
el: elLocal.find('.js-overviewNavigatorContainer')
|
||||
ticket_id: @ticket_id
|
||||
overview_id: @overview_id
|
||||
)
|
||||
|
@ -412,13 +413,13 @@ class App.TicketZoom extends App.Controller
|
|||
new App.TicketZoomTitle(
|
||||
object_id: @ticket_id
|
||||
overview_id: @overview_id
|
||||
el: elLocal.find('.ticket-title')
|
||||
el: elLocal.find('.js-ticketTitleContainer')
|
||||
task_key: @task_key
|
||||
)
|
||||
|
||||
new App.TicketZoomMeta(
|
||||
object_id: @ticket_id
|
||||
el: elLocal.find('.ticket-meta')
|
||||
el: elLocal.find('.js-ticketMetaContainer')
|
||||
)
|
||||
|
||||
@attributeBar = new App.TicketZoomAttributeBar(
|
||||
|
@ -445,7 +446,12 @@ class App.TicketZoom extends App.Controller
|
|||
)
|
||||
|
||||
@highligher = new App.TicketZoomHighlighter(
|
||||
el: elLocal.find('.highlighter')
|
||||
el: elLocal.find('.js-highlighterContainer')
|
||||
ticket_id: @ticket_id
|
||||
)
|
||||
|
||||
new App.TicketZoomSetting(
|
||||
el: elLocal.find('.js-settingContainer')
|
||||
ticket_id: @ticket_id
|
||||
)
|
||||
|
||||
|
@ -467,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
|
||||
|
@ -557,14 +564,16 @@ class App.TicketZoom extends App.Controller
|
|||
return if !@ticket
|
||||
currentStoreTicket = @ticket.attributes()
|
||||
delete currentStoreTicket.article
|
||||
internal = @Config.get('ui_ticket_zoom_article_note_new_internal')
|
||||
currentStore =
|
||||
ticket: currentStoreTicket
|
||||
article:
|
||||
to: ''
|
||||
cc: ''
|
||||
subject: ''
|
||||
type: 'note'
|
||||
body: ''
|
||||
internal: 'true'
|
||||
internal: internal
|
||||
in_reply_to: ''
|
||||
|
||||
if @permissionCheck('ticket.customer')
|
||||
|
@ -575,7 +584,7 @@ class App.TicketZoom extends App.Controller
|
|||
formCurrent: =>
|
||||
currentParams =
|
||||
ticket: @formParam(@el.find('.edit'))
|
||||
article: @formParam(@el.find('.article-add'))
|
||||
article: @articleNew.params()
|
||||
|
||||
# add attachments if exist
|
||||
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
|
||||
|
@ -684,7 +693,7 @@ class App.TicketZoom extends App.Controller
|
|||
tagAdd: (tag) =>
|
||||
return if !@sidebar
|
||||
return if !@sidebar.reload
|
||||
@sidebar.reload(tagAdd: tag)
|
||||
@sidebar.reload(tagAdd: tag, source: 'macro')
|
||||
tagRemove: (tag) =>
|
||||
return if !@sidebar
|
||||
return if !@sidebar.reload
|
||||
|
@ -789,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
|
||||
|
|
|
@ -391,6 +391,7 @@ class App.TicketZoomArticleActions extends App.Controller
|
|||
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
|
||||
|
||||
# check if quote need to be added
|
||||
signaturePosition = 'bottom'
|
||||
selected = App.ClipBoard.getSelected('html')
|
||||
if selected
|
||||
selected = App.Utils.htmlCleanup(selected).html()
|
||||
|
@ -399,6 +400,16 @@ class App.TicketZoomArticleActions extends App.Controller
|
|||
if selected
|
||||
selected = App.Utils.textCleanup(selected)
|
||||
selected = App.Utils.text2html(selected)
|
||||
|
||||
# full quote, if needed
|
||||
if !selected && article && App.Config.get('ui_ticket_zoom_article_email_full_quote')
|
||||
signaturePosition = 'top'
|
||||
if article.content_type.match('html')
|
||||
selected = App.Utils.textCleanup(article.body)
|
||||
if article.content_type.match('plain')
|
||||
selected = App.Utils.textCleanup(article.body)
|
||||
selected = App.Utils.text2html(selected)
|
||||
|
||||
if selected
|
||||
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
|
||||
|
||||
|
@ -409,7 +420,12 @@ class App.TicketZoomArticleActions extends App.Controller
|
|||
|
||||
type = App.TicketArticleType.findByAttribute(name:'email')
|
||||
|
||||
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
|
||||
App.Event.trigger('ui::ticket::setArticleType', {
|
||||
ticket: @ticket
|
||||
type: type
|
||||
article: articleNew
|
||||
signaturePosition: signaturePosition
|
||||
})
|
||||
|
||||
telegramPersonalMessageReply: (e) =>
|
||||
e.preventDefault()
|
||||
|
|
|
@ -28,7 +28,62 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
constructor: ->
|
||||
super
|
||||
|
||||
# set possble article types
|
||||
@internalSelector = true
|
||||
@type = @defaults['type'] || 'note'
|
||||
@setPossibleArticleTypes()
|
||||
|
||||
if @permissionCheck('ticket.customer')
|
||||
@internalSelector = false
|
||||
|
||||
@textareaHeight =
|
||||
open: 148
|
||||
closed: 20
|
||||
|
||||
@dragEventCounter = 0
|
||||
@attachments = []
|
||||
|
||||
@render()
|
||||
|
||||
if @defaults.body or @isIE10()
|
||||
@openTextarea(null, true)
|
||||
|
||||
# set article type and expand text area
|
||||
@bind('ui::ticket::setArticleType', (data) =>
|
||||
return if data.ticket.id.toString() isnt @ticket_id.toString()
|
||||
|
||||
@openTextarea(null, true)
|
||||
for key, value of data.article
|
||||
if key is 'body'
|
||||
@$('[data-name="' + key + '"]').html(value)
|
||||
else
|
||||
@$('[name="' + key + '"]').val(value).trigger('change')
|
||||
|
||||
# preselect article type
|
||||
@setArticleType(data.type.name, data.signaturePosition)
|
||||
|
||||
# set focus at end of field
|
||||
if data.position is 'end'
|
||||
@placeCaretAtEnd(@textarea.get(0))
|
||||
return
|
||||
|
||||
# set focus into field
|
||||
@textarea.focus()
|
||||
)
|
||||
|
||||
# reset new article screen
|
||||
@bind('ui::ticket::taskReset', (data) =>
|
||||
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
||||
@type = 'note'
|
||||
@defaults = {}
|
||||
@render()
|
||||
)
|
||||
|
||||
# rerender, e. g. on language change
|
||||
@bind('ui:rerender', =>
|
||||
@render()
|
||||
)
|
||||
|
||||
setPossibleArticleTypes: =>
|
||||
possibleArticleType =
|
||||
note: true
|
||||
phone: true
|
||||
|
@ -50,12 +105,9 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
possibleArticleType['email'] = true
|
||||
|
||||
# gets referenced in @setArticleType
|
||||
@internalSelector = true
|
||||
@type = @defaults['type'] || 'note'
|
||||
@articleTypes = []
|
||||
if possibleArticleType.note
|
||||
internal = @Config.get('ui_ticket_zoom_article_new_internal')
|
||||
|
||||
internal = @Config.get('ui_ticket_zoom_article_note_new_internal')
|
||||
@articleTypes.push {
|
||||
name: 'note'
|
||||
icon: 'note'
|
||||
|
@ -64,10 +116,13 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
features: ['attachment']
|
||||
}
|
||||
if possibleArticleType.email
|
||||
attributes = ['to', 'cc', 'subject']
|
||||
if !@Config.get('ui_ticket_zoom_article_email_subject')
|
||||
attributes = ['to', 'cc']
|
||||
@articleTypes.push {
|
||||
name: 'email'
|
||||
icon: 'email'
|
||||
attributes: ['to', 'cc']
|
||||
attributes: attributes
|
||||
internal: false,
|
||||
features: ['attachment']
|
||||
}
|
||||
|
@ -80,6 +135,9 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
features: []
|
||||
}
|
||||
if possibleArticleType['twitter status']
|
||||
attributes = ['body:limit', 'body:initials']
|
||||
if !@Config.get('ui_ticket_zoom_article_twitter_initials')
|
||||
attributes = ['body:limit']
|
||||
@articleTypes.push {
|
||||
name: 'twitter status'
|
||||
icon: 'twitter'
|
||||
|
@ -90,6 +148,9 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
warningTextLength: 30
|
||||
}
|
||||
if possibleArticleType['twitter direct-message']
|
||||
attributes = ['body:limit', 'body:initials']
|
||||
if !@Config.get('ui_ticket_zoom_article_twitter_initials')
|
||||
attributes = ['body:limit']
|
||||
@articleTypes.push {
|
||||
name: 'twitter direct-message'
|
||||
icon: 'twitter'
|
||||
|
@ -130,57 +191,6 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
},
|
||||
]
|
||||
|
||||
if @permissionCheck('ticket.customer')
|
||||
@internalSelector = false
|
||||
|
||||
@textareaHeight =
|
||||
open: 148
|
||||
closed: 20
|
||||
|
||||
@dragEventCounter = 0
|
||||
@attachments = []
|
||||
|
||||
@render()
|
||||
|
||||
if @defaults.body or @isIE10()
|
||||
@openTextarea(null, true)
|
||||
|
||||
# set article type and expand text area
|
||||
@bind('ui::ticket::setArticleType', (data) =>
|
||||
return if data.ticket.id.toString() isnt @ticket_id.toString()
|
||||
|
||||
@openTextarea(null, true)
|
||||
for key, value of data.article
|
||||
if key is 'body'
|
||||
@$('[data-name="' + key + '"]').html(value)
|
||||
else
|
||||
@$('[name="' + key + '"]').val(value).trigger('change')
|
||||
|
||||
# preselect article type
|
||||
@setArticleType(data.type.name)
|
||||
|
||||
# set focus at end of field
|
||||
if data.position is 'end'
|
||||
@placeCaretAtEnd(@textarea.get(0))
|
||||
return
|
||||
|
||||
# set focus into field
|
||||
@textarea.focus()
|
||||
)
|
||||
|
||||
# reset new article screen
|
||||
@bind('ui::ticket::taskReset', (data) =>
|
||||
return if data.ticket_id.toString() isnt @ticket_id.toString()
|
||||
@type = 'note'
|
||||
@defaults = {}
|
||||
@render()
|
||||
)
|
||||
|
||||
# rerender, e. g. on language change
|
||||
@bind('ui:rerender', =>
|
||||
@render()
|
||||
)
|
||||
|
||||
placeCaretAtEnd: (el) ->
|
||||
el.focus()
|
||||
if typeof window.getSelection isnt 'undefined' && typeof document.createRange isnt 'undefined'
|
||||
|
@ -300,6 +310,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
data:
|
||||
ticket: ticket
|
||||
user: App.Session.get()
|
||||
config: App.Config.all()
|
||||
)
|
||||
callback = (ticket) ->
|
||||
textModule.reload(
|
||||
|
@ -318,9 +329,6 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
params.form_id = @form_id
|
||||
params.content_type = 'text/html'
|
||||
|
||||
if !params['internal']
|
||||
params['internal'] = false
|
||||
|
||||
if @permissionCheck('ticket.customer')
|
||||
sender = App.TicketArticleSender.findByAttribute('name', 'Customer')
|
||||
type = App.TicketArticleType.findByAttribute('name', 'web')
|
||||
|
@ -332,6 +340,11 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
params.sender_id = sender.id
|
||||
params.type_id = type.id
|
||||
|
||||
if params.internal
|
||||
params.internal = true
|
||||
else
|
||||
params.internal = false
|
||||
|
||||
if params.type is 'twitter status'
|
||||
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'), false)
|
||||
params.content_type = 'text/plain'
|
||||
|
@ -471,13 +484,15 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
|
||||
@$('[name=internal]').val('')
|
||||
|
||||
setArticleType: (type) =>
|
||||
setArticleType: (type, signaturePosition = 'bottom') =>
|
||||
wasScrolledToBottom = @isScrolledToBottom()
|
||||
@type = type
|
||||
@$('[name=type]').val(type).trigger('change')
|
||||
@articleNewEdit.attr('data-type', type)
|
||||
@$('.js-selectableTypes').addClass('hide').filter("[data-type='#{type}']").removeClass('hide')
|
||||
|
||||
@setPossibleArticleTypes()
|
||||
|
||||
# get config
|
||||
config = {}
|
||||
for articleTypeConfig in @articleTypes
|
||||
|
@ -510,7 +525,7 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
@$('[data-name=body] [data-signature="true"]').remove()
|
||||
|
||||
# apply new signature
|
||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent })
|
||||
signatureFinished = App.Utils.replaceTags(signature.body, { user: App.Session.get(), ticket: ticketCurrent, config: App.Config.all() })
|
||||
|
||||
body = @$('[data-name=body]')
|
||||
if App.Utils.signatureCheck(body.html() || '', signatureFinished)
|
||||
|
@ -518,7 +533,10 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
body.append('<br><br>')
|
||||
signature = $("<div data-signature=\"true\" data-signature-id=\"#{signature.id}\">#{signatureFinished}</div>")
|
||||
App.Utils.htmlStrip(signature)
|
||||
body.append(signature)
|
||||
if signaturePosition is 'top'
|
||||
body.prepend(signature)
|
||||
else
|
||||
body.append(signature)
|
||||
@$('[data-name=body]').replaceWith(body)
|
||||
|
||||
# remove old signature
|
||||
|
@ -552,6 +570,20 @@ class App.TicketZoomArticleNew extends App.Controller
|
|||
@delay(@updateLetterCount, 600)
|
||||
@$('.js-textSizeLimit').removeClass('hide')
|
||||
|
||||
# convert remote src images to data uri
|
||||
@$('[data-name=body] img').each( (i,image) ->
|
||||
$image = $(image)
|
||||
src = $image.attr('src')
|
||||
if !_.isEmpty(src) && !src.match(/^data:image/i)
|
||||
canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(image, 0, 0)
|
||||
dataURL = canvas.toDataURL()
|
||||
$image.attr('src', dataURL)
|
||||
)
|
||||
|
||||
@scrollToBottom() if wasScrolledToBottom
|
||||
|
||||
isScrolledToBottom: ->
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
class App.TicketZoomSetting extends App.Controller
|
||||
events:
|
||||
'click .js-setting': 'show'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
return if !@permissionCheck('admin')
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
@html(App.view('ticket_zoom/setting')())
|
||||
|
||||
show: ->
|
||||
new Modal()
|
||||
|
||||
class Modal extends App.ControllerModal
|
||||
buttonClose: true
|
||||
buttonCancel: true
|
||||
buttonSubmit: false
|
||||
head: 'Settings'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
render: =>
|
||||
super
|
||||
|
||||
post: =>
|
||||
new App.SettingsArea(
|
||||
area: 'UI::TicketZoom'
|
||||
el: @el.find('.modal-body')
|
||||
)
|
||||
|
||||
content: ->
|
||||
App.view('generic/page_loading')()
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class SidebarCustomer extends App.Controller
|
||||
sidebarItem: =>
|
||||
return if !@permissionCheck('ticket.agent')
|
||||
{
|
||||
items = {
|
||||
head: 'Customer'
|
||||
name: 'customer'
|
||||
icon: 'person'
|
||||
|
@ -11,14 +11,16 @@ class SidebarCustomer extends App.Controller
|
|||
name: 'customer-change'
|
||||
callback: @changeCustomer
|
||||
},
|
||||
{
|
||||
title: 'Edit Customer'
|
||||
name: 'customer-edit'
|
||||
callback: @editCustomer
|
||||
},
|
||||
]
|
||||
callback: @showCustomer
|
||||
}
|
||||
return items if @ticket && @ticket.customer_id == 1
|
||||
items.actions.push {
|
||||
title: 'Edit Customer'
|
||||
name: 'customer-edit'
|
||||
callback: @editCustomer
|
||||
}
|
||||
items
|
||||
|
||||
showCustomer: (el) =>
|
||||
@el = el
|
||||
|
|
|
@ -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("<div>#{App.i18n.translateInline('none')}</div>")
|
||||
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')
|
|
@ -1,5 +1,6 @@
|
|||
class SidebarOrganization extends App.Controller
|
||||
sidebarItem: =>
|
||||
return if !@permissionCheck('ticket.agent')
|
||||
return if !@ticket.organization_id
|
||||
{
|
||||
head: 'Organization'
|
||||
|
|
|
@ -69,7 +69,7 @@ class SidebarTicket extends App.Controller
|
|||
if args.tags
|
||||
@tagWidget.reload(args.tags)
|
||||
if args.tagAdd
|
||||
@tagWidget.add(args.tagAdd)
|
||||
@tagWidget.add(args.tagAdd, args.source)
|
||||
if args.tagRemove
|
||||
@tagWidget.remove(args.tagRemove)
|
||||
|
||||
|
|
|
@ -7,6 +7,12 @@ class App.WidgetAvatar extends App.ObserverController
|
|||
email: true
|
||||
image: true
|
||||
vip: true
|
||||
out_of_office: true,
|
||||
out_of_office_start_at: true,
|
||||
out_of_office_end_at: true,
|
||||
out_of_office_replacement_id: true,
|
||||
active: true
|
||||
|
||||
globalRerender: false
|
||||
|
||||
render: (user) =>
|
||||
|
|
|
@ -5,10 +5,10 @@ class Widget
|
|||
banner = """
|
||||
|
|
||||
| Welcome Zammad Developer!
|
||||
| You can enable debugging by the following examples (value is a regex):
|
||||
| You can enable debugging with the following examples (value is a regex):
|
||||
|
|
||||
| App.Log.config('module', '(websocket|delay|interval)') // enable debugging for websocket, delay and interval class
|
||||
| App.Log.config('content', 'send') // enable debugging for messages which contains the string 'send'
|
||||
| App.Log.config('content', 'send') // enable debugging for messages which contain the string 'send'
|
||||
| App.Log.config('banner', false) // disable this banner
|
||||
|
|
||||
| App.Log.config() // current settings
|
||||
|
|
|
@ -10,9 +10,9 @@ class Widget
|
|||
|
|
||||
| Hi there, nice to meet you!
|
||||
|
|
||||
| Visit %chttps://zammad.org/participate%c and let's make Zammad better.
|
||||
| Visit %chttp://zammad.com/jobs%c to learn about our current job openings.
|
||||
|
|
||||
| The Zammad Team.
|
||||
| Your Zammad Team
|
||||
|
|
||||
"""
|
||||
console.log(banner, 'text-decoration: underline;', 'text-decoration: none;')
|
||||
|
|
|
@ -25,6 +25,7 @@ class App.HttpLog extends App.Controller
|
|||
render: =>
|
||||
@html App.view('widget/http_log')(
|
||||
records: @records
|
||||
description: @description
|
||||
)
|
||||
|
||||
show: (e) =>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
class App.ScriptSnipped extends App.Controller
|
||||
#events:
|
||||
# 'click .js-record': 'show'
|
||||
|
||||
elements:
|
||||
'.js-code': 'code'
|
||||
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
#@fetch()
|
||||
@records = []
|
||||
@render()
|
||||
|
||||
render: =>
|
||||
@html App.view('widget/script_snipped')(
|
||||
records: @records
|
||||
description: @description
|
||||
style: @style
|
||||
content: @content
|
||||
)
|
||||
|
||||
@code.each (i, block) ->
|
||||
hljs.highlightBlock block
|
|
@ -86,16 +86,16 @@ class App.WidgetTag extends App.Controller
|
|||
return
|
||||
@add(item)
|
||||
|
||||
add: (items) =>
|
||||
add: (items, source = '') =>
|
||||
for item in items.split(',')
|
||||
item = item.trim()
|
||||
@addItem(item)
|
||||
@addItem(item, source)
|
||||
|
||||
addItem: (item) =>
|
||||
addItem: (item, source = '') =>
|
||||
if _.contains(@localTags, item)
|
||||
@render()
|
||||
return
|
||||
return if App.Config.get('tag_new') is false && !@possibleTags[item]
|
||||
return if source != 'macro' && App.Config.get('tag_new') is false && !@possibleTags[item]
|
||||
@localTags.push item
|
||||
@render()
|
||||
App[@object_type].tagAdd(@object.id, item)
|
||||
|
|
|
@ -8,7 +8,7 @@ class App.WidgetTextModule extends App.Controller
|
|||
# remember instances
|
||||
@bindElements = []
|
||||
if @selector
|
||||
@bindElements = @$( @selector ).textmodule()
|
||||
@bindElements = @$(@selector).textmodule()
|
||||
else
|
||||
if @el.attr('contenteditable')
|
||||
@bindElements = @el.textmodule()
|
||||
|
|
57
app/assets/javascripts/app/lib/app_init/queue_manager.coffee
Normal file
57
app/assets/javascripts/app/lib/app_init/queue_manager.coffee
Normal file
|
@ -0,0 +1,57 @@
|
|||
class App.QueueManager
|
||||
_instance = undefined
|
||||
|
||||
@init: ->
|
||||
_instance ?= new _queueSingleton
|
||||
|
||||
@add: (key, data) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _queueSingleton
|
||||
_instance.add(key, data)
|
||||
|
||||
@pull: (key) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _queueSingleton
|
||||
_instance.pull(key)
|
||||
|
||||
@all: (key) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _queueSingleton
|
||||
_instance.all(key)
|
||||
|
||||
@run: (key, callback) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _queueSingleton
|
||||
_instance.run(key, callback)
|
||||
|
||||
class _queueSingleton
|
||||
constructor: ->
|
||||
@queues = {}
|
||||
@queueRunning = {}
|
||||
|
||||
add: (key, data) ->
|
||||
if !@queues[key]
|
||||
@queues[key] = []
|
||||
@queues[key].push data
|
||||
true
|
||||
|
||||
pull: (key) ->
|
||||
return if !@queues[key]
|
||||
@queues[key].shift()
|
||||
|
||||
all: (key) ->
|
||||
@queues[key]
|
||||
|
||||
run: (key, callback) ->
|
||||
return if !@queues[key]
|
||||
return if @queueRunning[key]
|
||||
localQueue = @queues[key]
|
||||
return if _.isEmpty(localQueue)
|
||||
@queueRunning[key] = true
|
||||
loop
|
||||
callback = localQueue.shift()
|
||||
callback()
|
||||
if !localQueue[0]
|
||||
@queueRunning[key] = false
|
||||
break
|
||||
true
|
|
@ -5,9 +5,9 @@ class App._CollectionSingletonBase
|
|||
constructor: ->
|
||||
@callbacks = {}
|
||||
@counter = 0
|
||||
|
||||
@key = "collection-#{@event}"
|
||||
# read from cache
|
||||
cache = App.SessionStorage.get("collection-#{@event}")
|
||||
cache = App.SessionStorage.get(@key)
|
||||
if cache
|
||||
@set(cache)
|
||||
|
||||
|
@ -73,6 +73,9 @@ class App._CollectionSingletonBase
|
|||
|
||||
callback: (data) =>
|
||||
for counter, attr of @callbacks
|
||||
attr.callback(data)
|
||||
if attr.one
|
||||
delete @callbacks[counter]
|
||||
callback = ->
|
||||
attr.callback(data)
|
||||
if attr.one
|
||||
delete @callbacks[counter]
|
||||
App.QueueManager.add(@key, callback)
|
||||
App.QueueManager.run(@key)
|
||||
|
|
|
@ -33,6 +33,11 @@ class App.ColumnSelect extends Spine.Controller
|
|||
@select @pickedValue
|
||||
, 300, {trailing: false}
|
||||
|
||||
if @attribute.onChange
|
||||
@shadow.on('change', =>
|
||||
@attribute.onChange(@shadow.val())
|
||||
)
|
||||
|
||||
render: ->
|
||||
@values = []
|
||||
_.each @options.attribute.options, (option) =>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
class App.Delay
|
||||
_instance = undefined
|
||||
|
||||
@set: (callback, timeout, key, level) ->
|
||||
@set: (callback, timeout, key, level, queue) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _delaySingleton
|
||||
_instance.set(callback, timeout, key, level)
|
||||
_instance.set(callback, timeout, key, level, queue)
|
||||
|
||||
@clear: (key, level) ->
|
||||
if _instance == undefined
|
||||
|
@ -21,6 +21,11 @@ class App.Delay
|
|||
_instance ?= new _delaySingleton
|
||||
_instance.reset()
|
||||
|
||||
@count: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _intervalSingleton
|
||||
_instance.count()
|
||||
|
||||
@_all: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _delaySingleton
|
||||
|
@ -32,7 +37,7 @@ class _delaySingleton extends Spine.Module
|
|||
constructor: ->
|
||||
@levelStack = {}
|
||||
|
||||
set: (callback, timeout, key, level) =>
|
||||
set: (callback, timeout, key, level, queue) =>
|
||||
|
||||
if !level
|
||||
level = '_all'
|
||||
|
@ -44,11 +49,15 @@ class _delaySingleton extends Spine.Module
|
|||
key = Math.floor(Math.random() * 99999)
|
||||
|
||||
# setTimeout
|
||||
@log 'debug', 'set', key, timeout, level, callback
|
||||
call = =>
|
||||
@log 'debug', 'set', key, timeout, level, callback, queue
|
||||
localCallback = =>
|
||||
@clear(key, level)
|
||||
callback()
|
||||
delay_id = setTimeout(call, timeout)
|
||||
if queue
|
||||
App.QueueManager.add('delay', callback)
|
||||
App.QueueManager.run('delay')
|
||||
else
|
||||
callback()
|
||||
delay_id = setTimeout(localCallback, timeout)
|
||||
|
||||
# remember all delays
|
||||
if !@levelStack[level]
|
||||
|
@ -93,6 +102,13 @@ class _delaySingleton extends Spine.Module
|
|||
@levelStack[level] = {}
|
||||
true
|
||||
|
||||
count: =>
|
||||
return 0 if !@levelStack
|
||||
count = 0
|
||||
for levelName, levelValue of @levelStack
|
||||
count += Object.keys(levelValue).length
|
||||
count
|
||||
|
||||
_all: =>
|
||||
@levelStack
|
||||
|
||||
|
|
|
@ -30,6 +30,11 @@ class App.i18n
|
|||
_instance ?= new _i18nSingleton()
|
||||
_instance.date(args, offset)
|
||||
|
||||
@dir: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _i18nSingleton()
|
||||
_instance.dir()
|
||||
|
||||
@get: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _i18nSingleton()
|
||||
|
@ -88,6 +93,10 @@ class _i18nSingleton extends Spine.Module
|
|||
@_notTranslated = {}
|
||||
@dateFormat = 'yyyy-mm-dd'
|
||||
@timestampFormat = 'yyyy-mm-dd HH:MM'
|
||||
@dirToSet = 'ltr'
|
||||
|
||||
dir: ->
|
||||
@dirToSet
|
||||
|
||||
get: ->
|
||||
@locale
|
||||
|
@ -96,7 +105,7 @@ class _i18nSingleton extends Spine.Module
|
|||
|
||||
# prepare locale
|
||||
localeToSet = localeToSet.toLowerCase()
|
||||
dirToSet = 'ltr'
|
||||
@dirToSet = 'ltr'
|
||||
|
||||
# check if locale exists
|
||||
localeFound = false
|
||||
|
@ -104,7 +113,7 @@ class _i18nSingleton extends Spine.Module
|
|||
for locale in locales
|
||||
if locale.locale is localeToSet
|
||||
localeToSet = locale.locale
|
||||
dirToSet = locale.dir
|
||||
@dirToSet = locale.dir
|
||||
localeFound = true
|
||||
|
||||
# try aliases
|
||||
|
@ -112,7 +121,7 @@ class _i18nSingleton extends Spine.Module
|
|||
for locale in locales
|
||||
if locale.alias is localeToSet
|
||||
localeToSet = locale.locale
|
||||
dirToSet = locale.dir
|
||||
@dirToSet = locale.dir
|
||||
localeFound = true
|
||||
|
||||
# if no locale and no alias was found, try to find correct one
|
||||
|
@ -123,7 +132,7 @@ class _i18nSingleton extends Spine.Module
|
|||
for locale in locales
|
||||
if locale.alias is localeToSet
|
||||
localeToSet = locale.locale
|
||||
dirToSet = locale.dir
|
||||
@dirToSet = locale.dir
|
||||
localeFound = true
|
||||
|
||||
# check if locale need to be changed
|
||||
|
@ -137,7 +146,7 @@ class _i18nSingleton extends Spine.Module
|
|||
|
||||
# set lang and dir attribute of html tag
|
||||
$('html').prop('lang', localeToSet.substr(0, 2))
|
||||
$('html').prop('dir', dirToSet)
|
||||
$('html').prop('dir', @dirToSet)
|
||||
|
||||
@mapString = {}
|
||||
App.Ajax.request(
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
class App.Interval
|
||||
_instance = undefined
|
||||
|
||||
@set: (callback, timeout, key, level) ->
|
||||
@set: (callback, timeout, key, level, queue) ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _intervalSingleton
|
||||
_instance.set(callback, timeout, key, level)
|
||||
_instance.set(callback, timeout, key, level, queue)
|
||||
|
||||
@clear: (key, level) ->
|
||||
if _instance == undefined
|
||||
|
@ -21,6 +21,11 @@ class App.Interval
|
|||
_instance ?= new _intervalSingleton
|
||||
_instance.reset()
|
||||
|
||||
@count: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _intervalSingleton
|
||||
_instance.count()
|
||||
|
||||
@_all: ->
|
||||
if _instance == undefined
|
||||
_instance ?= new _intervalSingleton
|
||||
|
@ -32,7 +37,7 @@ class _intervalSingleton extends Spine.Module
|
|||
constructor: ->
|
||||
@levelStack = {}
|
||||
|
||||
set: (callback, timeout, key, level) =>
|
||||
set: (callback, timeout, key, level, queue) =>
|
||||
|
||||
if !level
|
||||
level = '_all'
|
||||
|
@ -44,9 +49,15 @@ class _intervalSingleton extends Spine.Module
|
|||
key = Math.floor(Math.random() * 99999)
|
||||
|
||||
# setTimeout
|
||||
@log 'debug', 'set', key, timeout, level, callback
|
||||
callback()
|
||||
interval_id = setInterval(callback, timeout)
|
||||
@log 'debug', 'set', key, timeout, level, callback, queue
|
||||
localCallback = ->
|
||||
if queue
|
||||
App.QueueManager.add('interval', callback)
|
||||
App.QueueManager.run('interval')
|
||||
else
|
||||
callback()
|
||||
localCallback()
|
||||
interval_id = setInterval(localCallback, timeout)
|
||||
|
||||
# remember all interval
|
||||
if !@levelStack[level]
|
||||
|
@ -91,5 +102,12 @@ class _intervalSingleton extends Spine.Module
|
|||
@levelStack[level] = {}
|
||||
true
|
||||
|
||||
count: =>
|
||||
return 0 if !@levelStack
|
||||
count = 0
|
||||
for levelName, levelValue of @levelStack
|
||||
count += Object.keys(levelValue).length
|
||||
count
|
||||
|
||||
_all: =>
|
||||
@levelStack
|
||||
|
|
|
@ -71,7 +71,10 @@ class _Singleton
|
|||
callback: (view, data) =>
|
||||
for counter, meta of @callbacks
|
||||
if meta.view is view
|
||||
meta.callback(data)
|
||||
callback = ->
|
||||
meta.callback(data)
|
||||
App.QueueManager.add('ticket_overviews', callback)
|
||||
App.QueueManager.run('ticket_overviews')
|
||||
|
||||
class App.OverviewListCollection
|
||||
_instance = new _Singleton
|
||||
|
|
|
@ -38,11 +38,16 @@ class App.PrettyDate
|
|||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
month = months[created.getMonth()]
|
||||
|
||||
# for less than 7 days
|
||||
if diff < (60 * 60 * 24 * 7)
|
||||
# for less than 6 days
|
||||
# weekday HH::MM
|
||||
if diff < (60 * 60 * 24 * 6)
|
||||
string = "#{App.i18n.translateInline(weekday)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
||||
else if diff < (60 * 60 * 24 * 7) * 365
|
||||
# if it was this year
|
||||
# weekday DD. MM HH::MM
|
||||
else if created.getYear() is current.getYear()
|
||||
string = "#{App.i18n.translateInline(weekday)} #{created.getDate()}. #{App.i18n.translateInline(month)} #{created.getHours()}:#{@s(created.getMinutes(), 2)}"
|
||||
# if it was the year before
|
||||
# weekday YYYY-MM-DD HH::MM
|
||||
else
|
||||
string = "#{App.i18n.translateInline(weekday)} #{App.i18n.translateTimestamp(time)}"
|
||||
if escalation
|
||||
|
|
|
@ -124,10 +124,12 @@ class App.Utils
|
|||
@linkify: (string) ->
|
||||
window.linkify(string)
|
||||
|
||||
# htmlEscapedAndLinkified = App.Utils.linkify(rawText)
|
||||
# htmlEscapedAndPhoneified = App.Utils.phoneify(rawText)
|
||||
@phoneify: (string) ->
|
||||
string = string.replace(/\s+/g, '')
|
||||
"tel://#{encodeURIComponent(string)}"
|
||||
return string if _.isEmpty(string)
|
||||
string = string.replace(/[^0-9,\+,#,\*]+/g, '')
|
||||
.replace(/(.)\+/, '$1')
|
||||
"tel:#{string}"
|
||||
|
||||
# wrappedText = App.Utils.wrap(rawText, maxLineLength)
|
||||
@wrap: (ascii, max = 82) ->
|
||||
|
@ -649,6 +651,7 @@ class App.Utils
|
|||
# textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } )
|
||||
@replaceTags: (template, objects) ->
|
||||
template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, (index, key) ->
|
||||
key = key.replace(/<.+?>/g, '')
|
||||
levels = key.split(/\./)
|
||||
dataRef = objects
|
||||
for level in levels
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -88,7 +88,7 @@
|
|||
// handle enter
|
||||
this.$element.on('keydown', function (e) {
|
||||
_this.log('keydown', e.keyCode)
|
||||
if ( _this.preventInput ) {
|
||||
if (_this.preventInput) {
|
||||
this.log('preventInput', _this.preventInput)
|
||||
return
|
||||
}
|
||||
|
@ -97,18 +97,29 @@
|
|||
if (e.keyCode === 13) {
|
||||
|
||||
// disbale multi line
|
||||
if ( !_this.options.multiline ) {
|
||||
if (!_this.options.multiline) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
// break <blockquote> after enter on empty line
|
||||
sel = window.getSelection()
|
||||
node = $(sel.anchorNode)
|
||||
if (node.parent().is('blockquote')) {
|
||||
if (sel) {
|
||||
node = $(sel.anchorNode)
|
||||
if (node && node.parent() && node.parent().is('blockquote')) {
|
||||
e.preventDefault()
|
||||
document.execCommand('Insertparagraph')
|
||||
document.execCommand('Outdent')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// behavior to enter new line on alt+enter
|
||||
// on alt + enter not realy newline is fired, to make
|
||||
// it compat. to other systems, do it here
|
||||
if (!e.shiftKey && e.altKey && !e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault()
|
||||
document.execCommand('Insertparagraph')
|
||||
document.execCommand('Outdent')
|
||||
_this.paste('<br><br>')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +248,7 @@
|
|||
|
||||
// limit check
|
||||
if ( !_this.allowKey(e) ) {
|
||||
if ( !_this.maxLengthOk( 1 ) ) {
|
||||
if ( !_this.maxLengthOk(1) ) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
@ -295,7 +306,7 @@
|
|||
else {
|
||||
img = "<img style=\"width: 100%; max-width: " + width + "px;\" src=\"" + result + "\">"
|
||||
}
|
||||
document.execCommand('insertHTML', false, img)
|
||||
_this.paste(img)
|
||||
}
|
||||
|
||||
// resize if to big
|
||||
|
@ -367,13 +378,7 @@
|
|||
text = App.Utils.removeEmptyLines(text)
|
||||
_this.log('insert', text)
|
||||
|
||||
// as fallback, insert html via pasteHtmlAtCaret (for IE 11 and lower)
|
||||
if (docType == 'text3') {
|
||||
_this.pasteHtmlAtCaret(text)
|
||||
}
|
||||
else {
|
||||
document.execCommand('insertHTML', false, text)
|
||||
}
|
||||
_this.paste(text)
|
||||
return true
|
||||
})
|
||||
|
||||
|
@ -533,37 +538,6 @@
|
|||
return this.$element.html().trim()
|
||||
}
|
||||
|
||||
// taken from https://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294
|
||||
Plugin.prototype.pasteHtmlAtCaret = function(html) {
|
||||
var sel, range;
|
||||
if (window.getSelection) {
|
||||
sel = window.getSelection()
|
||||
if (sel.getRangeAt && sel.rangeCount) {
|
||||
range = sel.getRangeAt(0)
|
||||
range.deleteContents()
|
||||
|
||||
var el = document.createElement('div')
|
||||
el.innerHTML = html;
|
||||
var frag = document.createDocumentFragment(), node, lastNode
|
||||
while ( (node = el.firstChild) ) {
|
||||
lastNode = frag.appendChild(node)
|
||||
}
|
||||
range.insertNode(frag)
|
||||
|
||||
if (lastNode) {
|
||||
range = range.cloneRange()
|
||||
range.setStartAfter(lastNode)
|
||||
range.collapse(true)
|
||||
sel.removeAllRanges()
|
||||
sel.addRange(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (document.selection && document.selection.type != 'Control') {
|
||||
document.selection.createRange().pasteHTML(html)
|
||||
}
|
||||
}
|
||||
|
||||
// log method
|
||||
Plugin.prototype.log = function() {
|
||||
if (App && App.Log) {
|
||||
|
@ -574,7 +548,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
$.fn[pluginName] = function ( options ) {
|
||||
// paste some content
|
||||
Plugin.prototype.paste = function(string) {
|
||||
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
|
||||
|
||||
// IE <= 10
|
||||
if (document.selection && document.selection.createRange) {
|
||||
var range = document.selection.createRange()
|
||||
if (range.pasteHTML) {
|
||||
range.pasteHTML(string)
|
||||
}
|
||||
}
|
||||
// IE == 11
|
||||
else if (isIE11 && document.getSelection) {
|
||||
var range = document.getSelection().getRangeAt(0)
|
||||
var nnode = document.createElement('div')
|
||||
range.surroundContents(nnode)
|
||||
nnode.innerHTML = string
|
||||
}
|
||||
else {
|
||||
document.execCommand('insertHTML', false, string)
|
||||
}
|
||||
}
|
||||
|
||||
$.fn[pluginName] = function (options) {
|
||||
return this.each(function () {
|
||||
if (!$.data(this, 'plugin_' + pluginName)) {
|
||||
$.data(this, 'plugin_' + pluginName,
|
||||
|
@ -586,6 +583,9 @@
|
|||
// get correct val if textbox
|
||||
$.fn.ceg = function() {
|
||||
var plugin = $.data(this[0], 'plugin_' + pluginName)
|
||||
if (!plugin) {
|
||||
return
|
||||
}
|
||||
return plugin.value()
|
||||
}
|
||||
|
||||
|
|
|
@ -43,17 +43,21 @@
|
|||
|
||||
this.$element.on('keydown', function (e) {
|
||||
|
||||
// esc
|
||||
if (e.keyCode === 27) {
|
||||
_this.close()
|
||||
}
|
||||
|
||||
// navigate through item
|
||||
if (_this.isActive()) {
|
||||
|
||||
// esc
|
||||
if (e.keyCode === 27) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
_this.close()
|
||||
return
|
||||
}
|
||||
|
||||
// enter
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var id = _this.$widget.find('.dropdown-menu li.is-active').data('id')
|
||||
|
||||
// as fallback use hovered element
|
||||
|
@ -72,12 +76,14 @@
|
|||
// arrow keys left/right
|
||||
if (e.keyCode === 37 || e.keyCode === 39) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
|
||||
// up or down
|
||||
if (e.keyCode === 38 || e.keyCode === 40) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var active = _this.$widget.find('.dropdown-menu li.is-active')
|
||||
active.removeClass('is-active')
|
||||
|
||||
|
@ -92,6 +98,9 @@
|
|||
|
||||
var menu = _this.$widget.find('.dropdown-menu')
|
||||
|
||||
if (!active.get(0)) {
|
||||
return
|
||||
}
|
||||
if (active.position().top < 0) {
|
||||
// scroll up
|
||||
menu.scrollTop( menu.scrollTop() + active.position().top )
|
||||
|
@ -102,7 +111,11 @@
|
|||
menu.scrollTop( menu.scrollTop() + invisibleHeight )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// esc
|
||||
if (e.keyCode === 27) {
|
||||
_this.close()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -180,14 +193,14 @@
|
|||
Plugin.prototype.renderBase = function() {
|
||||
this.$element.after('<div class="shortcut dropdown"><ul class="dropdown-menu" style="max-height: 200px;"></ul></div>')
|
||||
this.$widget = this.$element.next()
|
||||
this.$widget.on('click', 'li', $.proxy(this.onEntryClick, this))
|
||||
this.$widget.on('mousedown', 'li', $.proxy(this.onEntryClick, this))
|
||||
this.$widget.on('mouseenter', 'li', $.proxy(this.onMouseEnter, this))
|
||||
}
|
||||
|
||||
// set height of widget
|
||||
Plugin.prototype.movePosition = function() {
|
||||
if (!this._position) return
|
||||
var height = this.$element.height() + 2
|
||||
var height = this.$element.outerHeight() + 2
|
||||
var widgetHeight = this.$widget.find('ul').height() //+ 60 // + height
|
||||
var top = -( widgetHeight + height ) + this._position.top
|
||||
var left = this._position.left - 6
|
||||
|
@ -250,9 +263,21 @@
|
|||
|
||||
// paste some content
|
||||
Plugin.prototype.paste = function(string) {
|
||||
if (document.selection) { // IE
|
||||
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
|
||||
|
||||
// IE <= 10
|
||||
if (document.selection && document.selection.createRange) {
|
||||
var range = document.selection.createRange()
|
||||
range.pasteHTML(string)
|
||||
if (range.pasteHTML) {
|
||||
range.pasteHTML(string)
|
||||
}
|
||||
}
|
||||
// IE == 11
|
||||
else if (isIE11 && document.getSelection) {
|
||||
var range = document.getSelection().getRangeAt(0)
|
||||
var nnode = document.createElement('div')
|
||||
range.surroundContents(nnode)
|
||||
nnode.innerHTML = string
|
||||
}
|
||||
else {
|
||||
document.execCommand('insertHTML', false, string)
|
||||
|
@ -295,14 +320,7 @@
|
|||
// for chrome, insert space again
|
||||
if (start) {
|
||||
if (spacerChar === ' ') {
|
||||
string = " "
|
||||
if (document.selection) { // IE
|
||||
var range = document.selection.createRange()
|
||||
range.pasteHTML(string)
|
||||
}
|
||||
else {
|
||||
document.execCommand('insertHTML', false, string)
|
||||
}
|
||||
this.paste(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,6 +331,7 @@
|
|||
}
|
||||
|
||||
Plugin.prototype.onEntryClick = function(event) {
|
||||
event.preventDefault()
|
||||
var id = $(event.target).data('id')
|
||||
this.take(id)
|
||||
}
|
||||
|
@ -325,7 +344,7 @@
|
|||
}
|
||||
for (var i = 0; i < this.collection.length; i++) {
|
||||
var item = this.collection[i]
|
||||
if ( item.id == id ) {
|
||||
if (item.id == id) {
|
||||
var content = item.content
|
||||
this.cutInput()
|
||||
this.paste(content)
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
modified by Felix Jan-2014
|
||||
- add this.$body = $(options.container || document.body)
|
||||
- adjustBackdrop: also adopt left, top and width from $body
|
||||
modified by Felix Jul-2017
|
||||
- add rtl support
|
||||
*/
|
||||
|
||||
|
||||
|
@ -244,6 +246,10 @@
|
|||
.css('height', 0)
|
||||
.css('height', this.$element[0].scrollHeight)
|
||||
|
||||
if(App.i18n.dir() == 'rtl'){
|
||||
this.$backdrop.css('right', 'auto')
|
||||
}
|
||||
|
||||
if(this.scrollbarWidth){
|
||||
this.$backdrop.css('width', this.$body.width() - this.scrollbarWidth)
|
||||
}
|
||||
|
@ -251,14 +257,22 @@
|
|||
|
||||
Modal.prototype.adjustDialog = function () {
|
||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||
|
||||
this.$element.css({
|
||||
var css = {
|
||||
left: this.$body.offset().left,
|
||||
top: this.$body.offset().top,
|
||||
width: this.$body.width(),
|
||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||
})
|
||||
}
|
||||
|
||||
if(App.i18n.dir() == 'rtl'){
|
||||
css.right = 'auto'
|
||||
var paddingLeft = css.paddingLeft
|
||||
css.paddingLeft = css.paddingRight
|
||||
css.paddingRight = paddingLeft
|
||||
}
|
||||
|
||||
this.$element.css(css)
|
||||
}
|
||||
|
||||
Modal.prototype.resetAdjustments = function () {
|
||||
|
|
|
@ -32,6 +32,10 @@ class App.Model extends Spine.Model
|
|||
return @title
|
||||
if @subject
|
||||
return @subject
|
||||
if @phone
|
||||
return @phone
|
||||
if @login
|
||||
return @login
|
||||
return '???'
|
||||
|
||||
displayNameLong: ->
|
||||
|
@ -57,6 +61,12 @@ class App.Model extends Spine.Model
|
|||
return @email
|
||||
if @title
|
||||
return @title
|
||||
if @subject
|
||||
return @subject
|
||||
if @phone
|
||||
return @phone
|
||||
if @login
|
||||
return @login
|
||||
return '???'
|
||||
|
||||
icon: (user) ->
|
||||
|
@ -165,6 +175,31 @@ class App.Model extends Spine.Model
|
|||
|
||||
###
|
||||
|
||||
set new attributes of model (remove already available attributes)
|
||||
|
||||
App.Model.attributesSet(attributes)
|
||||
|
||||
###
|
||||
|
||||
@attributesSet: (attributes) ->
|
||||
|
||||
configure_attributes = App[ @.className ].configure_attributes
|
||||
attributesNew = []
|
||||
for localAttribute in configure_attributes
|
||||
found = false
|
||||
for attribute in attributes
|
||||
if attribute.name is localAttribute.name
|
||||
found = true
|
||||
break
|
||||
if !found
|
||||
attributesNew.push localAttribute
|
||||
for attribute in attributes
|
||||
App[@.className].attributes.push attribute.name
|
||||
attributesNew.push attribute
|
||||
App[ @.className ].configure_attributes = attributesNew
|
||||
|
||||
###
|
||||
|
||||
attributes = App.Model.attributesGet(optionalScreen, optionalAttributesList)
|
||||
|
||||
returns
|
||||
|
|
|
@ -6,7 +6,7 @@ class App.Job extends App.Model
|
|||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'timeplan', display: 'When should the job run?', tag: 'timer', null: true },
|
||||
{ name: 'condition', display: 'Conditions for effected objects', tag: 'ticket_selector', null: true },
|
||||
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true, notification: true },
|
||||
{ name: 'perform', display: 'Execute changes on objects', tag: 'ticket_perform_action', null: true, notification: true, ticket_delete: true },
|
||||
{ name: 'disable_notification', display: 'Disable Notifications', tag: 'boolean', default: true },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
|
|
|
@ -8,6 +8,7 @@ class App.Overview extends App.Model
|
|||
{ name: 'role_ids', display: 'Available for Role', tag: 'column_select', multiple: true, null: false, relation: 'Role', translate: true },
|
||||
{ name: 'user_ids', display: 'Available for User', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
||||
{ name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||
{ name: 'out_of_office', display: 'Only available for Users which are replacements for other users.', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||
{ name: 'condition', display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
|
||||
{ name: 'prio', display: 'Prio', readonly: 1 },
|
||||
{
|
||||
|
@ -72,4 +73,4 @@ Sie können auch individuelle Übersichten für einzelne Agenten oder agenten Gr
|
|||
'''
|
||||
|
||||
uiUrl: ->
|
||||
'#ticket/view/' + @link
|
||||
"#ticket/view/#{@link}"
|
||||
|
|
|
@ -6,7 +6,7 @@ class App.PostmasterFilter extends App.Model
|
|||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 250, 'null': false },
|
||||
{ name: 'channel', display: 'Channel', type: 'input', readonly: 1 },
|
||||
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match' },
|
||||
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match', note: 'You can use regular expression by using "regex:your_reg_exp".' },
|
||||
{ name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' },
|
||||
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
|
|
|
@ -53,8 +53,14 @@ class App.User extends App.Model
|
|||
cssClass += ' ' if cssClass
|
||||
cssClass += "size-#{ size }"
|
||||
|
||||
if @active is false
|
||||
cssClass += ' avatar--inactive'
|
||||
|
||||
if @isOutOfOffice()
|
||||
cssClass += ' avatar--vacation'
|
||||
|
||||
if placement
|
||||
placement = " data-placement='#{ placement }'"
|
||||
placement = " data-placement='#{placement}'"
|
||||
|
||||
if !avatar
|
||||
if type is 'personal'
|
||||
|
@ -104,6 +110,19 @@ class App.User extends App.Model
|
|||
vip: vip
|
||||
url: @imageUrl()
|
||||
|
||||
isOutOfOffice: ->
|
||||
return false if @out_of_office isnt true
|
||||
start_time = @out_of_office_start_at
|
||||
return false if !start_time
|
||||
end_time = @out_of_office_end_at
|
||||
return false if !end_time
|
||||
start_time = new Date(Date.parse(start_time))
|
||||
end_time = new Date(Date.parse(end_time))
|
||||
now = new Date((new Date).toDateString())
|
||||
if start_time <= now && end_time >= now
|
||||
return true
|
||||
false
|
||||
|
||||
imageUrl: ->
|
||||
return if !@image
|
||||
# set image url
|
||||
|
@ -237,3 +256,16 @@ class App.User extends App.Model
|
|||
break
|
||||
return access if access
|
||||
false
|
||||
|
||||
@outOfOfficeTextPlaceholder: ->
|
||||
today = new Date()
|
||||
outOfOfficeText = 'Christmas holiday'
|
||||
if today.getMonth() < 3
|
||||
outOfOfficeText = 'Easter holiday'
|
||||
else if today.getMonth() < 9
|
||||
outOfOfficeText = 'Summer holiday'
|
||||
outOfOfficeText
|
||||
|
||||
outOfOfficeText: ->
|
||||
return @preferences.out_of_office_text if !_.isEmpty(@preferences.out_of_office_text)
|
||||
App.User.outOfOfficeTextPlaceholder()
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
<h3><%- @T('Automatically show chat') %> (<%- @T('default') %>)</h3>
|
||||
<p><%- @T('The chat will show up once the connection to the server got established and if there is someone online to chat with.') %></p>
|
||||
|
||||
<pre><code class="language-html js-paramsBlock"><script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
||||
<pre><code class="language-html js-code"><script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
new ZammadChat({
|
||||
|
@ -153,7 +153,7 @@ $(function() {
|
|||
|
||||
<h3><%- @T('Manually open chat') %></h3>
|
||||
<p><%- @T('If you want to open the chat by the press of a button set the option §show§ to §false§ and add the class §open-zammad-chat§ to the button.') %></p>
|
||||
<pre><code class="language-html js-paramsBlock"><button class="open-zammad-chat">Chat with us</button>
|
||||
<pre><code class="language-html js-code"><button class="open-zammad-chat">Chat with us</button>
|
||||
|
||||
<script src="<%= @baseurl %>/assets/chat/chat.min.js"></script>
|
||||
<script>
|
||||
|
|
|
@ -126,6 +126,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3><%- @T('Requirements') %></h3>
|
||||
<p><%- @T("Zammad Forms requires jQuery. If you don't already use it on your website include it like this:") %></p>
|
||||
<pre><code class="language-html js-code"><script src="https://code.jquery.com/jquery-2.1.4.min.js"></script></code></pre>
|
||||
|
||||
<p><%- @T('You need to add the following Javascript code snippet to your web page') %>:</p>
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
<div class="chat-body js-body"></div>
|
||||
</div>
|
||||
<div class="chat-controls">
|
||||
<div class="chat-input form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div>
|
||||
<div class="chat-input">
|
||||
<div class="form-control form-control--small form-control--multiline js-customerChatInput" contenteditable="true"></div>
|
||||
</div>
|
||||
<div class="btn btn--primary btn--slim btn--small js-send"><%- @T('Send') %></div>
|
||||
</div>
|
|
@ -4,7 +4,7 @@
|
|||
<input type="checkbox" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>/>
|
||||
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||
<span class="label-text"><%= row.name %> <% if row.note: %>- <span class="help-text"><%= row.note %></span><% end %></span>
|
||||
<span class="label-text"><%= row.name %> <% if row.note: %>- <span class="help-text"><%- @T(row.note) %></span><% end %></span>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
|
@ -1,7 +1,7 @@
|
|||
<div class="<%= @attribute.class %>">
|
||||
<% for row in @attribute.options: %>
|
||||
<label>
|
||||
<input type="radio" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>> <%= row.name %> <% if row.note: %>- <%= row.note %><% end %>
|
||||
<input type="radio" value="<%= row.value %>" name="<%= @attribute.name %>" <%= row.checked %>> <%= row.name %> <% if row.note: %>- <%- @T(row.note) %><% end %>
|
||||
<%- @Icon('radio') %>
|
||||
<%- @Icon('radio-checked') %>
|
||||
</label>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="sidebar-content"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="tabsSidebar-tabs" style="margin-left: -<%- @scrollbarWidth %>px">
|
||||
<div class="tabsSidebar-tabs" style="<%- if @dir is 'rtl' then 'margin-right' else 'margin-left' %>: -<%- @scrollbarWidth %>px">
|
||||
<% for item in @items: %>
|
||||
<div class="tabsSidebar-tab" data-tab="<%= item.name %>">
|
||||
<%- @Icon(item.icon) %>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<input type="checkbox" value="<%= role.id %>" name="role_ids" <% if @rolesSelected[role.id]: %>checked<% end %>/>
|
||||
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||
<span class="label-text"><%= role.displayName() %> <% if role.note: %>- <span class="help-text"><%= role.note %></span><% end %></span>
|
||||
<span class="label-text"><%- @T(role.displayName()) %> <% if role.note: %>- <span class="help-text"><%- @T(role.note) %></span><% end %></span>
|
||||
</label>
|
||||
<% if role.permissions: %>
|
||||
<% for permission in role.permissions: %>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
</form>
|
||||
|
||||
<form class="setup wizard hide js-inbound">
|
||||
<div class="wizard-slide">
|
||||
<div class="wizard-slide wizard-slide--large">
|
||||
<h2><%- @T('Email Inbound') %></h2>
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<div class="wizard-slide vertical hide" data-slide="zendesk-import">
|
||||
<h2><%- @T('%s Migration', 'Zendesk') %></h2>
|
||||
<div class="alert alert--danger hide js-error" role="alert"></div>
|
||||
<div class="alert alert--info hide js-ticket-count-info" role="alert"><%- @T("There are more than 1000 tickets in the Zendesk system. Due to API rate limit restrictions we can't get the exact number of tickets yet and have to fetch them in batches of 1000. This might take some time, better grab a cup of coffee. The total number of tickets gets updated as soon as the currently known number is surpassed.") %></div>
|
||||
<div class="wizard-body flex vertical justified">
|
||||
<table class="progressTable">
|
||||
<tr class="js-group">
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
<div class="page-content">
|
||||
<% if @description: %>
|
||||
<% for item in @description: %>
|
||||
<p><%- @T(item[0], item[1], item[2]) %></p>
|
||||
<p><%- @T(item...) %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="js-form"></div>
|
||||
<div class="js-scriptSnipped"></div>
|
||||
<div class="js-log"></div>
|
||||
</div>
|
|
@ -0,0 +1,71 @@
|
|||
<div class="js-lastImport"></div>
|
||||
<div class="js-notConfigured">
|
||||
<p><%- @T('No %s configured.', 'Exchange') %></p>
|
||||
<button type="submit" class="btn btn--primary js-wizard"><%- @T('Configure') %></button>
|
||||
</div>
|
||||
<div class="js-summary hide">
|
||||
<h2><%- @T('Settings') %></h2>
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"><%- @T('Name') %>
|
||||
<th width="70%"><%- @T('Value') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Endpoint') %>
|
||||
<td class="settings-list-row-control"><%= @config.endpoint %>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('User') %>
|
||||
<td class="settings-list-row-control"><%= @config.user %>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Password') %>
|
||||
<td class="settings-list-row-control"><%= @M(@config.password) %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2><%- @T('Mapping') %></h2>
|
||||
|
||||
<h3><%- @T('Folders') %></h3>
|
||||
<% if _.isEmpty(@folders): %>
|
||||
<table class="settings-list settings-list--stretch settings-list--placeholder">
|
||||
<thead><tr><th><%- @T('No Entries') %>
|
||||
</table>
|
||||
<% else: %>
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%- @T('Folder') %>
|
||||
<% for folder_name in @folders: %>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%= folder_name %>
|
||||
<% end %>
|
||||
</thead>
|
||||
<tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<h3><%- @T('User') %></h3>
|
||||
<% if _.isEmpty(@config.attributes): %>
|
||||
<table class="settings-list settings-list--stretch settings-list--placeholder">
|
||||
<thead><tr><th><%- @T('No Entries') %>
|
||||
</table>
|
||||
<% else: %>
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%"><%- @T('Exchange') %>
|
||||
<th width="60%"><%- @T('Zammad') %>
|
||||
<% for key, value of @config.attributes: %>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%= key %>
|
||||
<td class="settings-list-row-control"><%= value %>
|
||||
<% end %>
|
||||
</thead>
|
||||
<tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-wizard"><%- @T('Change') %></button>
|
||||
</div>
|
|
@ -0,0 +1,54 @@
|
|||
<div class="box box--message">
|
||||
<h2><%- @T('Last sync') %></h2>
|
||||
<% if _.isEmpty(@job.started_at): %>
|
||||
<% if @job.result && @job.result.error: %>
|
||||
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
||||
<% else if @job.result && @job.result.info: %>
|
||||
<div class="alert alert--info" role="alert"><%- @T('Info: %s', @job.result.info) %></div>
|
||||
<% else: %>
|
||||
<p><%- @T('Job is waiting to get started...') %></p>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<% if @job.finished_at: %>
|
||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @Ttimestamp(@job.finished_at) %></p>
|
||||
<% if @job.result && @job.result.error: %>
|
||||
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
||||
<% else if @job.result && @job.result.info: %>
|
||||
<div class="alert alert--info" role="alert"><%- @T('Info: %s', @job.result.info) %></div>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<% if @job.result && @job.result.error: %>
|
||||
<p><%- @Ttimestamp(@job.started_at) %></p>
|
||||
<div class="alert alert--danger" role="alert"><%- @T('An error occurred: %s', @job.result.error) %></div>
|
||||
<% else if !@countDone: %>
|
||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Counting entries. This may take a while.') %></p>
|
||||
<% else: %>
|
||||
<p><%- @Ttimestamp(@job.started_at) %> - <%- @T('Running...') %></p>
|
||||
<div class="flex">
|
||||
<progress max="<%= @job.result.sum %>" value="<%= @countDone %>"></progress>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !_.isEmpty(@job.result) && @countDone: %>
|
||||
<ul>
|
||||
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @countDone %>/<%= @job.result.sum %>):
|
||||
<ul>
|
||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
||||
</ul>
|
||||
</li>
|
||||
<% if !_.isEmpty(@job.result.folders): %>
|
||||
<li><%- @T('%s folders', 'Exchange') %>:
|
||||
<ul>
|
||||
<% for folder, result of @job.result.folders: %>
|
||||
<li><%- folder %>: <%= result.created %> <%- @T('created') %>, <%= result.updated %> <%- @T('updated') %>, <%= result.unchanged %> <%- @T('untouched') %>, <%= result.failed %> <%- @T('failed') %>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% if @job.finished_at: %>
|
||||
<button type="submit" class="btn btn--primary js-start-sync"><%- @T('Start new') %></button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
<ul>
|
||||
<li><%- @T('%s user to %s user', 'Exchange', 'Zammad') %> (<%= @countDone %>):
|
||||
<ul>
|
||||
<li><%- @T('Users') %>: <%= @job.result.created %> <%- @T('created') %>, <%= @job.result.updated %> <%- @T('updated') %>, <%= @job.result.unchanged %> <%- @T('untouched') %>, <%= @job.result.skipped %> <%- @T('skipped') %>, <%= @job.result.failed %> <%- @T('failed') %>
|
||||
</ul>
|
||||
</li>
|
||||
<% if !_.isEmpty(@job.result.folders): %>
|
||||
<li><%- @T('%s folders', 'Exchange') %>:
|
||||
<ul>
|
||||
<% for folder, result of @job.result.folders: %>
|
||||
<li><%- folder %>: <%= result.created %> <%- @T('created') %>, <%= result.updated %> <%- @T('updated') %>, <%= result.unchanged %> <%- @T('untouched') %>, <%= result.failed %> <%- @T('failed') %>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
|
@ -0,0 +1,7 @@
|
|||
<tr class="js-entry">
|
||||
<td style="max-width: 240px" class="settings-list-control-cell js-exchangeAttribute">
|
||||
<td class="settings-list-control-cell js-userAttribute">
|
||||
<td class="settings-list-row-control">
|
||||
<div class="btn btn--text js-remove">
|
||||
<%- @Icon('trash') %> <%- @T('Remove') %>
|
||||
</div>
|
|
@ -0,0 +1,258 @@
|
|||
<div class="modal-dialog">
|
||||
|
||||
<form class="modal-content setup wizard js-discover">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"><%- @T('Name') %>
|
||||
<th width="70%"><%- @T('Value') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('User') %>
|
||||
<td class="settings-list-control-cell"><input type="text" name="user" class="form-control form-control--small js-user" value="" placeholder="" autocomplete="off" required>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Password') %>
|
||||
<td class="settings-list-control-cell"><input type="password" name="password" class="form-control form-control--small js-password" value="" placeholder="" autocomplete="new-password" required>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right js-submit"><%- @T('Connect') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="modal-content setup wizard hide js-connect">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<p class="wizard-loadingText">
|
||||
<span class="loading icon"></span> <%- @T('Connecting ...') %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer"></div>
|
||||
</form>
|
||||
|
||||
<form class="modal-content setup wizard hide js-bind">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"><%- @T('Name') %>
|
||||
<th width="70%"><%- @T('Value') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Endpoint') %>
|
||||
<td class="settings-list-control-cell"><input type="text" name="endpoint" class="form-control form-control--small" value="" placeholder="https://outlook.office365.com/EWS/Exchange.asmx" autocomplete="off" required>
|
||||
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('User') %>
|
||||
<td class="settings-list-control-cell"><input type="text" name="user" class="form-control form-control--small js-user" value="" placeholder="" autocomplete="off" required>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Password') %>
|
||||
<td class="settings-list-control-cell"><input type="password" name="password" class="form-control form-control--small js-password" value="" placeholder="" autocomplete="new-password" required>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right js-submit"><%- @T('Connect') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="modal-content setup wizard hide js-folders">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Folders') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
|
||||
<div class="column_select form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for="folders"><%- @T('Import %s', 'Folders') %></label>
|
||||
</div>
|
||||
<div class="controls js-foldersSelect">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<a class="btn btn--text btn--secondary js-goToSlide align-left" data-slide="js-bind"><%- @T('Go Back') %></a>
|
||||
</div>
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right js-submitTry is-disabled"><%- @T('Continue') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="modal-content setup wizard hide js-analyze">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<p class="wizard-loadingText">
|
||||
<span class="loading icon"></span> <%- @T('Analyzing structure...') %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer"></div>
|
||||
</form>
|
||||
|
||||
<div class="modal-content setup wizard hide js-mapping">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Mapping') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
|
||||
<h2><%- @T('User') %></h2>
|
||||
<form class="js-userMappingForm">
|
||||
<table class="settings-list js-userAttributeMap" style="width: 100%;">
|
||||
<colgroup>
|
||||
<col width="240">
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%- @T('%s Attribute', 'Exchange') %>
|
||||
<th><%- @T('%s Attribute', 'Zammad') %>
|
||||
<th><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control" colspan="3">
|
||||
<div class="btn btn--text btn--create js-add">
|
||||
<%- @Icon('plus-small') %> <%- @T('Add') %>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<a class="btn btn--text btn--secondary js-goToSlide align-left" data-slide="js-bind"><%- @T('Go Back') %></a>
|
||||
</div>
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right js-submitTry"><%- @T('Continue') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="modal-content setup wizard hide js-dry">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="js-preprogress">
|
||||
<p class="wizard-loadingText">
|
||||
<span class="loading icon"></span>
|
||||
<%- @T('Counting entries. This may take a while.') %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="js-analyzing hide">
|
||||
<p class="wizard-loadingText">
|
||||
<%- @T('Analyzing entries with given configuration...') %>
|
||||
</p>
|
||||
<div class="centered js-progress">
|
||||
<progress max="" value=""></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer"></div>
|
||||
</form>
|
||||
|
||||
<div class="modal-content setup wizard hide js-try">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %> <%- @T('Configuration') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
<p><%- @T('With your current configuration the following will happen') %>:</p>
|
||||
<div class="js-summary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<a class="btn btn--text btn--secondary js-goToSlide align-left" data-slide="js-mapping"><%- @T('Go Back') %></a>
|
||||
</div>
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right js-submitSave"><%- @T('Save configuration') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="modal-content setup wizard hide js-error">
|
||||
<div class="modal-header">
|
||||
<div class="modal-close js-close">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
<h1 class="modal-title"><%- @T('Exchange') %></h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wizard-body vertical justified">
|
||||
<div class="alert alert--danger hide" role="alert"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-rightFooter">
|
||||
<button class="btn btn--primary align-right"><%- @T('Cancel') %></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
25
app/assets/javascripts/app/views/integration/idoit.jst.eco
Normal file
25
app/assets/javascripts/app/views/integration/idoit.jst.eco
Normal file
|
@ -0,0 +1,25 @@
|
|||
<form>
|
||||
<h2><%- @T('Settings') %></h2>
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%"><%- @T('Name') %>
|
||||
<th width="80%"><%- @T('Value') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('API token') %> *
|
||||
<td class="settings-list-control-cell"><input type="text" class="form-control form-control--small" value="<%= @config.api_token %>" name="api_token">
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Endpoint') %> *
|
||||
<td class="settings-list-control-cell"><input type="text" class="form-control form-control--small" value="<%= @config.endpoint %>" placeholder="https://idoit.example.com/i-doit/" name="endpoint">
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('Client ID') %>
|
||||
<td class="settings-list-control-cell"><input type="text" class="form-control form-control--small" value="<%= @config.client_id %>" name="client_id">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
|
@ -0,0 +1,25 @@
|
|||
<hr>
|
||||
<% if _.isEmpty(@items): %>
|
||||
<%- @T('none') %>
|
||||
<% else: %>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th style="width: 30px"></th>
|
||||
<th style="width: 50px"><%- @T('ID') %></th>
|
||||
<th><%- @T('Name') %></th>
|
||||
<th><%- @T('Status') %></th>
|
||||
<th><%- @T('Link') %></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for item in @items: %>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="object_id" value="<%= item.id %>"/></td>
|
||||
<td><%= item.id %></td>
|
||||
<td><%= item.title %></td>
|
||||
<td><%= item.cmdb_status_title %></td>
|
||||
<td><a href="<%- item.link %>" target="_blank">i-doit</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
|
@ -0,0 +1,5 @@
|
|||
<form class="js-search flex horizontal">
|
||||
<div class="controls controls--select js-typeSelect"></div>
|
||||
<input type="text" name="title" value="" autocomplete="off" placeholder="<%- @Ti('Search') %>" class="form-control"/>
|
||||
</form>
|
||||
<form class="js-result"></form>
|
|
@ -51,6 +51,10 @@
|
|||
<label for="id1">Name</label>
|
||||
<input id="id1" class="form-control" type="text" placeholder="Text Input">
|
||||
</div>
|
||||
<div class="input form-group">
|
||||
<label for="id1">Name (readonly)</label>
|
||||
<input id="id1" class="form-control" type="text" placeholder="Text Input" readonly value="Sandor Clegane">
|
||||
</div>
|
||||
<div class="input form-group">
|
||||
<label for="id2">Password</label>
|
||||
<input id="id2" class="form-control" type="password" value="Password Input">
|
||||
|
|
|
@ -116,8 +116,8 @@
|
|||
</div>
|
||||
|
||||
<div class="searchfield">
|
||||
<%- @Icon('magnifier') %>
|
||||
<input class="js-search form-control" name="search" placeholder="Search for users" type="search">
|
||||
<%- @Icon('magnifier') %>
|
||||
</div>
|
||||
|
||||
<div class="userSearch horizontal">
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div class="formGroup-label">
|
||||
<label for="password"><%- @Ti('Password') %></label>
|
||||
</div>
|
||||
<input id="password" name="password" type="password" class="form-control"/>
|
||||
<input id="password" name="password" type="password" class="form-control" autocomplete="off"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<div class="page-header">
|
||||
<div class="page-header-title"><h1><%- @T('Out of Office') %></h1></div>
|
||||
</div>
|
||||
|
||||
<form class="action">
|
||||
<div class="action-flow action-flow--row">
|
||||
<div class="action-row">
|
||||
<div class="action-flow action-flow--noWrap">
|
||||
<h2><span class="action-form-status">
|
||||
<% if @localData.out_of_office is true: %>
|
||||
<%- @Icon('status', 'ok inline') %>
|
||||
<% else: %>
|
||||
<%- @Icon('status', 'error inline') %>
|
||||
<% end %></span> <input id="out_of_office_reason" name="out_of_office_text" class="form-control form-control--inline" type="text" placeholder="<%- @Ti('e. g.') %> <%- @Ti(@placeholder) %>" value="<% if !_.isEmpty(@localData.out_of_office_text): %><%= @localData.out_of_office_text %><% end %>"></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-block action-block--flex">
|
||||
<div class="label"><%- @T('From') %></div>
|
||||
<div class="form-group js-startDate"></div>
|
||||
</div>
|
||||
<div class="action-block action-block--flex">
|
||||
<div class="label"><%- @T('Till') %></div>
|
||||
<div class="form-group js-endDate"></div>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<label for="out_of_office_replacement"><%- @T('Replacement') %></label>
|
||||
<div class="dropdown js-recipientDropdown"></div>
|
||||
</div>
|
||||
<div class="action-controls">
|
||||
<div class="btn btn--danger js-disabled"><%- @Ti('Disable') %></div>
|
||||
<div class="btn btn--create js-enable"><%- @Ti('Enable') %></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -3,8 +3,8 @@
|
|||
<div class="detail-search">
|
||||
<div class="detail-search-header">
|
||||
<div class="searchfield">
|
||||
<%- @Icon('magnifier') %>
|
||||
<input class="js-search form-control<%= if !@query then ' is-empty' %>" name="query" placeholder="<%- @Ti('Find what you search. E. g. "search phrase"') %>" value="<%= @query %>" type="search" autocomplete="off">
|
||||
<%- @Icon('magnifier') %>
|
||||
<div class="empty-search js-emptySearch">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
<input type="text" name="proxy_password" value="<%= @proxy_password %>" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-text"><%- @T('No proxy for the following hosts.') %></p>
|
||||
<div class="horizontal end">
|
||||
<div class="form-item flex">
|
||||
<input type="text" name="proxy_no" value="<%= @proxy_no %>" placeholder="localhost,127.0.0.1" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="horizontal justify-end form-controls">
|
||||
<button class="btn btn js-test"><%- @T('Test Connection') %></button>
|
||||
<button class="btn btn--primary js-submit hide"><%- @T('Submit') %></button>
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
<div class="tabsSidebar-holder">
|
||||
<div class="scrollPageHeader tabsSidebar-sidebarSpacer" style="right: <%- @scrollbarWidth %>px">
|
||||
<div class="scrollPageHeader tabsSidebar-sidebarSpacer" style="<%- if @dir is 'rtl' then 'left' else 'right' %>: <%- @scrollbarWidth %>px">
|
||||
<small><%- @C('ticket_hook') %> <span class="ticket-number"><%- @ticket.number %></span></small>
|
||||
<div class="ticket-title"></div>
|
||||
<div class="highlighter"></div>
|
||||
<div class="overview-navigator"></div>
|
||||
<div class="js-ticketTitleContainer ticket-title"></div>
|
||||
<div class="js-highlighterContainer highlighter"></div>
|
||||
<div class="js-overviewNavigatorContainer overview-navigator"></div>
|
||||
</div>
|
||||
<div class="main no-padding flex tabsSidebar-sidebarSpacer tabsSidebar-tabsSpacer">
|
||||
<div class="ticketZoom">
|
||||
<div class="ticketZoom-controls">
|
||||
<div class="highlighter"></div>
|
||||
<div class="overview-navigator"></div>
|
||||
<div class="js-settingContainer"></div>
|
||||
<div class="spacer"></div>
|
||||
<div class="js-highlighterContainer highlighter"></div>
|
||||
<div class="js-overviewNavigatorContainer overview-navigator"></div>
|
||||
</div>
|
||||
<div class="ticketZoom-header">
|
||||
<div class="flex vertical center">
|
||||
<div class="js-avatar"></div>
|
||||
<div class="ticket-title"></div>
|
||||
<div class="ticket-meta"></div>
|
||||
<div class="js-ticketTitleContainer ticket-title"></div>
|
||||
<div class="js-ticketMetaContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-article"></div>
|
||||
|
|
|
@ -41,13 +41,19 @@
|
|||
<div class="formGroup-label">
|
||||
<label for=""><%- @T('To') %></label>
|
||||
</div>
|
||||
<div class="controls"><input id="" type="text" name="to" value="<%= @article.to %>" class="form-control js-mail-inputs" required="required"></div>
|
||||
<div class="controls"><input type="text" name="to" value="<%= @article.to %>" class="form-control js-mail-inputs" required="required"></div>
|
||||
</div>
|
||||
<div class="input form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for=""><%- @T('Cc') %></label>
|
||||
</div>
|
||||
<div class="controls"><input id="" type="text" name="cc" value="<%= @article.cc %>" class="form-control js-mail-inputs"></div>
|
||||
<div class="controls"><input type="text" name="cc" value="<%= @article.cc %>" class="form-control js-mail-inputs"></div>
|
||||
</div>
|
||||
<div class="input form-group">
|
||||
<div class="formGroup-label">
|
||||
<label for=""><%- @T('Subject') %></label>
|
||||
</div>
|
||||
<div class="controls"><input type="text" name="subject" value="<%= @article.subject %>" class="form-control js-mail-inputs2"></div>
|
||||
</div>
|
||||
|
||||
<div class="textBubble js-writeArea">
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="btn btn--action js-setting centered">
|
||||
<%- @Icon('cog', 'dropdown-icon') %>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<% for object in @objects: %>
|
||||
<div class="sidebar-block">
|
||||
<label class="horizontal">
|
||||
<%- @T(object.title) %>
|
||||
<div class="list-item-delete js-delete" data-object-id="<%= object.id %>" data-type="remove">
|
||||
<%- @Icon('diagonal-cross') %>
|
||||
</div>
|
||||
</label>
|
||||
<%- @T('ID') %>: <a href="<%- object.link %>" target="_blank"><%= object.id %><br></a>
|
||||
<%- @T('Status') %>: <%= object.cmdb_status_title %><br>
|
||||
<%- @T('Type') %>: <%= object.type_title %><br>
|
||||
</div>
|
||||
<% end %>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue