diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/pages/appearances.scss new file mode 100644 index 00000000000..e2070f17c3b --- /dev/null +++ b/app/assets/stylesheets/pages/appearances.scss @@ -0,0 +1,11 @@ +.appearance-logo-preview { + max-width: 400px; + margin-bottom: 20px; +} + +.appearance-light-logo-preview { + background-color: $background-color; + max-width: 72px; + padding: 10px; + margin-bottom: 10px; +} diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb new file mode 100644 index 00000000000..26cf74e4849 --- /dev/null +++ b/app/controllers/admin/appearances_controller.rb @@ -0,0 +1,57 @@ +class Admin::AppearancesController < Admin::ApplicationController + before_action :set_appearance, except: :create + + def show + end + + def preview + end + + def create + @appearance = Appearance.new(appearance_params) + + if @appearance.save + redirect_to admin_appearances_path, notice: 'Appearance was successfully created.' + else + render action: 'show' + end + end + + def update + if @appearance.update(appearance_params) + redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.' + else + render action: 'show' + end + end + + def logo + @appearance.remove_logo! + + @appearance.save + + redirect_to admin_appearances_path, notice: 'Logo was succesfully removed.' + end + + def header_logos + @appearance.remove_header_logo! + @appearance.save + + redirect_to admin_appearances_path, notice: 'Header logo was succesfully removed.' + end + + private + + # Use callbacks to share common setup or constraints between actions. + def set_appearance + @appearance = Appearance.last || Appearance.new + end + + # Only allow a trusted parameter "white list" through. + def appearance_params + params.require(:appearance).permit( + :title, :description, :logo, :logo_cache, :header_logo, :header_logo_cache, + :updated_by + ) + end +end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 868b05929d7..509f4f412ca 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -55,14 +55,15 @@ class UploadsController < ApplicationController "user" => User, "project" => Project, "note" => Note, - "group" => Group + "group" => Group, + "appearance" => Appearance } upload_models[params[:model]] end def upload_mount - upload_mounts = %w(avatar attachment file) + upload_mounts = %w(avatar attachment file logo header_logo) if upload_mounts.include?(params[:mounted_as]) params[:mounted_as] diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index c5820bf4c50..e0abc3a2869 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -1,21 +1,33 @@ module AppearancesHelper - def brand_item - nil - end - def brand_title - 'GitLab Community Edition' + if brand_item && brand_item.title + brand_item.title + else + 'GitLab Community Edition' + end end def brand_image - nil + if brand_item.logo? + image_tag brand_item.logo + else + nil + end end def brand_text - nil + markdown(brand_item.description) + end + + def brand_item + @appearance ||= Appearance.first end def brand_header_logo - render 'shared/logo.svg' + if brand_item && brand_item.header_logo? + image_tag brand_item.header_logo + else + render 'shared/logo.svg' + end end end diff --git a/app/models/appearance.rb b/app/models/appearance.rb new file mode 100644 index 00000000000..4cf8dd9a8ce --- /dev/null +++ b/app/models/appearance.rb @@ -0,0 +1,9 @@ +class Appearance < ActiveRecord::Base + validates :title, presence: true + validates :description, presence: true + validates :logo, file_size: { maximum: 1.megabyte } + validates :header_logo, file_size: { maximum: 1.megabyte } + + mount_uploader :logo, AttachmentUploader + mount_uploader :header_logo, AttachmentUploader +end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml new file mode 100644 index 00000000000..6f325914d14 --- /dev/null +++ b/app/views/admin/appearances/_form.html.haml @@ -0,0 +1,58 @@ += form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| + - if @appearance.errors.any? + .alert.alert-danger + - @appearance.errors.full_messages.each do |msg| + %p= msg + + %fieldset.sign-in + %legend + Sign in/Sign up pages: + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control" + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_area :description, class: "form-control", rows: 10 + .hint + Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}. + .form-group + = f.label :logo, class: 'control-label' + .col-sm-10 + - if @appearance.logo? + = image_tag @appearance.logo_url, class: 'appearance-logo-preview' + - if @appearance.persisted? + %br + = link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + %hr + = f.hidden_field :logo_cache + = f.file_field :logo, class: "" + .hint + Maximum file size is 1MB. Pages are optimized for a 640x360 px logo. + + %fieldset.app_logo + %legend + Navigation bar: + .form-group + = f.label :header_logo, 'Header logo', class: 'control-label' + .col-sm-10 + - if @appearance.header_logo? + = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' + - if @appearance.persisted? + %br + = link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-logo" + %hr + = f.hidden_field :header_logo_cache + = f.file_field :header_logo, class: "" + .hint + Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo + + .form-actions + = f.submit 'Save', class: 'btn btn-save' + - if @appearance.persisted? + = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' + + - if @appearance.updated_at + %span.pull-right + Last edit #{time_ago_with_tooltip(@appearance.updated_at)} diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml new file mode 100644 index 00000000000..dd4a64e80bc --- /dev/null +++ b/app/views/admin/appearances/preview.html.haml @@ -0,0 +1,29 @@ +- page_title "Preview | Appearance" +%h3.page-title + Appearance settings - Preview +%hr + +.ui-box + .title + Sign-in page + %div + .login-page + .container + .content + .login-title + %h1= brand_title + %hr + .container + .content + .row + .col-sm-7 + .brand-image + = brand_image + .brand_text + = brand_text + .col-sm-4 + .login-box + %h3.page-title Sign in + = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" + = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" + = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml new file mode 100644 index 00000000000..089e8e4cb7a --- /dev/null +++ b/app/views/admin/appearances/show.html.haml @@ -0,0 +1,7 @@ +- page_title "Appearance" +%h3.page-title + Appearance settings +%p.light + You can modify the look and feel of GitLab here + += render 'form' diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index ac1d5429382..280a1b93729 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -56,6 +56,11 @@ = icon('cog fw') %span Background Jobs + = nav_link(controller: :appearances) do + = link_to admin_appearances_path, title: 'Appearances' do + = icon('image') + %span + Appearance = nav_link(controller: :applications) do = link_to admin_applications_path, title: 'Applications' do diff --git a/config/routes.rb b/config/routes.rb index 1485b64da19..a2acf170a6b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -156,6 +156,11 @@ Rails.application.routes.draw do to: "uploads#show", constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } + # Project markdown uploads get ":namespace_id/:project_id/:secret/:filename", to: "projects/uploads#show", @@ -253,6 +258,14 @@ Rails.application.routes.draw do end end + resource :appearances, path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + resource :application_settings, only: [:show, :update] do resources :services put :reset_runners_token diff --git a/db/migrate/20160222153918_create_appearances_ce.rb b/db/migrate/20160222153918_create_appearances_ce.rb new file mode 100644 index 00000000000..5e66d5094bd --- /dev/null +++ b/db/migrate/20160222153918_create_appearances_ce.rb @@ -0,0 +1,14 @@ +class CreateAppearancesCE < ActiveRecord::Migration + def change + unless table_exists?(:appearances) + create_table :appearances do |t| + t.string :title + t.text :description + t.string :header_logo + t.string :logo + + t.timestamps null: false + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4708c29d9ae..53a941d30de 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160220123949) do +ActiveRecord::Schema.define(version: 20160222153918) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -24,6 +24,15 @@ ActiveRecord::Schema.define(version: 20160220123949) do t.datetime "updated_at" end + create_table "appearances", force: :cascade do |t| + t.string "title" + t.text "description" + t.string "header_logo" + t.string "logo" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "application_settings", force: :cascade do |t| t.integer "default_projects_limit" t.boolean "signup_enabled" diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md new file mode 100644 index 00000000000..d4d9f5f7b5e --- /dev/null +++ b/doc/customization/branded_login_page.md @@ -0,0 +1,19 @@ +# Changing the appearance of the login page + +GitLab Community Edition offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page. + +By default, the page shows the GitLab logo and description. + +![default_login_page](branded_login_page/default_login_page.png) + +## Changing the appearance of the login page + +Navigate to the **Admin** area and go to the **Appearance** page. + +Fill in the required details like Title, Description and upload the company logo. + +![appearance](branded_login_page/appearance.png) + +After saving the page, your GitLab login page will have the details you filled in: + +![company_login_page](branded_login_page/custom_sign_in.png) diff --git a/doc/customization/branded_login_page/appearance.png b/doc/customization/branded_login_page/appearance.png new file mode 100644 index 00000000000..6bce1f0a287 Binary files /dev/null and b/doc/customization/branded_login_page/appearance.png differ diff --git a/doc/customization/branded_login_page/custom_sign_in.png b/doc/customization/branded_login_page/custom_sign_in.png new file mode 100644 index 00000000000..d6020b029a2 Binary files /dev/null and b/doc/customization/branded_login_page/custom_sign_in.png differ diff --git a/doc/customization/branded_login_page/default_login_page.png b/doc/customization/branded_login_page/default_login_page.png new file mode 100644 index 00000000000..795c7954d8e Binary files /dev/null and b/doc/customization/branded_login_page/default_login_page.png differ diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md index e993230bb88..a0cb234bea0 100644 --- a/doc/customization/welcome_message.md +++ b/doc/customization/welcome_message.md @@ -1,12 +1,12 @@ -# Customize the complete sign-in page (GitLab Enterprise Edition only) +# Customize the complete sign-in page -Please see [Branded login page](http://doc.gitlab.com/ee/customization/branded_login_page.html) +Please see [Branded login page](branded_login_page.md) # Add a welcome message to the sign-in page (GitLab Community Edition) It is possible to add a markdown-formatted welcome message to your GitLab sign-in page. Users of GitLab Enterprise Edition should use the [branded login -page feature](/ee/customization/branded_login_page.html) instead. +page feature](branded_login_page.md) instead. The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI. -Admin area > Settings \ No newline at end of file +Admin area > Settings diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature new file mode 100644 index 00000000000..5c1dd7531c1 --- /dev/null +++ b/features/admin/appearance.feature @@ -0,0 +1,37 @@ +Feature: Admin Appearance + Scenario: Create new appearance + Given I sign in as an admin + And I visit admin appearance page + When submit form with new appearance + Then I should be redirected to admin appearance page + And I should see newly created appearance + + Scenario: Preview appearance + Given application has custom appearance + And I sign in as an admin + When I visit admin appearance page + And I click preview button + Then I should see a customized appearance + + Scenario: Custom sign-in page + Given application has custom appearance + When I visit login page + Then I should see a customized appearance + + Scenario: Appearance logo + Given application has custom appearance + And I sign in as an admin + And I visit admin appearance page + When I attach a logo + Then I should see a logo + And I remove the logo + Then I should see logo removed + + Scenario: Header logos + Given application has custom appearance + And I sign in as an admin + And I visit admin appearance page + When I attach header logos + Then I should see header logos + And I remove the header logos + Then I should see header logos removed diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb new file mode 100644 index 00000000000..0d1be46d11d --- /dev/null +++ b/features/steps/admin/appearance.rb @@ -0,0 +1,72 @@ +class Spinach::Features::AdminAppearance < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + + step 'submit form with new appearance' do + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + click_button 'Save' + end + + step 'I should be redirected to admin appearance page' do + expect(current_path).to eq admin_appearances_path + expect(page).to have_content 'Appearance settings' + end + + step 'I should see newly created appearance' do + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_content 'Last edit' + end + + step 'I click preview button' do + click_link "Preview" + end + + step 'application has custom appearance' do + create(:appearance) + end + + step 'I should see a customized appearance' do + expect(page).to have_content appearance.title + expect(page).to have_content appearance.description + end + + step 'I attach a logo' do + attach_file(:appearance_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) + click_button 'Save' + end + + step 'I attach header logos' do + attach_file(:appearance_header_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) + click_button 'Save' + end + + step 'I should see a logo' do + expect(page).to have_xpath('//img[@src="/uploads/appearance/logo/1/dk.png"]') + end + + step 'I should see header logos' do + expect(page).to have_xpath('//img[@src="/uploads/appearance/header_logo/1/dk.png"]') + end + + step 'I remove the logo' do + click_link 'Remove logo' + end + + step 'I remove the header logos' do + click_link 'Remove header logo' + end + + step 'I should see logo removed' do + expect(page).not_to have_xpath('//img[@src="/uploads/appearance/logo/1/gitlab_logo.png"]') + end + + step 'I should see header logos removed' do + expect(page).not_to have_xpath('//img[@src="/uploads/appearance/header_logo/1/header_logo_light.png"]') + end + + def appearance + Appearance.last + end +end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 0f9835e7356..6432a786bfc 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -7,6 +7,10 @@ module SharedPaths visit new_project_path end + step 'I visit login page' do + visit new_user_session_path + end + # ---------------------------------------- # User # ---------------------------------------- @@ -187,6 +191,10 @@ module SharedPaths visit admin_groups_path end + step 'I visit admin appearance page' do + visit admin_appearances_path + end + step 'I visit admin teams page' do visit admin_teams_path end diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb new file mode 100644 index 00000000000..cf2a2b76bcb --- /dev/null +++ b/spec/factories/appearances.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :appearance do + title "MepMep" + description "This is my Community Edition instance" + end +end diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb new file mode 100644 index 00000000000..c5658bd26e1 --- /dev/null +++ b/spec/models/appearance_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe Appearance, type: :model do + subject { create(:appearance) } + + it { is_expected.to be_valid } + + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:description) } +end