#465: added pundit gem, wip...

This commit is contained in:
Vokhmin Alexey V 2015-03-13 01:43:13 +03:00
parent f5a68ed4ae
commit 4fec2213c8
38 changed files with 435 additions and 93 deletions

View File

@ -14,8 +14,7 @@ gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
gem 'omniauth-github'
# gem 'omniauth-openid', '~> 1.0.1'
# gem 'cancan', '1.6.10'
gem 'cancan', git: 'git://github.com/rosa-abf/cancan.git', tag: '1.6.10-abf'
gem 'pundit'
gem 'ancestry'
gem 'paperclip'

View File

@ -26,13 +26,6 @@ GIT
ransack (~> 1.3)
sass-rails
GIT
remote: git://github.com/rosa-abf/cancan.git
revision: fe1089b70c08d3ed11bac4f8e69ecb3d1d9adc29
tag: 1.6.10-abf
specs:
cancan (1.6.10)
GIT
remote: git://github.com/rosa-abf/grack.git
revision: 020be3fef3fb308b9d214252522aa5945bf6584a
@ -349,6 +342,8 @@ GEM
activemodel (>= 4.0.1, < 5.0)
puma (2.11.1)
rack (>= 1.1, < 2.0)
pundit (0.3.0)
activesupport (>= 3.0.0)
pygments.rb (0.6.2)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.2.0)
@ -590,7 +585,6 @@ DEPENDENCIES
better_errors
binding_of_caller
bootstrap-sass
cancan!
cape
capistrano
capistrano_colors
@ -637,6 +631,7 @@ DEPENDENCIES
pg
protected_attributes
puma
pundit
rack-throttle (~> 0.3.0)
rack-utf8_sanitizer
rails (= 4.1.9)

View File

@ -4,7 +4,7 @@ class Api::V1::BaseController < ApplicationController
helper_method :member_path
rescue_from CanCan::AccessDenied do |exception|
rescue_from Pundit::NotAuthorizedError do |exception|
respond_to do |format|
format.json { render json: {message: t('flash.exception_message')}.to_json, status: 403 }
format.csv { render text: t('flash.exception_message'), status: 403 }

View File

@ -12,7 +12,7 @@ class Api::V1::ProjectsController < Api::V1::BaseController
end
def get_id
if @project = Project.find_by_owner_and_name(params[:owner], params[:name])
if @project = Project.find_by_owner_and_name_cached(params[:owner], params[:name])
authorize! :show, @project
else
raise ActiveRecord::RecordNotFound

View File

@ -40,7 +40,7 @@ class Api::V1::RepositoriesController < Api::V1::BaseController
key, now = [@repository.platform.id, :repository_packages], Time.zone.now
last_request = Rails.cache.read(key)
if last_request.present? && last_request + 15.minutes > now
raise CanCan::AccessDenied
raise Pundit::NotAuthorizedError
else
Rails.cache.write(key, now, expires_at: 15.minutes)

View File

@ -1,4 +1,7 @@
class ApplicationController < ActionController::Base
include StrongParams
include Pundit
AIRBRAKE_IGNORE = [
ActionController::InvalidAuthenticityToken,
AbstractController::ActionNotFound
@ -14,6 +17,7 @@ class ApplicationController < ActionController::Base
before_action :set_locale
before_action -> { EventLog.current_controller = self },
only: [:create, :destroy, :open_id, :cancel, :publish, :change_visibility] # :update
before_action :banned?
after_action -> { EventLog.current_controller = nil }
helper_method :get_owner
@ -27,7 +31,7 @@ class ApplicationController < ActionController::Base
AbstractController::ActionNotFound, with: :render_404
end
rescue_from CanCan::AccessDenied do |exception|
rescue_from Pundit::NotAuthorizedError do |exception|
redirect_to forbidden_url, alert: t("flash.exception_message")
end
@ -40,6 +44,16 @@ class ApplicationController < ActionController::Base
protected
# Disables access to site for banned users
def banned?
authorize :user, :banned?
# if user_signed_in? && current_user.is_banned?
# sign_out current_user
# flash[:error] = I18n.t('messages.account_suspended')
# redirect_to root_path
# end
end
# For this example, we are simply using token authentication
# via parameters. However, anyone could use Rails's token
# authentication features to get the token from a header.

View File

@ -0,0 +1,9 @@
module StrongParams
extend ActiveSupport::Concern
protected
def permit_params(param_name, *accessible)
(params[param_name] || ActionController::Parameters.new).permit(*accessible.flatten)
end
end

View File

@ -6,7 +6,7 @@ class Groups::MembersController < Groups::BaseController
end
def update
raise CanCan::AccessDenied if @group.owner_id.to_s == params[:member_id]
raise Pundit::NotAuthorizedError if @group.owner_id.to_s == params[:member_id]
relation = @group.actors.where(actor_id: params[:member_id], actor_type: 'User').first
relation ||= @group.actors.build(actor_id: params[:member_id], actor_type: 'User')

View File

@ -3,14 +3,14 @@ class Platforms::PlatformsController < Platforms::BaseController
before_action :authenticate_user!
skip_before_action :authenticate_user!, only: [:advisories, :members, :show] if APP_CONFIG['anonymous_access']
load_and_authorize_resource
# load_and_authorize_resource
def index
respond_to do |format|
format.html {}
format.json {
@platforms = @platforms.accessible_by(current_ability, :related)
@platforms = PlatformPolicy::Scope.new(current_user, Platform).related
@platforms_count = @platforms.count
@platforms = @platforms.paginate(page: current_page, per_page: Platform.per_page)
}
@ -18,6 +18,7 @@ class Platforms::PlatformsController < Platforms::BaseController
end
def show
authorize @platform = Platform.find_cached(params[:id])
end
def new

View File

@ -125,7 +125,7 @@ class Projects::BuildListsController < Projects::BaseController
end
def dependent_projects
raise CanCan::AccessDenied if @build_list.save_to_platform.personal?
raise Pundit::NotAuthorizedError if @build_list.save_to_platform.personal?
if request.post?
prs = params[:build_list]

View File

@ -5,7 +5,7 @@ class Projects::Git::BaseController < Projects::BaseController
before_action :authenticate_user, only: %i(show index blame raw archive diff tags branches)
end
load_and_authorize_resource :project
# load_and_authorize_resource :project
before_action :set_treeish_and_path
before_action :set_branch_and_tree

View File

@ -136,7 +136,7 @@ class Projects::PullRequestsController < Projects::BaseController
end
def find_destination_project bang=true
project = Project.find_by_owner_and_name params[:to_project]
project = Project.find_by_owner_and_name_cached params[:to_project]
raise ActiveRecord::RecordNotFound if bang && !project
project || @project.pull_requests.last.try(:to_project) || @project.root
end

View File

@ -57,7 +57,7 @@ class BuildScript < ActiveRecord::Base
def attach_project
if @project_name.present?
self.project = Project.find_by_owner_and_name(@project_name)
self.project = Project.find_by_owner_and_name_cached(@project_name)
end
end

View File

@ -0,0 +1,33 @@
# Private: Finders of all sorts: methods to find FlashNotify records, methods to find
# other records which belong to given FlashNotify.
#
# This module gets included into FlashNotify.
module FlashNotify::Finders
extend ActiveSupport::Concern
included do
scope :published, -> { where(published: true) }
after_commit :clear_caches
after_touch :clear_caches
end
module ClassMethods
# Public: Get cached first published FlashNotify record.
#
# Returns FlashNotify record or nil.
def published_first_cached
Rails.cache.fetch('FlashNotify.published.first') do
published.first
end
end
end
protected
# Private: after_commit and after_touch hook which clears find_cached cache.
def clear_caches
Rails.cache.delete('FlashNotify.published.first')
end
end

View File

@ -0,0 +1,50 @@
# Private: Finders of all sorts: methods to find Platform records, methods to find
# other records which belong to given Platform.
#
# This module gets included into Platform.
module Platform::Finders
extend ActiveSupport::Concern
included do
scope :search_order, -> { order(:name) }
scope :search, -> (q) { where("#{table_name}.name ILIKE ?", "%#{q.to_s.strip}%") }
scope :by_visibilities, -> (v) { where(visibility: v) }
scope :opened, -> { where(visibility: Platform::VISIBILITY_OPEN) }
scope :hidden, -> { where(visibility: Platform::VISIBILITY_HIDDEN) }
scope :by_type, -> (type) { where(platform_type: type) if type.present? }
scope :main, -> { by_type(Platform::TYPE_MAIN) }
scope :personal, -> { by_type(Platform::TYPE_PERSONAL) }
scope :waiting_for_regeneration, -> { where(status: Platform::WAITING_FOR_REGENERATION) }
after_commit :clear_caches
after_touch :clear_caches
end
module ClassMethods
# Public: Get cached Platform record by ID or slug.
#
# platform_id - ID or Slug (Numeric/String)
#
# Returns Platform record.
# Raises ActiveRecord::RecordNotFound if nothing was found.
def find_cached(platform_id)
Rails.cache.fetch(['Platform.find', platform_id]) do
find(platform_id)
end
end
end
protected
# Private: after_commit and after_touch hook which clears find_cached cache.
def clear_caches
Rails.cache.delete(['Platform.find', id])
Rails.cache.delete(['Platform.find', slug])
if chg = previous_changes["slug"]
Rails.cache.delete(['Platform.find', chg.first])
end
end
end

View File

@ -0,0 +1,82 @@
# Private: Finders of all sorts: methods to find Project records, methods to find
# other records which belong to given Project.
#
# This module gets included into Project.
module Project::Finders
extend ActiveSupport::Concern
included do
scope :recent, -> { order(:name) }
scope :search_order, -> { order('CHAR_LENGTH(projects.name) ASC') }
scope :search, ->(q) {
q = q.to_s.strip
by_name("%#{q}%").search_order if q.present?
}
scope :by_name, ->(name) { where('projects.name ILIKE ?', name) if name.present? }
scope :by_owner, ->(name) { where('projects.owner_uname ILIKE ?', "%#{name}%") if name.present? }
scope :by_owner_and_name, ->(*params) {
term = params.map(&:strip).join('/').downcase
where("lower(concat(owner_uname, '/', name)) ILIKE ?", "%#{term}%") if term.present?
}
scope :by_visibilities, ->(v) { where(visibility: v) }
scope :opened, -> { where(visibility: 'open') }
scope :package, -> { where(is_package: true) }
scope :addable_to_repository, ->(repository_id) {
where('projects.id NOT IN (
SELECT ptr.project_id
FROM project_to_repositories AS ptr
WHERE ptr.repository_id = ?)', repository_id)
}
scope :by_owners, ->(group_owner_ids, user_owner_ids) {
where("(projects.owner_id in (?) AND projects.owner_type = 'Group') OR
(projects.owner_id in (?) AND projects.owner_type = 'User')", group_owner_ids, user_owner_ids)
}
scope :project_aliases, ->(project) {
where.not(id: project.id).
where('alias_from_id IN (:ids) OR id IN (:ids)', { ids: [project.alias_from_id, project.id].compact })
}
after_commit :clear_caches
after_touch :clear_caches
end
module ClassMethods
# Public: Get cached Project record by ID or slug.
#
# Returns Project record.
# Raises ActiveRecord::RecordNotFound if nothing was found.
def find_by_owner_and_name_cached(first, last = nil)
Rails.cache.fetch(['Project.find_by_owner_and_name', first, last]) do
find_by_owner_and_name(first, last)
end
end
def find_by_owner_and_name(first, last = nil)
arr = first.try(:split, '/') || []
arr = (arr << last).compact
return nil if arr.length != 2
where(owner_uname: arr.first, name: arr.last).first || by_owner_and_name(*arr).first
end
def find_by_owner_and_name!(first, last = nil)
find_by_owner_and_name_cached(first, last) or raise ActiveRecord::RecordNotFound
end
end
protected
# Private: after_commit and after_touch hook which clears find_cached cache.
def clear_caches
Rails.cache.delete(['Project.find_by_owner_and_name', owner_uname, name])
Rails.cache.delete(['Project.find_by_owner_and_name', name_with_owner])
Rails.cache.delete(['Project.find', id])
Rails.cache.delete(['Project.find', slug])
if chg = previous_changes["slug"]
Rails.cache.delete(['Project.find', chg.first])
end
end
end

View File

@ -1,15 +1,13 @@
require 'digest/md5'
class FlashNotify < ActiveRecord::Base
# attr_accessible :title, :body
include FlashNotify::Finders
STATUSES = %w[error success info]
validates :status, inclusion: {in: STATUSES}
validates :body_ru, :body_en, :status, presence: true
scope :published, -> { where(published: true) }
attr_accessible :body_ru, :body_en, :status, :published
def hash_id

View File

@ -109,7 +109,7 @@ class Issue < ActiveRecord::Base
owner_uname = Regexp.last_match[1].presence || Regexp.last_match[2].presence || project.owner.uname
project_name = Regexp.last_match[1] ? Regexp.last_match[2] : project.name
serial_id = Regexp.last_match[3]
project = Project.find_by_owner_and_name(owner_uname.chomp('/'), project_name)
project = Project.find_by_owner_and_name_cached(owner_uname.chomp('/'), project_name)
return nil unless project
return nil unless current_ability.can? :show, project
project.issues.where(serial_id: serial_id).first

View File

@ -8,6 +8,7 @@ class Platform < ActiveRecord::Base
include EventLoggable
include EmptyMetadata
include DefaultBranchable
include Platform::Finders
self.per_page = 20
@ -96,16 +97,6 @@ class Platform < ActiveRecord::Base
after_create -> { symlink_directory unless hidden? }
after_destroy -> { remove_symlink_directory unless hidden? }
scope :search_order, -> { order(:name) }
scope :search, -> (q) { where("#{table_name}.name ILIKE ?", "%#{q.to_s.strip}%") }
scope :by_visibilities, -> (v) { where(visibility: v) }
scope :opened, -> { where(visibility: VISIBILITY_OPEN) }
scope :hidden, -> { where(visibility: VISIBILITY_HIDDEN) }
scope :by_type, -> (type) { where(platform_type: type) if type.present? }
scope :main, -> { by_type(TYPE_MAIN) }
scope :personal, -> { by_type(TYPE_PERSONAL) }
scope :waiting_for_regeneration, -> { where(status: WAITING_FOR_REGENERATION) }
accepts_nested_attributes_for :platform_arch_settings, allow_destroy: true
attr_accessible :name,
:distrib_type,

View File

@ -8,6 +8,7 @@ class Project < ActiveRecord::Base
include UrlHelper
include EventLoggable
include Project::DefaultBranch
include Project::Finders
VISIBILITIES = ['open', 'hidden']
MAX_OWN_PROJECTS = 32000
@ -67,37 +68,6 @@ class Project < ActiveRecord::Base
:autostart_status
attr_readonly :owner_id, :owner_type
scope :recent, -> { order(:name) }
scope :search_order, -> { order('CHAR_LENGTH(projects.name) ASC') }
scope :search, ->(q) {
q = q.to_s.strip
by_name("%#{q}%").search_order if q.present?
}
scope :by_name, ->(name) { where('projects.name ILIKE ?', name) if name.present? }
scope :by_owner, ->(name) { where('projects.owner_uname ILIKE ?', "%#{name}%") if name.present? }
scope :by_owner_and_name, ->(*params) {
term = params.map(&:strip).join('/').downcase
where("lower(concat(owner_uname, '/', name)) ILIKE ?", "%#{term}%") if term.present?
}
scope :by_visibilities, ->(v) { where(visibility: v) }
scope :opened, -> { where(visibility: 'open') }
scope :package, -> { where(is_package: true) }
scope :addable_to_repository, ->(repository_id) {
where('projects.id NOT IN (
SELECT ptr.project_id
FROM project_to_repositories AS ptr
WHERE ptr.repository_id = ?)', repository_id)
}
scope :by_owners, ->(group_owner_ids, user_owner_ids) {
where("(projects.owner_id in (?) AND projects.owner_type = 'Group') OR
(projects.owner_id in (?) AND projects.owner_type = 'User')", group_owner_ids, user_owner_ids)
}
scope :project_aliases, ->(project) {
where.not(id: project.id).
where('alias_from_id IN (:ids) OR id IN (:ids)', { ids: [project.alias_from_id, project.id].compact })
}
before_validation :truncate_name, on: :create
before_save -> { self.owner_uname = owner.uname if owner_uname.blank? || owner_id_changed? || owner_type_changed? }
before_create :set_maintainer
@ -106,19 +76,6 @@ class Project < ActiveRecord::Base
attr_accessor :url, :srpms_list, :mass_import, :add_to_repository_id
class << self
def find_by_owner_and_name(first, last = nil)
arr = first.try(:split, '/') || []
arr = (arr << last).compact
return nil if arr.length != 2
where(owner_uname: arr.first, name: arr.last).first || by_owner_and_name(*arr).first
end
def find_by_owner_and_name!(first, last = nil)
find_by_owner_and_name(first, last) or raise ActiveRecord::RecordNotFound
end
end
def init_mass_import
Project.perform_later :low, :run_mass_import, url, srpms_list, visibility, owner, add_to_repository_id
end

View File

@ -0,0 +1,11 @@
class AdvisoryPolicy < ApplicationPolicy
def index?
true
end
def show?
true
end
end

View File

@ -0,0 +1,117 @@
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
# raise Pundit::NotAuthorizedError, 'must be logged in' unless user
@user = user || User.new
@record = record
end
BASIC_ACTIONS = %i(index? show? create? update? destroy? destroy_all?)
def index?
false
end
def show?
false
end
def new?
create?
end
def edit?
update?
end
def update?
false
end
def create?
false
end
def destroy?
false
end
def permitted_attributes
[]
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope
end
# Public: Get user's group ids.
#
# Returns the Array of group ids.
def user_group_ids
Rails.cache.fetch(['ApplicationPolicy#user_group_ids', user.id]) do
user.group_ids
end
end
end
protected
# Public: Check if provided user is the current user.
#
# Returns true if it is, false otherwise.
def current_user?(u)
u == user
end
# Public: Check if provided user is guest.
#
# Returns true if he is, false otherwise.
def is_guest?
user.new_record?
end
# Public: Check if provided user is user.
#
# Returns true if he is, false otherwise.
def is_user?
user.persisted?
end
# Public: Check if provided user is tester.
#
# Returns true if he is, false otherwise.
def is_tester?
user.role == 'tester'
end
# Public: Check if provided user is system.
#
# Returns true if he is, false otherwise.
def is_system?
user.role == 'system'
end
# Public: Check if provided user is admin.
#
# Returns true if he is, false otherwise.
def is_admin?
user.role == 'admin'
end
# Public: Check if provided user is banned.
#
# Returns true if he is, false otherwise.
def is_banned?
user.role == 'banned'
end
end

View File

@ -0,0 +1,3 @@
class BuildListPolicy < ApplicationPolicy
end

View File

@ -0,0 +1,3 @@
class GroupPolicy < ApplicationPolicy
end

View File

@ -0,0 +1,62 @@
class PlatformPolicy < ApplicationPolicy
def index?
true
end
def show?
return true unless record.hidden?
return true if record.owner == user
return true if owner?
end
def create?
is_admin?
end
def members?
owner? || local_admin?
end
def clone?
return false if record.personal?
owner? || local_admin?
end
class Scope < Scope
def related
scope.where <<-SQL, { user_id: user.id, user_group_ids: user_group_ids, platform_ids: related_platform_ids }
(
platforms.id IN (:platform_ids)
) OR (
platforms.owner_type = 'User' AND platforms.owner_id = :user_id
) OR (
platforms.owner_type = 'Group' AND platforms.owner_id IN (:user_group_ids)
)
SQL
end
protected
def related_platform_ids
Rails.cache.fetch(['PlatformPolicy::Scope#related_platform_ids', user.id]) do
user.repositories.pluck(:platform_id)
end
end
end
protected
def owner?
record.owner == user ||
record.owner.is_a?(Group) && user_group_ids.include?(record.owner_id)
end
def local_admin?
Rails.cache.fetch(['PlatformPolicy#local_admin?', record, user]) do
user.best_role(record) == 'admin'
end
end
end

View File

@ -0,0 +1,3 @@
class ProductPolicy < ApplicationPolicy
end

View File

@ -0,0 +1,3 @@
class ProjectPolicy < ApplicationPolicy
end

View File

@ -0,0 +1,3 @@
class StatisticPolicy < ApplicationPolicy
end

View File

@ -0,0 +1,6 @@
class UserPolicy < ApplicationPolicy
def banned?
!is_banned?
end
end

View File

@ -15,7 +15,7 @@ json.feed do
end if user
project_name_with_owner = "#{item.data[:project_owner]}/#{item.data[:project_name]}"
@project = Project.find_by_owner_and_name(item.data[:project_owner], item.data[:project_name])
@project = Project.find_by_owner_and_name_cached(item.data[:project_owner], item.data[:project_name])
json.project_name_with_owner project_name_with_owner
json.partial! item.partial, item: item, project_name_with_owner: project_name_with_owner

View File

@ -1,6 +1,6 @@
- if current_user || APP_CONFIG['anonymous_access']
.flash_notify
- if (flash_notify = FlashNotify.published.first) && flash_notify.should_show?(cookies[:flash_notify_hash])
- if (flash_notify = FlashNotify.published_first_cached) && flash_notify.should_show?(cookies[:flash_notify_hash])
.alert{class: "alert-#{flash_notify.status}"}
= flash_notify.body(I18n.locale).html_safe
%a.close#close-alert{:'data-dismiss'=>"alert", href: "#"} &times;

View File

@ -16,7 +16,7 @@ html
== yield :submenu if content_for?(:submenu)
- if current_user || APP_CONFIG['anonymous_access']
- if (flash_notify = FlashNotify.published.first) && flash_notify.should_show?(cookies[:flash_notify_hash])
- if (flash_notify = FlashNotify.published_first_cached) && flash_notify.should_show?(cookies[:flash_notify_hash])
javascript:
var FLASH_HASH_ID = "#{flash_notify.hash_id}";
.notify.alert.alert-dismissable.text-center class=alert_class(flash_notify.status)

View File

@ -20,7 +20,7 @@
%li
= image_tag 'square.png'
= link_to t('bottom_menu.developer_api'), t('bottom_menu.developer_api_url')
-if pr = Project.find_by_owner_and_name('abf/abf-ideas')
-if pr = Project.find_by_owner_and_name_cached('abf/abf-ideas')
%li
= image_tag 'square.png'
= link_to t('bottom_menu.abf_ideas'), project_issues_url(pr)

View File

@ -13,7 +13,7 @@
%li= link_to t('bottom_menu.support'), contact_url
%li ·
%li= link_to t('bottom_menu.developer_api'), t('bottom_menu.developer_api_url')
-if pr = Project.find_by_owner_and_name('abf/abf-ideas')
- if pr = Project.find_by_owner_and_name_cached('abf/abf-ideas')
%li ·
%li= link_to t('bottom_menu.abf_ideas'), project_issues_url(pr)
%li ·

View File

@ -12,7 +12,7 @@ nav.navbar.navbar-inverse.top_menu role = "navigation"
#top-menu-navbar-collapse.collapse.navbar-collapse
ul.nav.navbar-nav
- (collection = t 'top_menu').each do |base, title|
- if can? :index, base.to_s.classify.constantize
- if policy(base).index?
li class=top_menu_class(base)
a href=send("#{base}_path")
i.fa.hidden-sm class=top_menu_icon(base)

View File

@ -22,24 +22,24 @@
= link_to t("layout.repositories.list_header"), platform_repositories_path(@platform)
li class=('active' if contr == :contents)
= link_to t('layout.platforms.contents'), platform_contents_path(@platform)
- if can? :show, @platform
- if policy(@platform).show?
li class=('active' if act == :index && contr == :maintainers)
= link_to t("layout.platforms.maintainers"), platform_maintainers_path(@platform)
li class=('active' if contr == :mass_builds)
= link_to t("layout.platforms.mass_build"), platform_mass_builds_path(@platform)
- if can? :read, @platform.products.build
- if policy(@platform.products.build).index?
li class=('active' if contr == :products)
= link_to t("layout.products.list_header"), platform_products_path(@platform)
- if can? :advisories, @platform
- if policy(@platform.advisories.build).index?
li class=('active' if contr == :platforms && act == :advisories)
= link_to t("layout.advisories.list_header"), advisories_platform_path(@platform)
- if can? :update, @platform
- if policy(@platform).update?
li class=('active' if act == :edit && contr == :platforms)
= link_to t("platform_menu.settings"), edit_platform_path(@platform)
- if can? :members, @platform
- if policy(@platform).members?
li class=('active' if act == :members && contr == :platforms)
= link_to t("layout.platforms.members"), members_platform_path(@platform)
- if can? :edit, @platform
- if policy(@platform).update?
li class=('active' if contr == :key_pairs)
= link_to t("layout.key_pairs.header"), platform_key_pairs_path(@platform)
li class=('active' if contr == :tokens)

View File

@ -1,7 +1,9 @@
- set_meta_tags title: t('layout.platforms.list_header')
.row ng-controller='PlatformsCtrl'
.col-md-6.col-md-offset-3 ng-cloak=true
= link_to t('layout.platforms.new'), new_platform_path, class: 'btn btn-primary' if can? :create, Platform
- if policy(:platform).create?
a.btn.btn-primary href=new_platform_path
= t('layout.platforms.new')
table.table.table-hover.offset10
thead
tr

View File

@ -47,7 +47,7 @@
b= t('layout.platforms.distrib_type')
.col-md-8= @platform.distrib_type
- if can? :clone, @platform
- if policy(@platform).clone?
.row
.col-md-4
.col-md-8