Added branding feature.

This commit is contained in:
Martin Edenhofer 2015-07-12 03:32:40 +02:00
parent ca42df1ef6
commit ac43efc60f
8 changed files with 267 additions and 60 deletions

View file

@ -5,17 +5,32 @@ class App.SettingsArea extends App.Controller
# check authentication # check authentication
return if !@authenticate() return if !@authenticate()
App.Setting.bind 'refresh change', @render @load()
App.Setting.fetch()
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: => render: =>
settings = App.Setting.all() settings = App.Setting.search(
filter:
area: @area
)
html = $('<div></div>') html = $('<div></div>')
for setting in settings 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 ) item = new App.SettingsAreaItem( setting: setting )
html.append( item.el ) html.append( item.el )
@html html @html html
@ -55,6 +70,7 @@ class App.SettingsAreaItem extends App.Controller
update: (e) => update: (e) =>
e.preventDefault() e.preventDefault()
@formDisable(e)
params = @formParam(e.target) params = @formParam(e.target)
directValue = 0 directValue = 0
@ -78,6 +94,7 @@ class App.SettingsAreaItem extends App.Controller
ui = @ ui = @
@setting.save( @setting.save(
done: => done: =>
ui.formEnable(e)
App.Event.trigger 'notify', { App.Event.trigger 'notify', {
type: 'success' type: 'success'
@ -90,4 +107,95 @@ class App.SettingsAreaItem extends App.Controller
# login check # login check
App.Auth.loginCheck() 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 )

View file

@ -1,31 +1,31 @@
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 class System extends App.ControllerTabs
constructor: -> constructor: ->
super super
return if !@authenticate() return if !@authenticate()
@title 'System', true @title 'System', true
@tabs = [ @tabs = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Base' } }, { 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: 'Storage', 'target': 'storage', controller: App.SettingsArea, params: { area: 'System::Storage' } },
{ name: 'Geo Services', 'target': 'geo', controller: App.SettingsArea, params: { area: 'System::Geo' } }, { name: 'Geo Services', 'target': 'geo', controller: App.SettingsArea, params: { area: 'System::Geo' } },
{ name: 'Frontend', 'target': 'ui', controller: App.SettingsArea, params: { area: 'System::UI' } }, { name: 'Frontend', 'target': 'ui', controller: App.SettingsArea, params: { area: 'System::UI' } },
] ]
# render page
@render() @render()
class Security extends App.ControllerTabs class Security extends App.ControllerTabs
constructor: -> constructor: ->
super super
return if !@authenticate() return if !@authenticate()
@title 'Security', true @title 'Security', true
@tabs = [ @tabs = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Security::Base' } }, { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Security::Base' } },
# { name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } }, # { 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: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsArea, params: { area: 'Security::ThirdPartyAuthentication' } },
# { name: 'Session', 'target': 'session', controller: '' }, # { name: 'Session', 'target': 'session', controller: '' },
] ]
@render() @render()
class Import extends App.ControllerTabs class Import extends App.ControllerTabs
constructor: -> constructor: ->
super super
return if !@authenticate() return if !@authenticate()
@title 'Import', true @title 'Import', true
# import
@tabs = [ @tabs = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Import::Base' } }, { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Import::Base' } },
{ name: 'OTRS', 'target': 'otrs', controller: App.SettingsArea, params: { area: 'Import::OTRS' } }, { name: 'OTRS', 'target': 'otrs', controller: App.SettingsArea, params: { area: 'Import::OTRS' } },
] ]
@render() @render()
class Ticket extends App.ControllerTabs class Ticket extends App.ControllerTabs
constructor: -> constructor: ->
super super
return if !@authenticate() return if !@authenticate()
@title 'Ticket', true @title 'Ticket', true
# ticket
@tabs = [ @tabs = [
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Ticket::Base' } }, { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Ticket::Base' } },
{ name: 'Number', 'target': 'number', controller: App.SettingsArea, params: { area: 'Ticket::Number' } }, { name: 'Number', 'target': 'number', controller: App.SettingsArea, params: { area: 'Ticket::Number' } },
# { name: 'Sender Format', 'target': 'sender-format', controller: App.SettingsArea, params: { area: 'Ticket::SenderFormat' } }, # { name: 'Sender Format', 'target': 'sender-format', controller: App.SettingsArea, params: { area: 'Ticket::SenderFormat' } },
] ]
@render() @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( '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( '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( 'SettingTicket', { prio: 1600, parent: '#settings', name: 'Ticket', target: '#settings/ticket', controller: Ticket, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -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>

View file

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

View file

@ -6,6 +6,14 @@ class SettingsController < ApplicationController
# GET /settings # GET /settings
def index def index
return if deny_if_not_role(Z_ROLENAME_ADMIN) 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) model_index_render(Setting, params)
end end
@ -27,6 +35,57 @@ class SettingsController < ApplicationController
model_update_render(Setting, params) model_update_render(Setting, params)
end 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 # DELETE /settings/1
def destroy def destroy
return if deny_if_not_role(Z_ROLENAME_ADMIN) return if deny_if_not_role(Z_ROLENAME_ADMIN)

View file

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

View file

@ -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

View file

@ -36,7 +36,7 @@ Setting.create_if_not_exists(
Setting.create_if_not_exists( Setting.create_if_not_exists(
title: 'Product Name', title: 'Product Name',
name: '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.', description: 'Defines the name of the application, shown in the web interface, tabs and title bar of the web browser.',
options: { options: {
form: [ form: [
@ -51,29 +51,10 @@ Setting.create_if_not_exists(
state: 'Zammad', state: 'Zammad',
frontend: true 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.',
options: {
form: [
{
display: '',
null: false,
name: 'product_logo',
tag: 'input',
},
],
},
state: 'logo.svg',
frontend: true
)
Setting.create_if_not_exists( Setting.create_if_not_exists(
title: 'Organization', title: 'Organization',
name: 'organization', name: 'organization',
area: 'System::Base', area: 'System::Branding',
description: 'Will also be included in emails as an X-Header.', description: 'Will also be included in emails as an X-Header.',
options: { options: {
form: [ form: [
@ -88,6 +69,24 @@ Setting.create_if_not_exists(
state: '', state: '',
frontend: true frontend: true
) )
Setting.create_if_not_exists(
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: 'product_logo',
tag: 'input',
},
],
},
state: 'logo.svg',
frontend: true
)
Setting.create_if_not_exists( Setting.create_if_not_exists(
title: 'SystemID', title: 'SystemID',