Add new permissions management ui (role overrides)

When editing permissions for an account, course
or admin section the UI has changed to use
drop downs instead of a 6 state check box. It
has also been switched to use 100% backbonejs to
handle creating and editing roles. This works with
the roles api and allows you to create custom
roles for courses.

fixes #CNVS-1165

Test Plan

Apply this test in two places. The Site Admin and
a custom university.

1. Go to the "permissions" tab
2. Notice permissions are available per role in a
table.

------------ Adding/Removing Roles --------------
1. When under the "Account Role" tab click
"Add Role"
2. Enter a role name and click "Create/Add"
3. A new role should appear automatically.
4. You should be able to delete this role by
clicking the x next to it's name.

------------ Editing Permissions ----------------
1. Go to a role in the permissions tab.
2. Try to edit one of it's permissions. It should
have a drop down with options to select
permissions. You should be able to enable/disable
or set to default and lock the permission.
Read only permissions cannot be clicked on.
3. Buttons with default set should have a special
"default" class added to the button representing
its permission.

------------ Organization of Roles --------------
1. In the course role tab, add a few roles with
different base types (use the dropwdown)
2. Roles should be grouped together by base role
type.

------------ Automatic Saving ------------------
1. Change a permission on a role
2. The role should automatically save the
the permission after selecting the option you want

Change-Id: I343afc36b85183e5913c8eef6111ea2c5ae62726
Reviewed-on: https://gerrit.instructure.com/16323
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
QA-Review: Adam Phillipps <adam@instructure.com>
This commit is contained in:
Sterling Cobb 2012-12-26 13:09:58 -07:00
parent 14f6a92c0b
commit f3d8cdd94c
35 changed files with 2370 additions and 349 deletions

View File

@ -72,7 +72,9 @@ define [
# in charge of getting variables ready to pass to handlebars during render
# override with your own logic to do something fancy.
toJSON: ->
(@model ? @collection)?.toJSON arguments...
json = ((@model ? @collection)?.toJSON arguments...) || {}
json.cid = @cid
json
##
# Renders all child views
@ -83,13 +85,15 @@ define [
##
# Renders a single child view and appends its designated element
# Use ids in your view, not classes. This
#
# @api private
renderView: (view, className) =>
target = @$('.' + className).first()
renderView: (view, selector) =>
target = @$("##{selector}")
target = @$(".#{selector}") unless target.length
view.setElement target
view.render()
@[className] ?= view
@[selector] ?= view
##
# Binds a `@model` data to the element's html. Whenever the data changes

View File

@ -0,0 +1,80 @@
require [
'jquery',
'underscore'
'compiled/models/Role'
'compiled/models/Account'
'compiled/collections/RolesCollection'
'compiled/views/roles/RolesOverrideIndexView'
'compiled/views/roles/AccountRolesView'
'compiled/views/roles/CourseRolesView'
'compiled/views/roles/ManageRolesView'
'compiled/views/roles/NewRoleView'
], ($, _, Role, Account, RolesCollection, RolesOverrideIndexView, AccountRolesView, CourseRolesView, ManageRolesView, NewRoleView) ->
account_roles = new RolesCollection ENV.ACCOUNT_ROLES
course_roles = new RolesCollection ENV.COURSE_ROLES
course_permissions = ENV.COURSE_PERMISSIONS
account_permissions = ENV.ACCOUNT_PERMISSIONS
course_role_types = []
_.each ENV.COURSE_ROLES, (role) ->
if role.role == role.base_role_type
course_role_types.push
value : role.base_role_type
label : role.label
# They will both use the same collection.
rolesOverrideIndexView = new RolesOverrideIndexView
el: '#content'
views:
'account-roles': new AccountRolesView
views:
'#account_roles' : new ManageRolesView
collection: account_roles
permission_groups: account_permissions
'new-role' : new NewRoleView
base_role_types: [{value:'AccountMembership', label:'AccountMembership'}]
admin_roles: true
collection: account_roles
'course-roles': new CourseRolesView
views:
'#course_roles' : new ManageRolesView
collection: course_roles
permission_groups: course_permissions
'new-role' : new NewRoleView
base_role_types: course_role_types
collection: course_roles
rolesOverrideIndexView.render()
# Make sure the left navigation permissions is highlighted.
$('#section-tabs .permissions').addClass 'active'
# This is not the right way to do this and is just a hack until
# something offical in canvas is built.
# Adds toggle functionality to the menu buttons.
# Yes, it's ugly but works :) Sorry.
# ============================================================
# DELETE ME SOMEDAY!
# ============================================================
$(document).on 'click', (event) ->
container = $('.btn-group')
if (container.has(event.target).length is 0 and !$(event.target).hasClass('.btn'))
container.removeClass 'open'
$(document).on 'click', '.btn', (event) ->
event.preventDefault()
previous_state = $(this).parent().hasClass 'open'
$('.btn-group').removeClass 'open'
if (previous_state == false && !$(this).attr('disabled') )
$(this).parent().addClass('open')
$(this).siblings('.dropdown-menu').find('input').first().focus()
$(document).on 'keyup', (event) =>
if (event.keyCode == 27)
$('.btn-group').removeClass 'open'
$(this).focus()
##################################################################

View File

@ -0,0 +1,32 @@
define [
'Backbone'
'underscore'
'compiled/models/Role'
], (Backbone, _, Role) ->
class RolesCollection extends Backbone.Collection
model: Role
sortOrder: [
"NoPermissions"
"AccountMembership"
"StudentEnrollment"
"TaEnrollment"
"TeacherEnrollment"
"DesignerEnrollment"
"ObserverEnrollment"
]
# Method Summary
# make a comment
# @api backbone override
comparator: (role) ->
base_role_type= role.get 'base_role_type'
index = _.indexOf @sortOrder, base_role_type
if base_role_type == role.get 'role'
return "#{index}_#{base_role_type}"
else if role.get "role" == "AccountAdmin"
return "0_#{base_role_type}"
else
return "#{index}_#{base_role_type}_#{role}"

View File

@ -0,0 +1,6 @@
define [
'Backbone'
'underscore'
], (Backbone, _) ->
class Account extends Backbone.Model

View File

@ -0,0 +1,77 @@
define [
'Backbone'
'underscore'
'compiled/models/Account'
], (Backbone, _, Account) ->
class Role extends Backbone.Model
# Method Summary
# Each role has an Account model nested in it. When creating
# a new role, run it through and makes sure attributes have
# and account model nested in model.account. It's NOT using
# parse because parse sets the id to role in parse (because roles
# have 'fake' ids so backbone will work) and when an id is set
# when you try to save, it assumes it was all ready created and
# does a PUT instead of a POST request. Thus, we don't call parse
# we call nestAccountModel.
# @api backbone override
initialize: (attributes, options) ->
super
if attributes
parsedAttributes = @nestAccountModel attributes
@set parsedAttributes
# Method Summary
# urlRoot is used in url to generate the a restful url. Because
# the "id" is set to the roles name (see parse function), the
# url uses the role name in place of the :id attribute in the url
#
# ie:
# /accounts/:account_id/roles
# /accounts/:account_id/roles/:some_role_name
#
# produces
# /accounts/1/roles
# /accounts/1/roles/StudentAssistant
#
# @api override backbone
urlRoot: -> "/api/v1/accounts/#{@get('account').get('id')}/roles"
# Method Summary
# ResourceName is used by a collection to help determin the url
# that should be generated for the resource.
# @api custom backbone override
resourceName: 'roles'
# Method Summary
# Expects data to have data.account object that will be used to
# create a new model. The new model replaces the old account
# object.
# @api private
nestAccountModel: (data) ->
data.account = new Account data.account
data
# Method Summary
# Parse is called when data is set via attributes to the model.
# Because roles might not always have a unique id, we are
# setting the id to be the role name. This takes care of checks
# for "isNew()" as well as issues with generating a correct url.
# Also, ensure that account is wrapped in a backbone model since
# the url relies on it being a backbone model.
# @api override backbone
parse: (data) ->
data.id = data.role if data.role
data = @nestAccountModel(data)
data
# Method Summary
# See backbones explaination of a validate method for in depth
# details but in short, if your return something from validate
# there is an error, if you don't, there are no errors. Throw
# in the error object to any validation function you make. It's
# passed by reference dawg.
# @api override backbone
validate: (attrs) ->
errors = {}
errors unless _.isEmpty errors

View File

@ -0,0 +1,9 @@
define [], ->
BASE_ROLE_TYPES = [
'AccountMembership'
'StudentEnrollment'
'TeacherEnrollment'
'TaEnrollment'
'ObserverEnrollment'
'DesignerEnrollment'
]

View File

@ -0,0 +1,8 @@
define [
'jquery'
'underscore'
'Backbone'
'jst/roles/accountRoles'
], ($, _, Backbone, template) ->
class AccountRolesView extends Backbone.View
template: template

View File

@ -0,0 +1,8 @@
define [
'jquery'
'underscore'
'Backbone'
'jst/roles/courseRoles'
], ($, _, Backbone, template) ->
class CourseRolesView extends Backbone.View
template: template

View File

@ -0,0 +1,120 @@
define [
'jquery'
'underscore'
'Backbone'
'jst/roles/manageRoles'
'compiled/views/roles/PermissionButtonView'
'compiled/views/roles/RoleHeaderView'
], ($, _, Backbone, template, PermissionButtonView, RoleHeaderView) ->
class ManageRolesView extends Backbone.View
template: template
className: 'manage-roles-table'
# Method Summary
# When a new Role is added/removed from the collection, re-draw the table.
initialize: ->
super
@permission_groups = @options.permission_groups if @options.permission_groups
@collection.on 'add', @renderTable
@collection.on 'remove', @renderTable
# Method Summary
# Gets called after this backbone view has
# been rendered. For each permission in the
# permission list, it will add a new
# permission select box for each role in the roles
# collection. In this way, we are drawing the
# whole table row by row since html doesn't
# support drawing column by column.
# @api custom backbone
filter: ->
@renderTable()
# Method Summary
# The table has two parts. A header and the tbody part. The header
# has some functionality to do with deleting a role so contains its
# own logic. renderHeader gets called when renderTable gets
# called, which should get called when role is added or removed.
# @api private
renderHeader: ->
@$el.find('thead tr').html "<th>Permissions</th>"
@collection.each (role) =>
roleHeaderView = new RoleHeaderView
model: role
@$el.find('thead tr').append roleHeaderView.render().el
# Method Summary
# Creates the permission table by drawing it all at once.
# This is necessary because html tables only support
# drawing a table via rows instead of columns and we are
# representing our data in coloumns. works by bring in
# permission_groups, drawing the main group headers then
# for each main group of permissions, drawing each one of
# those permissions rows by iterating over each role in the
# collection and drawing a select box for each role. The
# permissions_group object looks like this.
#
# permission_groups = [
# {
# group_name:"Parent label"
# group_permissions: [
# {
# label: "title"
# permission_name: "some_property"
# }
# {
# label: "title again"
# permission_name: "some_property_again"
# }
#
# ]
# }
# {
# group_name:"Parent label 2"
# group_permissions: [
# label: "title 2"
# permission_name: "some_other_property_2"
# ]
# }
# ]
#
# Steps: 1. Draw group header
# 2. Draw permission label
# 3. Draw each select box from the collection of roles.
# @api private
renderTable: =>
@renderHeader()
@$el.find('tbody').html '' # Clear tbody in case it gets re-drawing.
_.each @permission_groups, (permission_group) =>
# Add the headers to the group
permission_group_header = """
<tr class="toolbar">
<th>#{permission_group.group_name.toUpperCase()}</th>
<td colspan="#{@collection.length}"></td>
</tr>
"""
@$el.find('tbody').append permission_group_header
# Add each permission item.
_.each permission_group.group_permissions, (permission_row) =>
permission_row_html = """
<tr>
<th role="rowheader">#{permission_row.label}</th>
</tr>
"""
@$el.find('tbody').append permission_row_html
@collection.each (role) =>
permissionButtonView = new PermissionButtonView
model: role
permission_name: permission_row.permission_name
@$el.find("tr")
.last()
.append permissionButtonView.render().el

View File

@ -0,0 +1,74 @@
define [
'i18n!editor'
'jquery'
'underscore'
'Backbone'
'jst/roles/newRole'
'compiled/models/Role'
'compiled/models/Account'
], (I18n, $, _, Backbone, template, Role, Account) ->
class NewRoleView extends Backbone.View
template: template
els:
"input[type=text]" : "$role_name"
"select" : "$base_role_type"
events:
"click button" : "createRole"
"submit form" : "createRole"
# Method Summary
# We need base role types so we know what types the user can
# select from. We also need to know if this is for admin roles.
# If it is, the user can only select one type of base_role_type
# which is "AccountMembership" See the template for better
# understanding.
# @api backbone override
initialize: ->
super
@base_role_types = @options?.base_role_types
@adminRoles = @options?.admin_roles
# Method Summary
# JSON is dumped into the template so we are adding some logic
# checks we can use to display certain information.
# @api backbone override
toJSON: ->
json = super
json['base_role_types'] = @base_role_types
json['adminRoles'] = @adminRoles
json
# Method Summary
# This will grab values out of the newRole view and save them
# as attributes when creating a new role. @collection.create
# will create a new role and if it is successful, add it to
# the collection. We also clear the form apon success. We
# wait: true which means, wait until the request comes back
# before adding the role to the collection.
# @api private
createRole: (event) ->
event.preventDefault()
role_name = @$role_name.val()
base_role_type = @$base_role_type.val()
attributes =
base_role_type: base_role_type
role: role_name
account: ENV.CURRENT_ACCOUNT.account
@collection.create attributes,
success: (model) =>
@clearForm()
error: ->
alert I18n.t "role.duplicate_role_error", "Could not create this role because a role with this name already exists. Please try a different name"
wait: true
# Method Summary
# Clear all variables in the form.
# @api private
clearForm: ->
@$role_name.val('')
@$base_role_type.val('')

View File

@ -0,0 +1,362 @@
define [
'jquery'
'underscore'
'Backbone'
'jst/roles/permissionButton'
], ($, _, Backbone, template) ->
class PermissionButtonView extends Backbone.View
template: template
tagName: 'td'
className: 'permissionButtonView'
events:
"change input[type=radio]" : "updateRole"
# Method Summary:
# Set the permission_name attribute.
#
# ============================================================================
# !!! NOTE permission_name This must be passed in for this view to work !!!
# ============================================================================
#
# @api backbone override
initialize: ->
super
@permission_name = @options.permission_name if @options.permission_name
# Method Summary
# We add a few values to the json being passed into the template so we can determine which item
# should be selected on the initial page load. We didn't create a helper for handlebars because
# this is very specific to this class. We have some logic here that checks to see what the
# permissions value is given it's key, the permission_name for some of the properties. Permissions
# object might look like this.
#
# ie:
# "enabled": true,
# "locked": true,
# "readonly": false,
# "explicit": true,
# "prior_default": false
#
# @api custom backbone override
toJSON: ->
json = super
json['enableChecked'] = @isEnabled()
json['enableAndLockChecked'] = @isEnabledAndLocked()
json['disableChecked'] = @isDisabled()
json['disableAndLockChecked'] = @isDisabledAndLocked()
json['systemDefaultChecked'] = @isDefault()
json['systemDefaultLockedChecked'] = @isDefaultAndLocked()
json['readOnly'] = @isReadOnly()
json['default'] = @isDefault() || @isDefaultAndLocked() # Any kind of default. Used for setting a css class
json
# Method Summary
# Make sure all initial icons are shown correctly. Puts the correct
# set of icons into the button by cloning the icons in the dropdown
# list. :) I'm a smarty pants. Also, add accessibility attributes.
# Each button has data about it's role and permission name. This
# makes testing easier.
# @api custom backbone override
filter: ->
@setPreviewIcons()
@$el.attr 'data-role_name', @model.id
@$el.attr 'data-permission_name', @permission_name
# Method Summary
# Preview Icons are set based on the model attributes, not what is
# selected; however model attributes should always be in sync with
# what is selected so it will have the same effect. There are only
# 6 possiblities.
# @api private
setPreviewIcons: ->
if @isEnabled() then @setEnabledIcon()
else if @isEnabledAndLocked() then @setEnabledLockedIcon()
else if @isDisabled() then @setDisabledIcon()
else if @isDisabledAndLocked() then @setDisabledLockedIcon()
else if @isDefault() then @setDefaultIcon()
else if @isDefaultAndLocked() then @setDefaultAndLockedIcon()
# Method Summary:
# We are checking the ides of each changed radio element because we
# can't get access to @cid inside of the "events" object. If somone
# can figure out how to do this feel free to remove this switch
# statement.
#
# TODO Remove the 'default_permission' its ugly
# @api private
updateRole: (event) ->
event.preventDefault()
switch $(event.target).attr('id')
when "button-#{@cid}-0"
@$el.find('a.btn').removeClass 'default_permission'
@enable()
break
when "button-#{@cid}-1"
@$el.find('a.btn').removeClass 'default_permission'
@enableAndLock()
break
when "button-#{@cid}-2"
@$el.find('a.btn').removeClass 'default_permission'
@disable()
break
when "button-#{@cid}-3"
@$el.find('a.btn').removeClass 'default_permission'
@disableAndLock()
break
when "button-#{@cid}-4"
@$el.find('a.btn').addClass 'default_permission'
@setSystemDefault()
break
when "button-#{@cid}-5"
@$el.find('a.btn').addClass 'default_permission'
@setSystemDefaultAndLocked()
break
@setPreviewIcons()
@closeMenu()
@saveModel()
# Method Summary
# Save the current role by calling .save.
# @api private
saveModel: ->
@model.save {},
failure: ->
alert 'Permission was not be saved!'
# Method Summary
# Close the menu by removing the 'open' class on it's parent btn-group
# @api private
closeMenu: ->
@$el.children('.btn-group').removeClass 'open'
# Method Summary for enable, enableAndLock, disable, disableAndLock
# When called, this enables the given role for this buttons property.
# Enabling a property should look like the following
#
# some_property:
# "enabled": true
# "locked" : false
# "explicit" : true
# @api private
enable: ->
@model.get('permissions')[@permission_name].enabled = true
@model.get('permissions')[@permission_name].explicit = true
@model.get('permissions')[@permission_name].locked = false
enableAndLock: ->
@model.get('permissions')[@permission_name].enabled = true
@model.get('permissions')[@permission_name].explicit = true
@model.get('permissions')[@permission_name].locked = true
disable: ->
@model.get('permissions')[@permission_name].enabled = false
@model.get('permissions')[@permission_name].explicit = true
@model.get('permissions')[@permission_name].locked = false
disableAndLock: ->
@model.get('permissions')[@permission_name].enabled = false
@model.get('permissions')[@permission_name].explicit = true
@model.get('permissions')[@permission_name].locked = true
# Method Summary
# This is the edge case for setting properties. This does care what
# the other properties were set to because when you set explict to
# false and save it, the request comming back will tell you what
# the prior default (either enabled or disabled) is. If no prior
# default is set, then you can assume what the permission is based
# on the properites sent back. For example, just use the enabled
# field.
#
# Example of the response that will be generated from setting
# explicit to false:
#
# ie:
# "enabled": true,
# "locked": true,
# "readonly": false,
# "explicit": true,
# "prior_default": false
#
# @api private
setSystemDefault: ->
@model.get('permissions')[@permission_name].locked = false
@model.get('permissions')[@permission_name].explicit = false
# Method Summary
# Same as systemDefault except locked is true.
# @api private
setSystemDefaultAndLocked: ->
@model.get('permissions')[@permission_name].locked = true
@model.get('permissions')[@permission_name].explicit = false
# Method Summary
# Check to see if this role is enabled.
#
# This means
#
# enabled : true
# locked : false
# explicit : true
#
# returns Boolean
# @api private
isEnabled: ->
@model.get('permissions')[@permission_name].enabled && !@isLocked() && @isExplicit()
# Method Summary
# Check to see if this role is enabled and locked.
#
# This means
#
# enabled : true
# locked : true
# explicit : true
#
# returns Boolean
# @api private
isEnabledAndLocked: ->
@model.get('permissions')[@permission_name].enabled && @isLocked() && @isExplicit()
# Method Summary
# Check to see if this role is disabled.
#
# This means
#
# enabled : false
# locked : false
# explicit : true
#
# returns Boolean
# @api private
isDisabled: ->
!@model.get('permissions')[@permission_name].enabled && !@isLocked() && @isExplicit()
# Method Summary
# Check to see if this role is disabled and locked.
#
# This means
#
# enabled : false
# locked : true
# explicit : true
#
# returns Boolean
# @api private
isDisabledAndLocked: ->
!@model.get('permissions')[@permission_name].enabled && @isLocked() && @isExplicit()
# Method Summary
# All default means is explicit is set to false and lock is false.
#
# This means
#
# enabled : 'don't care about this value :/'
# locked : false
# explicit : false
#
# @api private
isDefault: ->
!@isExplicit() && !@isLocked()
# Method Summary
# Default and lock does is make sure explicit is set to false and lock is true
#
# This means
#
# enabled : 'don't care about this value :/'
# locked : true
# explicit : false
#
# @api private
isDefaultAndLocked: ->
!@isExplicit() && @isLocked()
# Method Summary
# Read only attribute is set means you cannot change this permission.
#
# This means
#
# readonly : true
#
# @api private
isReadOnly: ->
@model.get('permissions')[@permission_name].readonly
# Method Summary
# Checks to see if the permission is explicit. Doesn't care about any other permissions.
# ie:
# explicit : true | false
#
# @api private
isExplicit: ->
@model.get('permissions')[@permission_name].explicit
# Method Summary
# Checks to see if the permission is locked. Doesn't care about any other permissions
#
# ie:
# locked : true | false
# @api private
isLocked: ->
@model.get('permissions')[@permission_name].locked
# Method Summary
# Set icon button for preview. In order to do this we just clone the radio buttons
# images into the buttons preview section. Takes in a string. The string can have these options
# 0 = "enabled"
# 1 = "enabledLocked"
# 2 = "disabled"
# 3 = "disabledLocked"
# @api private
setButtonPreview: (selected_radio) ->
icons = @$el.find("label[for=button-#{@cid}-#{selected_radio}] i").clone()
@$el.find('a.dropdown-toggle').html icons
# Method Summary
# Sets the button preview for an dropdown button to the enabled icons
# @api private
setEnabledIcon: ->
@setButtonPreview 0
# Method Summary
# Sets the button preview for an dropdown button to the enabled and locked icons
# @api private
setEnabledLockedIcon: ->
@setButtonPreview 1
# Method Summary
# Sets the button preview for an dropdown button to the disabled
# @api private
setDisabledIcon: ->
@setButtonPreview 2
# Method Summary
# Sets the button preview for an dropdown button to the disabled
# @api private
setDisabledLockedIcon: ->
@setButtonPreview 3
# Method Summary
# Check to see if there is a prior_set because if there is one set, we should always
# use that setting to show what the default is. If that setting is not set, we can
# assume its just the enabled value.
# @api private
setDefaultIcon: ->
if _.isUndefined @model.get('permissions')[@permission_name].prior_default
if @model.get('permissions')[@permission_name].enabled then @setEnabledIcon() else @setDisabledIcon()
else
if @model.get('permissions')[@permission_name].prior_default then @setEnabledIcon() else @setDisabledIcon()
# Method Summary
# Same as setDefaultIcon except everything has a lock applied to it.
# @api private
setDefaultAndLockedIcon: ->
if _.isUndefined @model.get('permissions')[@permission_name].prior_default
if @model.get('permissions')[@permission_name].enabled then @setEnabledLockedIcon() else @setDisabledLockedIcon()
else
if @model.get('permissions')[@permission_name].prior_default then @setEnabledLockedIcon() else @setDisabledLockedIcon()

View File

@ -0,0 +1,79 @@
define [
'i18n!editor'
'jquery'
'underscore'
'Backbone'
'jst/roles/roleHeader'
], (I18n, $, _, Backbone, template) ->
class RoleHeaderView extends Backbone.View
template: template
tagName: 'th'
className: 'roleHeader'
# Static roles are role's that cannot be deleted.
# The delete link will not show up next to their
# name.
staticRoles: [
'AccountAdmin'
'AccountMembership'
'StudentEnrollment'
'TeacherEnrollment'
'TaEnrollment'
'ObserverEnrollment'
'DesignerEnrollment'
]
events:
"click a" : "removeRole"
# Method Summary
# Are you abled to delete this role? You can delete
# a role if it's not one of the static roles.
# @api private
deletable: ->
!_.contains @staticRoles, @model.get('role')
# Method Summary
# We add attributes to JSON that gets passed into the
# handlebars template so we can manipulate the template.
# @api backbone override
toJSON: ->
json = super
json['deletable'] = @deletable()
json['showBaseRoleType'] = @showBaseRoleType()
json
# Method Summary
# Destroys the model. This will send a DELETE request to the model.
# If this role is in a collection (most likely is) it will automatically
# be removed from the collection and the collections remove event will
# be triggered.
#
# Make sure the user knows that if there are any enrollments on this role
# it will be frozen.
# @api private
removeRole: ->
if confirm I18n.t "role.remove_role_confirmation", "If there are any users with this role, they will keep the current permissions but you will not be able to create new users with this role. Click ok to continue deleting this role."
@model.destroy
error: (model, response) ->
alert "#{model.role} could not be remove, contact your site admin if this continues."
wait: true
# Method Summary
# Under the actual role name we display the base role type. We don't do
# this for every type of base_role_type however. These base_role_types
# we hide because it's implied. This gets used in the template to determin
# what should be shown.
# @api private
showBaseRoleType: ->
!_.contains ["AccountAdmin", "NoPermissions", "AccountMembership"], @model.get('base_role_type')
# Method Summary
# This is called after render to ensure column header is set for accessiblity.
# @api custom backbone override
filter: ->
@$el.attr('role', 'columnheader')

View File

@ -0,0 +1,16 @@
define [
'jquery'
'underscore'
'Backbone'
'jst/roles/rolesOverrideIndex'
], ($, _, Backbone, template) ->
class RolesOverrideIndexView extends Backbone.View
template: template
els:
"#role_tabs": "$roleTabs"
# Method Summary
# Enable tabs for account/course roles.
# @api custom backbone override
filter: ->
@$roleTabs.tabs()

View File

@ -87,6 +87,7 @@
class RoleOverridesController < ApplicationController
before_filter :require_context
before_filter :require_role, :only => [:activate_role, :add_role, :remove_role, :update, :show]
before_filter :set_js_env_for_current_account
# @API List roles
# List the roles available to an account.
@ -111,17 +112,36 @@ class RoleOverridesController < ApplicationController
def index
if authorized_action(@context, @current_user, :manage_role_overrides)
@managing_account_roles = @context.is_a?(Account) && (params[:account_roles] || @context.site_admin?)
account_role_data = []
if @managing_account_roles
@role_types = RoleOverride.account_membership_types(@context)
else
@role_types = RoleOverride.enrollment_types
role = Role.built_in_role("AccountAdmin")
account_role_data << role_json(@context, role, @current_user, session)
account_role_data[0][:id] = role.name
@context.available_custom_account_roles.each do |role|
json = role_json(@context, role, @current_user, session)
json[:id] = role.name
account_role_data << json
end
respond_to do |format|
format.html
course_role_data = []
custom_roles = @context.available_course_roles_by_name.values
RoleOverride::ENROLLMENT_TYPES.map do |role_hash|
role = Role.built_in_role(role_hash[:name])
json = role_json(@context, role, @current_user, session)
json[:id] = role.name
course_role_data << json
custom_roles.select { |cr| cr.base_role_type == role_hash[:base_role_name] }.map do |cr|
json = role_json(@context, cr, @current_user, session)
json[:id] = cr.name
course_role_data << json
end
end
js_env :ACCOUNT_ROLES => account_role_data
js_env :COURSE_ROLES => course_role_data
js_env :ACCOUNT_PERMISSIONS => account_permissions(@context)
js_env :COURSE_PERMISSIONS => course_permissions(@context)
end
end
@ -380,6 +400,12 @@ class RoleOverridesController < ApplicationController
end
end
# Summary:
# Adds ENV.CURRENT_ACCOUNT with the account we are working with.
def set_js_env_for_current_account
js_env :CURRENT_ACCOUNT => @context
end
# Internal: Get role from params or return error. Used as before filter.
#
# Returns found role or false (to halt execution).
@ -422,15 +448,85 @@ class RoleOverridesController < ApplicationController
manageable_permissions.keys.each do |permission|
if settings = permissions[permission]
if settings.has_key?(:enabled) && value_to_boolean(settings[:explicit])
override = value_to_boolean(settings[:enabled])
end
locked = value_to_boolean(settings[:locked]) if settings.has_key?(:locked)
if !value_to_boolean(settings[:readonly])
if settings.has_key?(:enabled) && value_to_boolean(settings[:explicit])
override = value_to_boolean(settings[:enabled])
end
locked = value_to_boolean(settings[:locked]) if settings.has_key?(:locked)
RoleOverride.manage_role_override(context, role, permission.to_s,
:override => override, :locked => locked)
RoleOverride.manage_role_override(context, role, permission.to_s,
:override => override, :locked => locked)
end
end
end
end
protected :set_permissions_for
private
def course_permissions(context)
site_admin = {:group_name => t('site_admin_permissions', "Site Admin Permissions"), :group_permissions => []}
account = {:group_name => t('account_permissions', "Account Permissions"), :group_permissions => []}
course = {:group_name => t('course_permissions', "Course & Account Permissions"), :group_permissions => []}
base_role_names = RoleOverride.enrollment_types.map do |enrollment_type|
enrollment_type[:base_role_name]
end
RoleOverride.manageable_permissions(context).each do |p|
hash = {:label => p[1][:label].call, :permission_name => p[0]}
# Check to see if the base role name is in the list of other base role names in p[1]
is_course_permission = !(base_role_names & p[1][:available_to]).empty?
if p[1][:account_only]
if p[1][:account_only] == :site_admin
site_admin[:group_permissions] << hash if is_course_permission
else
account[:group_permissions] << hash if is_course_permission
end
else
course[:group_permissions] << hash if is_course_permission
end
end
res = []
res << site_admin if site_admin[:group_permissions].any?
res << account if account[:group_permissions].any?
res << course if course[:group_permissions].any?
res.each{|pg| pg[:group_permissions] = pg[:group_permissions].sort_by{|p|p[:label]} }
res
end
# Returns a hash with the avalible permissions grouped by groups of permissions.
# context - the current context
def account_permissions(context)
site_admin = {:group_name => t('site_admin_permissions', "Site Admin Permissions"), :group_permissions => []}
account = {:group_name => t('account_permissions', "Account Permissions"), :group_permissions => []}
course = {:group_name => t('course_permissions', "Course & Account Permissions"), :group_permissions => []}
RoleOverride.manageable_permissions(context).each do |p|
hash = {:label => p[1][:label].call, :permission_name => p[0]}
if p[1][:account_only]
if p[1][:account_only] == :site_admin
site_admin[:group_permissions] << hash
else
account[:group_permissions] << hash
end
else
course[:group_permissions] << hash
end
end
res = []
res << site_admin if site_admin[:group_permissions].any?
res << account if account[:group_permissions].any?
res << course if course[:group_permissions].any?
res.each{|pg| pg[:group_permissions] = pg[:group_permissions].sort_by{|p|p[:label]} }
res
end
end

View File

@ -506,6 +506,12 @@ class Account < ActiveRecord::Base
self.account_users.find_by_user_id(user && user.id)
end
def available_custom_account_roles
account_roles = roles.for_accounts.active
account_roles |= self.parent_account.available_custom_account_roles if self.parent_account
account_roles
end
def available_account_roles
account_roles = roles.for_accounts.active.map(&:name)
account_roles |= ['AccountAdmin']

View File

@ -60,6 +60,10 @@ class Role < ActiveRecord::Base
!account_role?
end
def label
self.name
end
alias_method :destroy!, :destroy
def destroy
self.workflow_state = 'deleted'
@ -132,11 +136,19 @@ class Role < ActiveRecord::Base
class BuiltInRole
attr_accessor :name
def initialize(name)
@name = name
if @name == 'AccountAdmin'
@label = I18n.t('roles.account_admin', "Account Admin")
else
er = RoleOverride.enrollment_types.find{|er|er[:name] == @name}
@label = er[:label].call
end
end
def self.create(name)
return nil unless Role.built_in_role_names.include?(name)
r = BuiltInRole.new
r.name = name
r
BuiltInRole.new(name)
end
def base_role_type
@ -146,6 +158,10 @@ class Role < ActiveRecord::Base
def workflow_state
'active'
end
def label
@label
end
end
# returns a BuiltInRole for the role with the given name, or nil

View File

@ -0,0 +1,8 @@
div.dropdown-menu.new-role {
top: 0px !important;
padding: 20px;
select[disabled], select:disabled {
display: none;
}
}

View File

@ -0,0 +1,75 @@
.table td.permissionButtonView {
position: relative;
.default_permission {
opacity: .5;
}
.icon-x {
color: #9B0000;
}
.icon-check {
color: green;
}
.btn-group{
margin: 0 auto;
width: 40px;
position: relative;
.btn.dropdown-toggle{
position: relative;
min-width: 35px;
&:disabled, &[disabled] {
background: none;
border: none;
& .icon-lock {
display: none;
}
}
}
}
.dropdown-menu {
text-align: left;
/*left: 30%;*/
input[type=radio] {
display: none;
}
label{
display: block;
padding: 5px;
}
span.text{
float: right;
text-align: left !important;
width: 110px;
&:last{
float: none;
text-align: center;
display: block;
}
}
label:hover, label:focus, input:checked+label {
text-decoration: none;
color: white;
background-color: #08C;
background-color: #0081C2;
background-image: -moz-linear-gradient(top, #08C, #0077B3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08C), to(#0077B3));
background-image: -webkit-linear-gradient(top, #08C, #0077B3);
background-image: -o-linear-gradient(top, #08C, #0077B3);
background-image: linear-gradient(to bottom, #08C, #0077B3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF0088CC', endColorstr='#FF0077B3', GradientType=0);
}
}
}

View File

@ -0,0 +1,870 @@
/* line 9, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
table {
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
border-spacing: 0; }
/* line 19, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table {
width: 100%;
margin-bottom: 20px; }
/* line 24, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table th,
.table td {
padding: 8px;
line-height: 20px;
text-align: left;
vertical-align: top;
border-top: 1px solid #dddddd; }
/* line 31, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table th {
font-weight: bold; }
/* line 35, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table thead th {
vertical-align: bottom; }
/* line 44, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table caption + thead tr:first-child th,
.table caption + thead tr:first-child td,
.table colgroup + thead tr:first-child th,
.table colgroup + thead tr:first-child td,
.table thead:first-child tr:first-child th,
.table thead:first-child tr:first-child td {
border-top: 0; }
/* line 48, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table tbody + tbody {
border-top: 2px solid #dddddd; }
/* line 60, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-condensed th,
.table-condensed td {
padding: 4px 5px; }
/* line 69, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered {
border: 1px solid #dddddd;
border-collapse: separate;
*border-collapse: collapse;
border-left: 0;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px; }
/* line 76, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered th,
.table-bordered td {
border-left: 1px solid #dddddd; }
/* line 88, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered caption + thead tr:first-child th,
.table-bordered caption + tbody tr:first-child th,
.table-bordered caption + tbody tr:first-child td,
.table-bordered colgroup + thead tr:first-child th,
.table-bordered colgroup + tbody tr:first-child th,
.table-bordered colgroup + tbody tr:first-child td,
.table-bordered thead:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child td {
border-top: 0; }
/* line 93, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered thead:first-child tr:first-child th:first-child,
.table-bordered tbody:first-child tr:first-child td:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px; }
/* line 99, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered thead:first-child tr:first-child th:last-child,
.table-bordered tbody:first-child tr:first-child td:last-child {
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-moz-border-radius-topright: 4px; }
/* line 107, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered thead:last-child tr:last-child th:first-child,
.table-bordered tbody:last-child tr:last-child td:first-child,
.table-bordered tfoot:last-child tr:last-child td:first-child {
-webkit-border-radius: 0 0 0 4px;
-moz-border-radius: 0 0 0 4px;
border-radius: 0 0 0 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px; }
/* line 115, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered thead:last-child tr:last-child th:last-child,
.table-bordered tbody:last-child tr:last-child td:last-child,
.table-bordered tfoot:last-child tr:last-child td:last-child {
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-moz-border-radius-bottomright: 4px; }
/* line 125, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered caption + thead tr:first-child th:first-child,
.table-bordered caption + tbody tr:first-child td:first-child,
.table-bordered colgroup + thead tr:first-child th:first-child,
.table-bordered colgroup + tbody tr:first-child td:first-child {
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px; }
/* line 133, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-bordered caption + thead tr:first-child th:last-child,
.table-bordered caption + tbody tr:first-child td:last-child,
.table-bordered colgroup + thead tr:first-child th:last-child,
.table-bordered colgroup + tbody tr:first-child td:last-child {
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-moz-border-radius-topright: 4px; }
/* line 151, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-striped tbody tr:nth-child(odd) td,
.table-striped tbody tr:nth-child(odd) th {
background-color: #f9f9f9; }
/* line 164, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-hover tbody tr:hover td,
.table-hover tbody tr:hover th {
background-color: whitesmoke; }
/* line 178, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
table td[class*="span"],
table th[class*="span"],
.row-fluid table td[class*="span"],
.row-fluid table th[class*="span"] {
display: table-cell;
float: none;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span1,
.table th.span1 {
float: none;
width: 44px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span2,
.table th.span2 {
float: none;
width: 124px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span3,
.table th.span3 {
float: none;
width: 204px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span4,
.table th.span4 {
float: none;
width: 284px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span5,
.table th.span5 {
float: none;
width: 364px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span6,
.table th.span6 {
float: none;
width: 444px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span7,
.table th.span7 {
float: none;
width: 524px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span8,
.table th.span8 {
float: none;
width: 604px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span9,
.table th.span9 {
float: none;
width: 684px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span10,
.table th.span10 {
float: none;
width: 764px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span11,
.table th.span11 {
float: none;
width: 844px;
margin-left: 0; }
/* line 188, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table td.span12,
.table th.span12 {
float: none;
width: 924px;
margin-left: 0; }
/* line 199, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table tbody tr.success td {
background-color: #dff0d8; }
/* line 202, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table tbody tr.error td {
background-color: #f2dede; }
/* line 205, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table tbody tr.warning td {
background-color: #fcf8e3; }
/* line 208, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table tbody tr.info td {
background-color: #d9edf7; }
/* line 215, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-hover tbody tr.success:hover td {
background-color: #d0e9c6; }
/* line 218, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-hover tbody tr.error:hover td {
background-color: #ebcccc; }
/* line 221, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-hover tbody tr.warning:hover td {
background-color: #faf2cc; }
/* line 224, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_tables.scss */
.table-hover tbody tr.info:hover td {
background-color: #c4e3f3; }
/* line 8, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropup,
.dropdown {
position: relative; }
/* line 11, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-toggle {
*margin-bottom: -3px; }
/* line 16, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-toggle:active,
.open .dropdown-toggle {
outline: 0; }
/* line 22, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.caret {
display: inline-block;
width: 0;
height: 0;
vertical-align: top;
border-top: 4px solid black;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
content: ""; }
/* line 34, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown .caret {
margin-top: 8px;
margin-left: 2px; }
/* line 41, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
background-color: white;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box; }
/* line 64, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu.pull-right {
right: 0;
left: auto; }
/* line 70, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu .divider {
*width: 100%;
height: 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
border-bottom: 1px solid white; }
/* line 75, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 20px;
color: #333333;
white-space: nowrap; }
/* line 90, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu li > a:hover,
.dropdown-menu li > a:focus,
.dropdown-submenu:hover > a {
text-decoration: none;
color: white;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF0088CC', endColorstr='#FF0077B3', GradientType=0); }
/* line 99, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu .active > a,
.dropdown-menu .active > a:hover {
color: #333333;
text-decoration: none;
outline: 0;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF0088CC', endColorstr='#FF0077B3', GradientType=0); }
/* line 110, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu .disabled > a,
.dropdown-menu .disabled > a:hover {
color: #999999; }
/* line 114, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-menu .disabled > a:hover {
text-decoration: none;
background-color: transparent;
background-image: none;
cursor: default; }
/* line 123, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.open {
*z-index: 1000; }
/* line 128, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.open > .dropdown-menu {
display: block; }
/* line 135, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.pull-right > .dropdown-menu {
right: 0;
left: auto; }
/* line 147, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropup .caret,
.navbar-fixed-bottom .dropdown .caret {
border-top: 0;
border-bottom: 4px solid black;
content: ""; }
/* line 153, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropup .dropdown-menu,
.navbar-fixed-bottom .dropdown .dropdown-menu {
top: auto;
bottom: 100%;
margin-bottom: 1px; }
/* line 162, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu {
position: relative; }
/* line 166, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu > .dropdown-menu {
top: 0;
left: 100%;
margin-top: -6px;
margin-left: -1px;
-webkit-border-radius: 0 6px 6px 6px;
-moz-border-radius: 0 6px 6px 6px;
border-radius: 0 6px 6px 6px; }
/* line 175, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu:hover > .dropdown-menu {
display: block; }
/* line 180, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropup .dropdown-submenu > .dropdown-menu {
top: auto;
bottom: 0;
margin-top: 0;
margin-bottom: -2px;
-webkit-border-radius: 5px 5px 5px 0;
-moz-border-radius: 5px 5px 5px 0;
border-radius: 5px 5px 5px 0; }
/* line 191, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu > a:after {
display: block;
content: " ";
float: right;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 5px 0 5px 5px;
border-left-color: #cccccc;
margin-top: 5px;
margin-right: -10px; }
/* line 204, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu:hover > a:after {
border-left-color: white; }
/* line 209, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu.pull-left {
float: none; }
/* line 215, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown-submenu.pull-left > .dropdown-menu {
left: -100%;
margin-left: 10px;
-webkit-border-radius: 6px 0 6px 6px;
-moz-border-radius: 6px 0 6px 6px;
border-radius: 6px 0 6px 6px; }
/* line 227, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.dropdown .dropdown-menu .nav-header {
padding-left: 20px;
padding-right: 20px; }
/* line 234, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_dropdowns.scss */
.typeahead {
margin-top: 2px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px; }
/* line 9, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav {
margin-left: 0;
margin-bottom: 20px;
list-style: none; }
/* line 16, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > li > a {
display: block; }
/* line 19, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > li > a:hover {
text-decoration: none;
background-color: #eeeeee; }
/* line 25, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > .pull-right {
float: right; }
/* line 30, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-header {
display: block;
padding: 3px 15px;
font-size: 11px;
font-weight: bold;
line-height: 20px;
color: #999999;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-transform: uppercase; }
/* line 41, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav li + .nav-header {
margin-top: 9px; }
/* line 50, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list {
padding-left: 15px;
padding-right: 15px;
margin-bottom: 0; }
/* line 56, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list > li > a,
.nav-list .nav-header {
margin-left: -15px;
margin-right: -15px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); }
/* line 61, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list > li > a {
padding: 3px 15px; }
/* line 65, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list > .active > a,
.nav-list > .active > a:hover {
color: white;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
background-color: #0088cc; }
/* line 71, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list [class^="icon-"],
.nav-list [class*=" icon-"] {
margin-right: 2px; }
/* line 75, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-list .divider {
*width: 100%;
height: 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
border-bottom: 1px solid white; }
/* line 86, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs,
.nav-pills {
*zoom: 1; }
/* line 15, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_mixins.scss */
.nav-tabs:before, .nav-tabs:after,
.nav-pills:before,
.nav-pills:after {
display: table;
content: "";
line-height: 0; }
/* line 22, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_mixins.scss */
.nav-tabs:after,
.nav-pills:after {
clear: both; }
/* line 90, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > li,
.nav-pills > li {
float: left; }
/* line 94, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > li > a,
.nav-pills > li > a {
padding-right: 12px;
padding-left: 12px;
margin-right: 2px;
line-height: 14px; }
/* line 105, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs {
border-bottom: 1px solid #ddd; }
/* line 109, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > li {
margin-bottom: -1px; }
/* line 113, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > li > a {
padding-top: 8px;
padding-bottom: 8px;
line-height: 20px;
border: 1px solid transparent;
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0; }
/* line 119, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > li > a:hover {
border-color: #eeeeee #eeeeee #dddddd; }
/* line 125, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs > .active > a,
.nav-tabs > .active > a:hover {
color: #555555;
background-color: white;
border: 1px solid #ddd;
border-bottom-color: transparent;
cursor: default; }
/* line 138, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-pills > li > a {
padding-top: 8px;
padding-bottom: 8px;
margin-top: 2px;
margin-bottom: 2px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px; }
/* line 148, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-pills > .active > a,
.nav-pills > .active > a:hover {
color: white;
background-color: #0088cc; }
/* line 159, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-stacked > li {
float: none; }
/* line 162, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-stacked > li > a {
margin-right: 0; }
/* line 167, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs.nav-stacked {
border-bottom: 0; }
/* line 170, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs.nav-stacked > li > a {
border: 1px solid #ddd;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0; }
/* line 174, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs.nav-stacked > li:first-child > a {
-webkit-border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
border-top-right-radius: 4px;
-webkit-border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
border-top-left-radius: 4px; }
/* line 177, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs.nav-stacked > li:last-child > a {
-webkit-border-bottom-right-radius: 4px;
-moz-border-radius-bottomright: 4px;
border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-left-radius: 4px; }
/* line 180, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs.nav-stacked > li > a:hover {
border-color: #ddd;
z-index: 2; }
/* line 186, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-pills.nav-stacked > li > a {
margin-bottom: 3px; }
/* line 189, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-pills.nav-stacked > li:last-child > a {
margin-bottom: 1px; }
/* line 198, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs .dropdown-menu {
-webkit-border-radius: 0 0 6px 6px;
-moz-border-radius: 0 0 6px 6px;
border-radius: 0 0 6px 6px; }
/* line 201, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-pills .dropdown-menu {
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px; }
/* line 208, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav .dropdown-toggle .caret {
border-top-color: #0088cc;
border-bottom-color: #0088cc;
margin-top: 6px; }
/* line 213, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav .dropdown-toggle:hover .caret {
border-top-color: #005580;
border-bottom-color: #005580; }
/* move down carets for tabs */
/* line 218, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs .dropdown-toggle .caret {
margin-top: 8px; }
/* line 224, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav .active .dropdown-toggle .caret {
border-top-color: #fff;
border-bottom-color: #fff; }
/* line 228, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs .active .dropdown-toggle .caret {
border-top-color: #555555;
border-bottom-color: #555555; }
/* line 235, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > .dropdown.active > a:hover {
cursor: pointer; }
/* line 243, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav-tabs .open .dropdown-toggle,
.nav-pills .open .dropdown-toggle,
.nav > li.dropdown.open.active > a:hover {
color: white;
background-color: #999999;
border-color: #999999; }
/* line 250, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav li.dropdown.open .caret,
.nav li.dropdown.open.active .caret,
.nav li.dropdown.open a:hover .caret {
border-top-color: white;
border-bottom-color: white;
opacity: 1;
filter: alpha(opacity=100); }
/* line 257, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-stacked .open > a:hover {
border-color: #999999; }
/* line 271, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabbable {
*zoom: 1; }
/* line 15, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_mixins.scss */
.tabbable:before, .tabbable:after {
display: table;
content: "";
line-height: 0; }
/* line 22, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_mixins.scss */
.tabbable:after {
clear: both; }
/* line 274, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tab-content {
overflow: auto; }
/* line 281, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs,
.tabs-right > .nav-tabs,
.tabs-left > .nav-tabs {
border-bottom: 0; }
/* line 287, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tab-content > .tab-pane,
.pill-content > .pill-pane {
display: none; }
/* line 291, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tab-content > .active,
.pill-content > .active {
display: block; }
/* line 299, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs {
border-top: 1px solid #ddd; }
/* line 302, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs > li {
margin-top: -1px;
margin-bottom: 0; }
/* line 306, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs > li > a {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px; }
/* line 308, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs > li > a:hover {
border-bottom-color: transparent;
border-top-color: #ddd; }
/* line 314, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-below > .nav-tabs > .active > a,
.tabs-below > .nav-tabs > .active > a:hover {
border-color: transparent #ddd #ddd #ddd; }
/* line 323, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs > li,
.tabs-right > .nav-tabs > li {
float: none; }
/* line 327, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs > li > a,
.tabs-right > .nav-tabs > li > a {
min-width: 74px;
margin-right: 0;
margin-bottom: 3px; }
/* line 334, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs {
float: left;
margin-right: 19px;
border-right: 1px solid #ddd; }
/* line 339, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs > li > a {
margin-right: -1px;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px; }
/* line 343, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs > li > a:hover {
border-color: #eeeeee #dddddd #eeeeee #eeeeee; }
/* line 347, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-left > .nav-tabs .active > a,
.tabs-left > .nav-tabs .active > a:hover {
border-color: #ddd transparent #ddd #ddd;
*border-right-color: white; }
/* line 353, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-right > .nav-tabs {
float: right;
margin-left: 19px;
border-left: 1px solid #ddd; }
/* line 358, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-right > .nav-tabs > li > a {
margin-left: -1px;
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0; }
/* line 362, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-right > .nav-tabs > li > a:hover {
border-color: #eeeeee #eeeeee #eeeeee #dddddd; }
/* line 366, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.tabs-right > .nav-tabs .active > a,
.tabs-right > .nav-tabs .active > a:hover {
border-color: #ddd #ddd #ddd transparent;
*border-left-color: white; }
/* line 377, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > .disabled > a {
color: #999999; }
/* line 381, ../../../../../../../.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/bootstrap-sass-2.2.1.1/vendor/assets/stylesheets/bootstrap/_navs.scss */
.nav > .disabled > a:hover {
text-decoration: none;
background-color: transparent;
cursor: default; }
.role-tab {
padding-bottom: 200px !important;
overflow-x: scroll;
}
.manage-permissions{
thead th{
text-align: center;
white-space: nowrap;
width: 0;
}
td {
vertical-align: middle;
}
th{
width: 20%;
}
&.toolbar{
th, td {
background-color:transparent !important;
&:hover{
background-color:transparent !important;
}
}
}
}

View File

@ -0,0 +1,2 @@
<div class="new-role"></div>
<table id="account_roles" class='table table-striped table-hover table-condensed'></table>

View File

@ -0,0 +1,2 @@
<div class="new-role"></div>
<table id="course_roles" class='table table-striped table-hover table-condensed'></table>

View File

@ -0,0 +1,5 @@
<thead>
<tr></tr>
</thead>
<tbody></tbody>

View File

@ -0,0 +1,14 @@
<div class="btn-group">
<a class="btn btn-success dropdown-toggle" aria-role="link" href="#" data-toggle="dropdown" aria-haspopup="true">Add Role</a>
<div class="dropdown-menu bootstrap-form new-role" aria-hidden="true">
<form>
<input type='text' aria-label="Role Name" tabindex=1 placeholder="Role name"></input>
<select aria-label="Role Basetype" tabindex=2 {{#if adminRoles }}disabled{{/if}}>
{{#each base_role_types}}
<option role="menuitem" value="{{value}}">{{ label }}</option>
{{/each}}
</select>
<button type="submit" class="btn btn-success" aria-label="Add Role" tabindex=3>Add</button>
<form>
</div>
</div>

View File

@ -0,0 +1,25 @@
<div class="btn-group">
<a class="btn btn-small dropdown-toggle {{#if default }}default_permission{{/if}}" href="#" data-toggle="dropdown" {{#if readOnly }}disabled="disabled" data-tooltip title="You do not have permission to change this" {{/if}}><i class="icon-check"></i><i class="icon-lock"></i></a>
<div class="dropdown-menu" role="radiogroup">
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-0" {{#if enableChecked }}checked="checked"{{/if}} >
<label tabindex="0" for="button-{{cid}}-0"><i class="icon-check"></i></i><span class="text">{{#t "rolebutton.enable"}}Enable{{/t}}</span></label>
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-1" {{#if enableAndLockChecked }}checked="checked"{{/if}}>
<label tabindex=1 for="button-{{cid}}-1"><i class="icon-check"></i><i class="icon-lock"></i><span class="text">{{#t "rolebutton.enable_and_lock"}}Enable and Lock{{/t}}</span></label>
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-2" {{#if disableChecked }}checked="checked"{{/if}}>
<label tabindex=2 for="button-{{cid}}-2"><i class="icon-x"></i><span class="text">{{#t "rolebutton.disable"}}Disable{{/t}}</span></label>
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-3" {{#if disableAndLockChecked }}checked="checked"{{/if}}>
<label tabindex=3 for="button-{{cid}}-3"><i class="icon-x"></i><i class="icon-lock"></i><span class="text">{{#t "rolebutton.disable_and_lock"}}Disable and Lock{{/t}}</span></label>
<li class="divider"></li>
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-4" {{#if systemDefaultChecked }}checked="checked"{{/if}}>
<label tabindex=4 for="button-{{cid}}-4" class="clearfix"><span class="text">{{#t "rolebutton.use_default"}}Use Default{{/t}}</span></label>
<input type="radio" name="permission-for-{{cid}}" value="0" role="radio" id="button-{{cid}}-5" {{#if systemDefaultLockedChecked }}checked="checked"{{/if}}>
<label tabindex=5 for="button-{{cid}}-5" class="clearfix"><i class="icon-lock"></i><span class="text">{{#t "rolebutton.use_default"}}Use Default{{/t}}</span></label>
</div>
</div>

View File

@ -0,0 +1,2 @@
<em data-tooltip title="Based on {{base_role_type}}">{{label}}</em>
{{#if deletable}}<a href="#" aria-label="Delete role {{role}}"><i class="icon-end standalone-icon" ></i></a>{{/if}}

View File

@ -0,0 +1,9 @@
<div id="role_tabs" class="ui-tabs-minimal manage-permissions">
<ul>
<li><a href="#course-roles-tab" id="course_role_link">Course Roles</a></li>
<li><a href="#account-roles-tab" id="account_role_link">Account Roles</a></li>
</ul>
<div id="course-roles-tab" class="course-roles role-tab"></div>
<div id="account-roles-tab" class="account-roles role-tab"></div>
</div>

View File

@ -1,224 +1,7 @@
<% @active_tab = "permissions" %>
<% add_crumb t 'crumbs.permissions', "Permissions" %>
<% content_for :page_title do %><%= t :page_title, "Manage Permissions for %{account}", :account => @context.name %><% end %>
<% content_for :page_header do %>
<h1><%= t 'titles.manage_permissions', "Manage Permissions" %></h1>
<% end %>
<%
@body_classes << "full-width"
add_crumb t 'crumbs.permissions', "Permissions"
content_for :page_title, t(:page_title, "Manage Permissions for %{account}", :account => @context.name)
js_bundle :roles
%>
<% content_for :page_subhead do %>
<h2><%= t 'titles.subhead', "Set what users are allowed to do based on their role in the course." %></h2>
<% end %>
<style type="text/css" media="screen">
#permissions-table thead th{
padding: 4px 10px;
font-size: 1.0em;
min-width: 75px;
text-align: center;
}
#permissions-table tbody th {
text-align: left;
font-size: 12px;
}
#permissions-table tbody td {
text-align: center;
}
.lock{
height: 16px;
opacity: 0;
-moz-opacity: 0;
filter:alpha(opacity=0);
}
td:hover .lock{
opacity: 0.4;
-moz-opacity: 0.4;
filter:alpha(opacity=40);
cursor: pointer;
}
img.lock.locked{
opacity: 1;
-moz-opacity: 1;
filter:alpha(opacity=1);
}
img.lock.disabled{
opacity: 0;
-moz-opacity: 0;
filter:alpha(opacity=0);
cursor: auto;
}
#permissions-table.zebra-stripes tbody tr:hover {
background: #DCEFFB
}
</style>
<h2><%= @context.class.to_s %> <%= t('titles.permissions', 'Permissions') %></h2>
<div style="margin:1em;">
<div class="ui-widget ui-widget-content ui-corner-all" style="margin-bottom:1em; padding: 1em">
<h3><%= t 'titles.explanation', "Explanation:" %></h3>
<%= t :explanation_defaults, "The partially transparent checkboxes (%{default_checked} and %{default_unchecked}) indicate the system default.",
:default_checked => '<span class="six-checkbox six-checkbox-default-checked"></span>'.html_safe, :default_unchecked => '<span class="six-checkbox six-checkbox-default-unchecked"></span>'.html_safe %><br />
<%= mt :explanation_normal, "You can click to override the default and explicitly **allow** (%{normal_checked}) or **deny** (%{normal_unchecked}) this action.",
:normal_checked => '<span class="six-checkbox six-checkbox-checked"></span>'.html_safe, :normal_unchecked => '<span class="six-checkbox six-checkbox-unchecked"></span>'.html_safe %><br />
<%= t :explanation_disabled, "A grayed out box (%{disabled_checked} or %{disabled_unchecked}) means you are not allowed to change it.",
:disabled_checked => '<span class="six-checkbox six-checkbox-disabled-checked"></span>'.html_safe, :disabled_unchecked => '<span class="six-checkbox six-checkbox-disabled-unchecked"></span>'.html_safe %><br />
<%= t :explanation_locked, "If you would like to prevent anyone from overriding this in any sub-accounts, click the lock (%{lock}) icon.",
:lock => image_tag("locked_small.png", :class => 'lock locked') %>
</div>
<div style="text-align: right;">
<% if !@context.site_admin? %>
<% if @managing_account_roles %>
<a href="<%= context_url(@context, :context_permissions_url) %>"><%= t 'links.manage_course_roles', "Manage Course-Level Roles" %></a>
<% else %>
<a id="course_level_roles" href="<%= context_url(@context, :context_permissions_url, :account_roles => 1) %>"><%= t 'links.manage_accoiunt_roles', "Manage Account-Level Roles" %></a>
<% end %>
<% end %>
</div>
<% if @managing_account_roles %>
<table id="account_roles" style="margin-bottom: 10px;" class='ui-widget ui-widget-content ui-corner-all zebra-stripes'>
<thead class="ui-widget-header">
<tr>
<th><%= t 'headers.account_role_type', "Account Role Type" %></th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<% @context.available_account_roles.each do |type| %>
<tr class="<%= cycle("even", "odd") %>">
<td style="padding: 1px 3px;"><%= AccountUser.readable_type type %></td>
<td style="padding: 1px 3px; text-align: right;">
<%= link_to("<i class='icon-end standalone-icon'></i>".html_safe, context_url(@context, :remove_role_context_permissions_url, :role => type, :account_roles => params[:account_roles]), :class => "no-hover remove_role_link", :method => :delete, :confirm => t('prompts.delete_role_type', "Are you sure you want to delete this role type?")) unless type == 'AccountAdmin' %>
</td>
</tr>
<% end %>
<tr>
<td colspan="2" style="padding: 2px 3px;">
<%form_tag(context_url(@context, :add_role_context_permissions_url)) do %>
<input type="hidden" name="account_roles" value="1"/>
<%= before_label :new_role_type, "New Role Type" %>
<input class= "add_new_role" type="text" name="role_type"/>
<button id="add_new_role_button" class="btn btn-small" type="submit"><i class="icon-add"></i> <%= t '#buttons.add', "Add" %></button>
<% end %>
</td>
</tr>
</tbody>
</table>
<% end %>
<% form_tag(context_url(@context, :context_permissions_url, :account_roles => params[:account_roles])) do %>
<table cellspacing="0" id="permissions-table" class='ui-widget ui-widget-content ui-corner-all zebra-stripes'>
<thead class='ui-widget-header'>
<tr>
<th></th>
<% @role_types.each do |enrollment_type| %>
<th><%= enrollment_type[:label].respond_to?(:call) ? enrollment_type[:label].call() : enrollment_type[:label] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% permissions = RoleOverride.manageable_permissions(@context) %>
<% permissions.reject! { |k, p| (p[:available_to] & RoleOverride.enrollment_types.map { |role| role[:name] }).empty? unless @managing_account_roles } %>
<% permissions.to_a.sort { |a, b| a.last[:label].call() <=> b.last[:label].call() } %>
<% site_admin_permissions, permissions = permissions.partition { |(k, p)| p[:account_only] == :site_admin } %>
<% account_permissions, permissions = permissions.partition { |(k, p)| p[:account_only] } %>
<% unless site_admin_permissions.empty? -%>
<tr class='ui-widget-header'>
<td><%= t 'headers.site_admin_permissions', "Site Admin Permissions" %></td>
<td colspan='<%= @role_types.size %>'></td>
</tr>
<%= render :partial => 'permission', :collection => site_admin_permissions %>
<% end -%>
<% unless account_permissions.empty? -%>
<tr class='ui-widget-header'>
<td><%= t 'headers.account_permissions', "Account Permissions" %></td>
<td colspan='<%= @role_types.size %>'></td>
</tr>
<%= render :partial => 'permission', :collection => account_permissions %>
<% end -%>
<tr class='ui-widget-header'>
<td><%= t 'headers.course_permissions', "Course & Account Permissions" %></td>
<td colspan='<%= @role_types.size %>'></td>
</tr>
<%= render :partial => 'permission', :collection => permissions %>
</tbody>
</table>
<div class="button-container">
<a class="btn cancel_button" href="<%= url_for(:back) %>"><%= t '#buttons.cancel', "Cancel" %></a>
<button type="submit" class="btn btn-primary save_permissions_changes"><%= t 'buttons.save', "Save Changes" %></button>
</div>
<% end -%>
</div>
<% js_block do %>
<script type="text/javascript">
require([
'jquery' /* $ */,
'jquery.instructure_misc_plugins' /* /\.log\(/ */
], function($) {
$('.six-checkbox').click(function(){
var $this = $(this);
if (!$this.hasClass('six-checkbox-disabled-checked') && !$this.hasClass('six-checkbox-disabled-unchecked') ) { //if its not disabled
if (!$this.hasClass("six-checkbox-checked") && !$this.hasClass("six-checkbox-unchecked")) { //if it is not explicitly set
if ( $this.hasClass("six-checkbox-default-checked") || $this.hasClass("six-checkbox-default-unchecked") ) {
$this
.addClass("six-checkbox-checked")
.find("input")
.val('checked');
} else {
$this
.addClass("six-checkbox-checked")
.find("input")
.val('checked');
}
}
else { // it IS explicity set
if ($this.hasClass("six-checkbox-checked")) {
$this
.removeClass("six-checkbox-checked")
.addClass("six-checkbox-unchecked")
.find("input")
.val('unchecked');
}
else { //if ($this.hasClass("six-checkbox-unchecked")) {
$this
.removeClass("six-checkbox-unchecked")
.find("input")
.val('');
}
}
}
});
$.fn.setLockTitle = function(){
$(this).attr('title',
$(this).hasClass('locked') ?
<%= t('tooltips.locked', "This permission is locked, no one can override what is set here.").to_json.html_safe %> :
<%= t('tooltips.lock', "Click to not allow anyone to override what is set here.").to_json.html_safe %>
);
return this;
};
$('#permissions-table .lock:not(".disabled")')
.setLockTitle()
.click(function(){
$(this)
.toggleClass('locked')
.setLockTitle()
.next('input')
.val(
$(this).hasClass('locked')
);
// // uncomment this part if you want it to automatically check boxes that are left at the default if they are locked
// var $checkbox = $(this).parent().find('.six-checkbox');
// $.each(['checked', 'unchecked'], function(){
// console.log(this)
// if ( $checkbox.hasClass('six-checkbox-default-' + this) ) {
// $checkbox.addClass("six-checkbox-" + this)
// .find("input")
// .val(this);
// }
// });
});
});
</script>
<% end %>

View File

@ -20,10 +20,11 @@ module Api::V1::Role
include Api::V1::Json
include Api::V1::Account
def role_json(account, role, current_user, session)
def role_json(account, role, current_user, session, opts={})
json = {
:account => account_json(account, current_user, session, []),
:role => role.name,
:label => role.label,
:base_role_type => role.base_role_type,
:workflow_state => role.workflow_state,
:permissions => {}

View File

@ -394,7 +394,7 @@ describe "Roles API", :type => :integration do
describe "json response" do
it "should return the expected json format" do
json = api_call_with_settings
json.keys.sort.should == ["account", "base_role_type", "permissions", "role", "workflow_state"]
json.keys.sort.should == ["account", "base_role_type", "label", "permissions", "role", "workflow_state"]
json["account"].should == {
"name" => @account.name,
"root_account_id" => @account.root_account_id,

View File

@ -0,0 +1,59 @@
define [
'Backbone'
'underscore'
'compiled/models/Role'
'compiled/collections/RolesCollection'
'compiled/util/BaseRoleTypes'
], (Backbone,_, Role, RolesCollection) ->
module 'RolesCollection',
setup: ->
@account_id = 2
teardown: ->
@account_id = null
test "generate the correct url for a collection of roles", 1, ->
roles_collection = new RolesCollection null
contextAssetString: "account_#{@account_id}"
equal roles_collection.url(), "/api/v1/accounts/#{@account_id}/roles", "roles collection url"
test "fetches a collection of roles", 1, ->
server = sinon.fakeServer.create()
role1 = new Role
role2 = new Role
roles_collection = new RolesCollection null
contextAssetString: "account_#{@account_id}"
roles_collection.fetch success: =>
equal roles_collection.size(), 2, "Adds all of the roles to the collection"
server.respond 'GET', roles_collection.url(), [200, {
'Content-Type': 'application/json'
}, JSON.stringify([role1, role2])]
server.restore()
test "keeps roles in order based on sort order", 3, ->
RolesCollection.sortOrder = [
"AccountMembership"
"StudentEnrollment"
"TaEnrollment"
]
roleFirst = new Role base_role_type : "AccountMembership"
roleSecond = new Role base_role_type : "StudentEnrollment"
roleThird = new Role base_role_type : "TaEnrollment"
roleCollection = new RolesCollection([roleThird, roleFirst, roleSecond])
equal roleCollection.models[0], roleFirst, "First role is in order"
equal roleCollection.models[1], roleSecond, "Second role is in order"
equal roleCollection.models[2], roleThird, "Third role is in order"

View File

@ -0,0 +1,36 @@
define [
'Backbone'
'underscore'
'compiled/models/Role'
'compiled/models/Account'
'compiled/util/BaseRoleTypes'
], (Backbone,_, Role, Account, BASE_ROLE_TYPES) ->
module 'RoleModel',
setup: ->
@account = new Account id: 4
@role = new Role account: @account
@server = sinon.fakeServer.create()
teardown: ->
@server.restore()
@role = null
@account_id = null
test 'the id gets set to the role name when role is created', 1, ->
@role.fetch success: =>
equal @role.get('id'), "existingRole", "Role id should equal the roles name"
@server.respond 'GET', @role.url(), [200, {
'Content-Type': 'application/json'
}, JSON.stringify({"role": "existingRole"})]
test 'generates the correct url for existing and non-existing roles', 2, ->
equal @role.url(), "/api/v1/accounts/#{@account.get('id')}/roles", "non-existing role url"
@role.fetch success: =>
equal @role.url(), "/api/v1/accounts/#{@account.get('id')}/roles/existingRole", "existing role url"
@server.respond 'GET', @role.url(), [200, {
'Content-Type': 'application/json'
}, JSON.stringify({"role": "existingRole", "account" : @account})]

View File

@ -96,11 +96,13 @@ describe RoleOverridesController do
@existing_override.locked.should be_true
end
it "should only update the parts that are specified" do
it "only updates unchecked" do
post_with_settings(:override => 'unchecked')
@existing_override.reload
@existing_override.locked.should be_false
end
it "only updates enabled" do
@existing_override.enabled = true
@existing_override.save
@ -142,14 +144,16 @@ describe RoleOverridesController do
override.locked.should be_true
end
it "should only set the parts that are specified" do
it "sets override as false when override is unchecked" do
post_with_settings(:override => 'unchecked')
override = @account.role_overrides(true).find_by_permission_and_enrollment_type(@permission, @role)
override.should_not be_nil
override.enabled.should be_false
override.locked.should be_nil
override.destroy
end
it "sets the override to locked when specifiying locked" do
post_with_settings(:locked => 'true')
override = @account.role_overrides(true).find_by_permission_and_enrollment_type(@permission, @role)
override.should_not be_nil

View File

@ -0,0 +1,22 @@
#
# Copyright (C) 2012 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe RoleOverridesHelper do
end

View File

@ -0,0 +1,19 @@
(function() {
define(['Backbone', 'underscore', 'compiled/models/Role', 'compiled/collections/RolesCollection', 'compiled/util/BaseRoleTypes'], function(Backbone, _, Role, RolesCollection, BASE_ROLE_TYPES) {
module('RolesCollection', {
setup: function() {
return this.account_id = 2;
},
teardown: function() {
return this.account_id = null;
}
});
return test("generate the correct url for a collection of roles", 1, function() {
var roles_collection;
roles_collection = new RolesCollection;
return equal(roles_collection.url(), "/accounts/" + this.account_id + "/roles", "roles collection url");
});
});
}).call(this);

View File

@ -5,123 +5,215 @@ shared_examples_for "permission tests" do
course_with_admin_logged_in
end
def has_default_permission?(permission, enrollment_type)
default_permissions = Permissions.retrieve
default_permissions[permission][:true_for].include?(enrollment_type)
def select_enable(permission_name, role_name)
permission_button = fj("[data-permission_name='#{permission_name}'].[data-role_name='#{role_name}']")
permission_button.find_element(:css, "a").click
options = permission_button.find_elements(:css, ".dropdown-menu label")
options[0].click # 0 is Enable
wait_for_ajax_requests #Every select needs to wait for for the request to finish
end
def get_updated_role(permission, enrollment_type)
RoleOverride.first(:conditions => {:permission => permission.to_s, :enrollment_type => enrollment_type})
end
def select_enable_and_lock(permission_name, role_name)
permission_button = fj("[data-permission_name='#{permission_name}'].[data-role_name='#{role_name}']")
permission_button.find_element(:css, "a").click
options = permission_button.find_elements(:css, ".dropdown-menu label")
options[1].click # 1 is enabled and locked
def get_checkbox(permission, selector, enrollment_type)
row = nil
permissions = ff("#permissions-table tr > th")
permissions.each do |elem|
if (elem.text.include? permission)
row = elem.find_element(:xpath, "..")
break
end
end
#enrollment type is the number corresponding to the role i.e. Student = 0, Ta = 1, Teacher = 2...
row.find_elements(:css, selector)[enrollment_type]
end
def checkbox_verifier(permission, enrollment_type, disable_permission = false, locked = false)
selector = locked ? ".lock" : ".six-checkbox"
#get the element we need
check_box = get_checkbox(permission, selector, enrollment_type)
#we iterate according to the permission event
iterate = disable_permission ? 2 : 1
iterate.times { check_box.click }
f(".save_permissions_changes").click
wait_for_ajax_requests
check_box = get_checkbox(permission, selector, enrollment_type)
if (locked)
check_box.find_element(:xpath, "..").find_element(:css, "input").should have_value("true")
else
if (disable_permission)
check_box.find_element(:css, "input").should have_value("unchecked")
else
check_box.find_element(:css, "input").should have_value("checked")
end
end
end
def permissions_verifier(opts, default_permitted = false, disable_permission = false, locked = false)
if (default_permitted)
has_default_permission?(opts.keys[0], opts.values[0]).should be_true
else
has_default_permission?(opts.keys[0], opts.values[0]).should be_false
end
role = get_updated_role(opts.keys[0], opts.values[0])
role.should be_present
if (disable_permission)
role.enabled.should be_false
else
if (!locked)
role.enabled.should be_true
end
end
if (locked)
role.locked.should be_true
else
role.locked.should be_false
end
def select_disable(permission_name, role_name)
permission_button = fj("[data-permission_name='#{permission_name}'].[data-role_name='#{role_name}']")
permission_button.find_element(:css, "a").click
options = permission_button.find_elements(:css, ".dropdown-menu label")
options[2].click # 2 is Disabled
wait_for_ajax_requests
end
describe "new role permissions" do
before (:each) do
def select_disable_and_lock(permission_name, role_name)
permission_button = fj("[data-permission_name='#{permission_name}'].[data-role_name='#{role_name}']")
permission_button.find_element(:css, "a").click
options = permission_button.find_elements(:css, ".dropdown-menu label")
options[3].click # 3 is Disabled and locked
wait_for_ajax_requests
end
def add_new_account_role(role_name)
role = account.roles.build name: role_name
role.base_role_type = "AccountMembership"
role.save!
role
end
def add_new_course_role(role_name, role_type = "StudentEnrollment")
role = account.roles.build name: role_name
role.base_role_type = role_type
role.save!
role
end
describe "Adding new roles" do
before do
get url
end
def add_new_role(role)
f(".add_new_role").send_keys(role)
f("#add_new_role_button").click
it "adds a new account role" do
role_name = "an account role"
f("#account_role_link").click
f('#account-roles-tab .new-role a.dropdown-toggle').click
f('#account-roles-tab .new-role form input').send_keys(role_name)
f('#account-roles-tab .new-role button').click
wait_for_ajax_requests
account.reload
account.available_account_roles.should include(role)
f("#permissions-table tr").should include_text(role)
f('#account-roles-tab').should include_text(role_name)
end
it "should add a new account role type" do
role = "New Role"
add_new_role(role)
end
it "adds a new course role" do
role_name = "a course role"
it "should delete an added role" do
role = "New Role"
add_new_role(role)
f(".remove_role_link").click
driver.switch_to.alert.accept
f("#course_role_link").click
f('#course-roles-tab .new-role a.dropdown-toggle').click
f('#course-roles-tab .new-role form input').send_keys(role_name)
f('#course-roles-tab .new-role button').click
wait_for_ajax_requests
account.reload
account.roles.active.find_by_name(role).should be_nil
f("#permissions-table tr").should_not include_text(role)
end
it "should enable manage permissions of new role" do
role = "New Role"
add_new_role(role)
checkbox_verifier("Manage permissions", 1)
opts = {:manage_role_overrides => role}
permissions_verifier(opts)
end
it "should disable manage permissions of new role" do
role = "New Role"
add_new_role(role)
checkbox_verifier("Manage permissions", 1, true)
opts = {:manage_role_overrides => role}
permissions_verifier(opts, false, true)
end
it "should lock manage permissions of new role" do
role = "New Role"
add_new_role(role)
checkbox_verifier("Manage permissions", 1, false, true)
opts = {:manage_role_overrides => role}
permissions_verifier(opts, false, false, true)
f('#course-roles-tab').should include_text(role_name)
end
end
end
describe "Removing roles" do
context "when deleting account roles" do
let!(:role_name) {"delete this account role"}
before do
add_new_account_role role_name
get url
end
it "deletes a role" do
f("#account_role_link").click
f(".roleHeader a").click
driver.switch_to.alert.accept
wait_for_ajax_requests
f('#account-roles-tab').should_not include_text(role_name)
end
end
context "when deleting course roles" do
let!(:role_name) {"delete this course role"}
before do
add_new_course_role role_name
get url
end
it "deletes a role" do
f("#course_role_link").click
f(".roleHeader a").click
driver.switch_to.alert.accept
wait_for_ajax_requests
f('#course-roles-tab').should_not include_text(role_name)
end
end
end
describe "Managing roles" do
context "when managing account roles" do
let!(:role_name) {"TestAcccountRole"}
let!(:permission_name) {"read_sis"} # Everyone should have this permission
let!(:role) { add_new_account_role role_name }
before do
get url
f("#account_role_link").click
end
it "enables a permission" do
select_enable(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_true
role_override.locked.should be_false
end
it "locks and enables a permission" do
select_enable_and_lock(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_true
role_override.locked.should be_true
end
it "disables a permission" do
select_disable(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_false
role_override.locked.should be_false
end
it "locks and disables a permission" do
select_disable_and_lock(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_false
role_override.locked.should be_true
end
it "sets a permission to default"
it "sets a permission to default and locked"
end
context "when managing course roles" do
let!(:role_name) {"TestCourseRole"}
let!(:permission_name) {"read_sis"} # Everyone should have this permission
let!(:role) { add_new_course_role role_name }
before do
f("#course_role_link").click
get url
end
it "enables a permission" do
select_enable(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_true
role_override.locked.should be_false
end
it "locks and enables a permission" do
select_enable_and_lock(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_true
role_override.locked.should be_true
end
it "disables a permission" do
select_disable(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_false
role_override.locked.should be_false
end
it "locks and disables a permission" do
select_disable_and_lock(permission_name, role_name)
role_override = RoleOverride.find_by_enrollment_type(role.name)
role_override.enabled.should be_false
role_override.locked.should be_true
end
it "sets a permission to default"
it "sets a permission to default and locked"
end
end
end