Added branding feature.
This commit is contained in:
parent
ca42df1ef6
commit
ac43efc60f
8 changed files with 267 additions and 60 deletions
|
@ -5,15 +5,30 @@ 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 )
|
||||
|
||||
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
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,42 +33,32 @@ 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' } },
|
||||
]
|
||||
|
||||
@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( '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' )
|
||||
|
|
36
app/assets/javascripts/app/views/settings/logo.jst.eco
Normal file
36
app/assets/javascripts/app/views/settings/logo.jst.eco
Normal 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>
|
|
@ -4811,7 +4811,7 @@ label + .wizard-buttonList {
|
|||
}
|
||||
}
|
||||
|
||||
.setup.wizard .logo-preview {
|
||||
.setup.wizard .logo-preview, .branding .logo-preview {
|
||||
display: block;
|
||||
height: 0;
|
||||
max-width: 200px;
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -3,8 +3,10 @@ Zammad::Application.routes.draw do
|
|||
|
||||
# base objects
|
||||
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
|
||||
|
||||
|
|
9
db/migrate/20150712000001_update_setting.rb
Normal file
9
db/migrate/20150712000001_update_setting.rb
Normal 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
|
41
db/seeds.rb
41
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: [
|
||||
|
@ -51,29 +51,10 @@ Setting.create_if_not_exists(
|
|||
state: 'Zammad',
|
||||
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(
|
||||
title: 'Organization',
|
||||
name: 'organization',
|
||||
area: 'System::Base',
|
||||
area: 'System::Branding',
|
||||
description: 'Will also be included in emails as an X-Header.',
|
||||
options: {
|
||||
form: [
|
||||
|
@ -88,6 +69,24 @@ Setting.create_if_not_exists(
|
|||
state: '',
|
||||
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(
|
||||
title: 'SystemID',
|
||||
|
|
Loading…
Reference in a new issue