Browse Source

Added branding feature.

Martin Edenhofer 9 years ago
parent
commit
ac43efc60f

+ 113 - 5
app/assets/javascripts/app/controllers/_settings/area.js.coffee

@@ -5,17 +5,32 @@ class App.SettingsArea extends App.Controller
     # check authentication
     return if !@authenticate()
 
-    App.Setting.bind 'refresh change', @render
-    App.Setting.fetch()
+    @load()
+
+  load: ->
+    @ajax(
+      id:    "setting_area_#{@area}"
+      type:  'GET'
+      url:   "#{@apiPath}/settings/area/#{@area}"
+      processData: true
+      success: (data, status, xhr) =>
+        App.Collection.load( localStorage: false, type: 'Setting', data: data )
+        @render()
+    )
 
   render: =>
-    settings = App.Setting.all()
+    settings = App.Setting.search(
+      filter:
+        area: @area
+    )
 
     html = $('<div></div>')
     for setting in settings
-      if setting.area is @area
+      if setting.name is 'product_logo'
+        item = new App.SettingsAreaLogo( setting: setting )
+      else
         item = new App.SettingsAreaItem( setting: setting )
-        html.append( item.el )
+      html.append( item.el )
 
     @html html
 
@@ -55,6 +70,7 @@ class App.SettingsAreaItem extends App.Controller
 
   update: (e) =>
     e.preventDefault()
+    @formDisable(e)
     params = @formParam(e.target)
 
     directValue = 0
@@ -78,6 +94,7 @@ class App.SettingsAreaItem extends App.Controller
     ui = @
     @setting.save(
       done: =>
+        ui.formEnable(e)
 
         App.Event.trigger 'notify', {
           type:    'success'
@@ -90,4 +107,95 @@ class App.SettingsAreaItem extends App.Controller
 
         # login check
         App.Auth.loginCheck()
+      fail: =>
+        ui.formEnable(e)
     )
+
+class App.SettingsAreaLogo extends App.Controller
+  elements:
+    '.logo-preview': 'logoPreview'
+
+  events:
+    'submit form':       'submit'
+    'change .js-upload': 'onLogoPick'
+
+  constructor: ->
+    super
+    @render()
+
+  render: ->
+    logoFile = App.Config.get('product_logo')
+    logoUrl  = App.Config.get('image_path') + "/#{logoFile}"
+    @html App.view('settings/logo')(
+      setting: @setting
+      logoUrl: logoUrl
+    )
+
+  onLogoPick: (event) =>
+    reader = new FileReader()
+
+    reader.onload = (e) =>
+      @logoPreview.attr('src', e.target.result)
+
+    file = event.target.files[0]
+
+    # if no file is given, about in file upload was used
+    if !file
+      return
+
+    maxSiteInMb = 8
+    if file.size && file.size > 1024 * 1024 * maxSiteInMb
+      #@showAlert( 'logo', App.i18n.translateInline( 'File too big, max. %s MB allowed.', maxSiteInMb ) )
+      @logoPreview.attr( 'src', '' )
+      return
+
+    reader.readAsDataURL(file)
+
+  submit: (e) =>
+    e.preventDefault()
+    @formDisable(e)
+
+    # get params
+    @params = @formParam(e.target)
+
+    # add logo
+    @params.logo = @logoPreview.attr('src')
+
+    store = (logoResizeDataUrl) =>
+
+      # store image
+      @params.logo_resize = logoResizeDataUrl
+      @ajax(
+        id:          "setting_image_#{@setting.id}"
+        type:        'PUT'
+        url:         "#{@apiPath}/settings/image/#{@setting.id}"
+        data:        JSON.stringify(@params)
+        processData: true
+        success:     (data, status, xhr) =>
+          @formEnable(e)
+          if data.result is 'ok'
+            @formEnable(e)
+
+            App.Event.trigger 'notify', {
+              type:    'success'
+              msg:     App.i18n.translateContent('Update successful!')
+              timeout: 2000
+            }
+            @render()
+
+            App.Event.trigger( 'ui:rerender' )
+            for key, value of data.settings
+              App.Config.set( key, value )
+          else
+            App.Event.trigger 'notify', {
+              type:    'error'
+              msg:     App.i18n.translateContent(data.message)
+              timeout: 2000
+            }
+
+        fail: =>
+          @formEnable(e)
+      )
+
+    # add resized image
+    App.ImageService.resizeForAvatar( @params.logo, @logoPreview.width(), @logoPreview.height(), store )

+ 16 - 26
app/assets/javascripts/app/controllers/settings.js.coffee

@@ -1,31 +1,31 @@
-class System extends App.ControllerTabs
+class Branding extends App.ControllerTabs
   constructor: ->
     super
-
     return if !@authenticate()
+    @title 'Branding', true
+    @tabs = [
+      { name: 'Base',         'target': 'base',     controller: App.SettingsArea, params: { area: 'System::Branding' } },
+    ]
+    @render()
 
+class System extends App.ControllerTabs
+  constructor: ->
+    super
+    return if !@authenticate()
     @title 'System', true
-
     @tabs = [
       { name: 'Base',         'target': 'base',     controller: App.SettingsArea, params: { area: 'System::Base' } },
-  #    { name: 'Language', 'target': 'language',  controller: App.SettingsSystem, params: { area: 'System::Language' } },
-  #    { name: 'Log',      'target': 'log',       controller: App.SettingsSystem, params: { area: 'System::Log' } },
       { name: 'Storage',      'target': 'storage',  controller: App.SettingsArea, params: { area: 'System::Storage' } },
       { name: 'Geo Services', 'target': 'geo',      controller: App.SettingsArea, params: { area: 'System::Geo' } },
       { name: 'Frontend',     'target': 'ui',       controller: App.SettingsArea, params: { area: 'System::UI' } },
     ]
-
-    # render page
     @render()
 
 class Security extends App.ControllerTabs
   constructor: ->
     super
-
     return if !@authenticate()
-
     @title 'Security', true
-
     @tabs = [
       { name: 'Base',                     'target': 'base',             controller: App.SettingsArea, params: { area: 'Security::Base' } },
 #       { name: 'Authentication',           'target': 'auth',             controller: App.SettingsArea, params: { area: 'Security::Authentication' } },
@@ -33,44 +33,34 @@ class Security extends App.ControllerTabs
       { name: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsArea, params: { area: 'Security::ThirdPartyAuthentication' } },
 #       { name: 'Session',        'target': 'session',   controller: '' },
     ]
-
     @render()
 
 class Import extends App.ControllerTabs
   constructor: ->
     super
-
     return if !@authenticate()
-
     @title 'Import', true
-
-    # import
     @tabs = [
-      { name: 'Base',           'target': 'base',          controller: App.SettingsArea, params: { area: 'Import::Base' } },
-      { name: 'OTRS',           'target': 'otrs',          controller: App.SettingsArea, params: { area: 'Import::OTRS' } },
+      { name: 'Base',         'target': 'base',     controller: App.SettingsArea, params: { area: 'Import::Base' } },
+      { name: 'OTRS',         'target': 'otrs',     controller: App.SettingsArea, params: { area: 'Import::OTRS' } },
     ]
-
     @render()
 
 class Ticket extends App.ControllerTabs
   constructor: ->
     super
-
     return if !@authenticate()
-
     @title 'Ticket', true
-
-    # ticket
     @tabs = [
       { name: 'Base',           'target': 'base',          controller: App.SettingsArea, params: { area: 'Ticket::Base' } },
       { name: 'Number',         'target': 'number',        controller: App.SettingsArea, params: { area: 'Ticket::Number' } },
 #      { name: 'Sender Format',  'target': 'sender-format', controller: App.SettingsArea, params: { area: 'Ticket::SenderFormat' } },
     ]
-
     @render()
 
-App.Config.set( 'SettingSystem',    { prio: 1400, parent: '#settings', name: 'System', target: '#settings/system', controller: System, role: ['Admin'] }, 'NavBarAdmin' )
+App.Config.set( 'SettingBranding',  { prio: 1200, parent: '#settings', name: 'Branding', target: '#settings/branding', controller: Branding, role: ['Admin'] }, 'NavBarAdmin' )
+App.Config.set( 'SettingSystem',    { prio: 1400, parent: '#settings', name: 'System',   target: '#settings/system', controller: System, role: ['Admin'] }, 'NavBarAdmin' )
 App.Config.set( 'SettingSecurity',  { prio: 1500, parent: '#settings', name: 'Security', target: '#settings/security', controller: Security, role: ['Admin'] }, 'NavBarAdmin' )
-App.Config.set( 'SettingImport',    { prio: 1550, parent: '#settings', name: 'Import', target: '#settings/import', controller: Import, role: ['Admin'] }, 'NavBarAdmin' )
-App.Config.set( 'SettingTicket',    { prio: 1600, parent: '#settings', name: 'Ticket', target: '#settings/ticket', controller: Ticket, role: ['Admin'] }, 'NavBarAdmin' )
+App.Config.set( 'SettingImport',    { prio: 1550, parent: '#settings', name: 'Import',   target: '#settings/import', controller: Import, role: ['Admin'] }, 'NavBarAdmin' )
+App.Config.set( 'SettingTicket',    { prio: 1600, parent: '#settings', name: 'Ticket',   target: '#settings/ticket', controller: Ticket, role: ['Admin'] }, 'NavBarAdmin' )
 

+ 36 - 0
app/assets/javascripts/app/views/settings/logo.jst.eco

@@ -0,0 +1,36 @@
+<form class="settings-entry" id="<%= @setting.name %>">
+  <h2><%- @T( @setting.title ) %></h2>
+  <p><%- @T( @setting.description ) %></p>
+
+  <div class="login branding centered darkBackground">
+    <div class="hero-unit">
+      <img class="logo-preview" src="<%= @logoUrl %>">
+      <div class="logo-preview-placeholder"><%- @T('Your Logo') %></div>
+      <div class="centered">
+        <div class="btn btn--success fileUpload"><%- @T('Change') %><input type="file" class="js-upload" name="logo" accept="image/*"></div>
+      </div>
+
+      <div class="form-group">
+        <label for="username"><%- @Ti( 'Username / email' ) %></label>
+        <input id="username" name="username" type="text" class="form-control" value="<%= @S('login') %>" autocapitalize="off" disabled="disabled"/>
+      </div>
+
+      <div class="form-group">
+        <label for="password"><%- @Ti( 'Password' ) %></label>
+        <input id="password" name="password" type="password" class="form-control" value="some_pass" disabled="disabled"/>
+      </div>
+
+      <div class="form-group">
+        <label><input name="remember_me" value="1" type="checkbox" disabled="disabled"/> <%- @T( 'Remember me' ) %></label>
+      </div>
+
+      <div class="form-controls">
+        <button class="btn btn--primary" type="submit" disabled="disabled"><%- @T( 'Sign in' ) %></button>
+      </div>
+    </div>
+  </div>
+
+  <div class="horizontal end">
+    <button type="submit" class="btn btn--primary"><%- @T( 'Submit' ) %></button>
+  </div>
+</form>

+ 7 - 3
app/assets/stylesheets/zammad.css.scss

@@ -4811,12 +4811,12 @@ label + .wizard-buttonList {
   }
 }
 
-.setup.wizard .logo-preview {
+.setup.wizard .logo-preview, .branding .logo-preview {
   display: block;
   height: 0;
   max-width: 200px;
   max-height: 100px;
-  
+
   &[src=""] {
     visibility: hidden;
   }
@@ -4832,7 +4832,7 @@ label + .wizard-buttonList {
   }
 }
 
-.setup.wizard .logo-preview:not([src=""]) {
+.setup.wizard .logo-preview:not([src=""]), .branding .logo-preview:not([src=""]) {
   margin: 0 auto 15px;
   height: auto;
 
@@ -4845,6 +4845,10 @@ label + .wizard-buttonList {
   margin-top: 15px;
 }
 
+.branding.login {
+  padding: 24px 24px 0px;
+}
+
 .import.wizard .wizard-slide {
   height: 300px;
 }

+ 59 - 0
app/controllers/settings_controller.rb

@@ -6,6 +6,14 @@ class SettingsController < ApplicationController
   # GET /settings
   def index
     return if deny_if_not_role(Z_ROLENAME_ADMIN)
+
+    # only serve requested items
+    if params[:area]
+      model_index_render_result( Setting.where(area: params[:area]) )
+      return
+    end
+
+    # serve all items
     model_index_render(Setting, params)
   end
 
@@ -27,6 +35,57 @@ class SettingsController < ApplicationController
     model_update_render(Setting, params)
   end
 
+  # PUT /settings/image/:id
+  def update_image
+
+    if !params[:logo]
+      render json: {
+        result: 'invalid',
+        message: 'Need logo param',
+      }
+      return
+    end
+
+    # validate image
+    if params[:logo] !~ /^data:image/i
+      render json: {
+        result: 'invalid',
+        message: 'Invalid payload, need data:image in logo param',
+      }
+      return
+    end
+
+    # process image
+    file = StaticAssets.data_url_attributes(params[:logo])
+    if !file[:content] || !file[:mime_type]
+      render json: {
+        result: 'invalid',
+        message: 'Unable to process image upload.',
+      }
+      return
+    end
+
+    # store image 1:1
+    StaticAssets.store_raw(file[:content], file[:mime_type])
+
+    # store resized image 1:1
+    setting = Setting.find_by(name: 'product_logo')
+    if params[:logo_resize] && params[:logo_resize] =~ /^data:image/i
+
+      # data:image/png;base64
+      file = StaticAssets.data_url_attributes( params[:logo_resize] )
+
+      # store image 1:1
+      setting.state = StaticAssets.store( file[:content], file[:mime_type] )
+      setting.save
+    end
+
+    render json: {
+      result: 'ok',
+      settings: [setting],
+    }
+  end
+
   # DELETE /settings/1
   def destroy
     return if deny_if_not_role(Z_ROLENAME_ADMIN)

+ 7 - 5
config/routes/setting.rb

@@ -2,10 +2,12 @@ Zammad::Application.routes.draw do
   api_path = Rails.configuration.api_path
 
   # base objects
-  match api_path + '/settings',               to: 'settings#index',   via: :get
-  match api_path + '/settings/:id',           to: 'settings#show',    via: :get
-  match api_path + '/settings',               to: 'settings#create',  via: :post
-  match api_path + '/settings/:id',           to: 'settings#update',  via: :put
-  match api_path + '/settings/:id',           to: 'settings#destroy', via: :delete
+  match api_path + '/settings',               to: 'settings#index',         via: :get
+  match api_path + '/settings/area/:area',    to: 'settings#index',         via: :get
+  match api_path + '/settings/:id',           to: 'settings#show',          via: :get
+  match api_path + '/settings',               to: 'settings#create',        via: :post
+  match api_path + '/settings/image/:id',     to: 'settings#update_image',  via: :put
+  match api_path + '/settings/:id',           to: 'settings#update',        via: :put
+  match api_path + '/settings/:id',           to: 'settings#destroy',       via: :delete
 
 end

+ 9 - 0
db/migrate/20150712000001_update_setting.rb

@@ -0,0 +1,9 @@
+class UpdateSetting < ActiveRecord::Migration
+  def up
+    %w(product_name product_logo organization).each {|setting_name|
+      setting = Setting.find_by(name: setting_name)
+      setting.area = 'System::Branding'
+      setting.save
+    }
+  end
+end

+ 13 - 14
db/seeds.rb

@@ -36,7 +36,7 @@ Setting.create_if_not_exists(
 Setting.create_if_not_exists(
   title: 'Product Name',
   name: 'product_name',
-  area: 'System::Base',
+  area: 'System::Branding',
   description: 'Defines the name of the application, shown in the web interface, tabs and title bar of the web browser.',
   options: {
     form: [
@@ -52,40 +52,39 @@ Setting.create_if_not_exists(
   frontend: true
 )
 Setting.create_if_not_exists(
-  title: 'Logo',
-  name: 'product_logo',
-  area: 'System::CI',
-  description: 'Defines the logo of the application, shown in the web interface.',
+  title: 'Organization',
+  name: 'organization',
+  area: 'System::Branding',
+  description: 'Will also be included in emails as an X-Header.',
   options: {
     form: [
       {
         display: '',
         null: false,
-        name: 'product_logo',
+        name: 'organization',
         tag: 'input',
       },
     ],
   },
-  state: 'logo.svg',
+  state: '',
   frontend: true
 )
-
 Setting.create_if_not_exists(
-  title: 'Organization',
-  name: 'organization',
-  area: 'System::Base',
-  description: 'Will also be included in emails as an X-Header.',
+  title: 'Logo',
+  name: 'product_logo',
+  area: 'System::Branding',
+  description: 'Defines the logo of the application, shown in the web interface.',
   options: {
     form: [
       {
         display: '',
         null: false,
-        name: 'organization',
+        name: 'product_logo',
         tag: 'input',
       },
     ],
   },
-  state: '',
+  state: 'logo.svg',
   frontend: true
 )