Adding admin roles page
parent
0369a35228
commit
62f31aeab3
|
@ -97,6 +97,9 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide) ->
|
|||
$routeProvider.when("/project/:pslug/admin/memberships",
|
||||
{templateUrl: "/partials/admin-memberships.html"})
|
||||
|
||||
$routeProvider.when("/project/:pslug/admin/roles",
|
||||
{templateUrl: "/partials/admin-roles.html"})
|
||||
|
||||
# User settings
|
||||
$routeProvider.when("/project/:pslug/user-settings/user-profile",
|
||||
{templateUrl: "/partials/user-profile.html"})
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# This program 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, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program 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/>.
|
||||
#
|
||||
# File: modules/admin/memberships.coffee
|
||||
###
|
||||
|
||||
taiga = @.taiga
|
||||
|
||||
mixOf = @.taiga.mixOf
|
||||
bindOnce = @.taiga.bindOnce
|
||||
|
||||
module = angular.module("taigaAdmin")
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Project Roles Controller
|
||||
#############################################################################
|
||||
|
||||
class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
|
||||
@.$inject = [
|
||||
"$scope",
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"$tgConfirm",
|
||||
"$tgResources",
|
||||
"$routeParams",
|
||||
"$q",
|
||||
"$tgLocation"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->
|
||||
_.bindAll(@)
|
||||
|
||||
@scope.sectionName = "Roles" #i18n
|
||||
@scope.project = {}
|
||||
|
||||
promise = @.loadInitialData()
|
||||
promise.then null, ->
|
||||
console.log "FAIL" #TODO
|
||||
|
||||
loadProject: ->
|
||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||
@scope.project = project
|
||||
return project
|
||||
|
||||
loadRoles: ->
|
||||
return @rs.roles.list(@scope.projectId).then (data) =>
|
||||
@scope.roles = data
|
||||
@scope.role = @scope.roles[0]
|
||||
return data
|
||||
|
||||
loadInitialData: ->
|
||||
promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
|
||||
@scope.projectId = data.project
|
||||
return data
|
||||
|
||||
return promise.then(=> @.loadProject())
|
||||
.then(=> @.loadUsersAndRoles())
|
||||
.then(=> @.loadRoles())
|
||||
|
||||
setRole: (role) ->
|
||||
@scope.role = role
|
||||
@scope.$broadcast("role:changed", @scope.role)
|
||||
|
||||
delete: ->
|
||||
# TODO: i18n
|
||||
title = "Delete Role"
|
||||
subtitle = @scope.role.name
|
||||
|
||||
@confirm.ask(title, subtitle).then =>
|
||||
promise = @repo.remove(@scope.role)
|
||||
promise.then =>
|
||||
@confirm.notify('success')
|
||||
@.loadRoles()
|
||||
promise.then null, =>
|
||||
@confirm.notify('error')
|
||||
|
||||
setComputable: ->
|
||||
onSuccess = (role) =>
|
||||
@confirm.notify('success')
|
||||
|
||||
onError = =>
|
||||
@confirm.notify("error")
|
||||
@scope.role.computable = !@scope.role.computable
|
||||
|
||||
@repo.save(@scope.role).then onSuccess, onError
|
||||
|
||||
|
||||
module.controller("RolesController", RolesController)
|
||||
|
||||
|
||||
RolesDirective = ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgRoles", RolesDirective)
|
||||
|
||||
NewRoleDirective = ($tgrepo) ->
|
||||
DEFAULT_PERMISSIONS = ["view_project", "view_milestones", "view_us", "view_tasks", "view_issues"]
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$el.on "click", "a.add-button", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find(".new").removeClass("hidden")
|
||||
$el.find(".new").focus()
|
||||
$el.find(".add-button").hide()
|
||||
|
||||
$el.on "keyup", ".new", (event) ->
|
||||
event.preventDefault()
|
||||
if event.keyCode == 13 # Enter key
|
||||
target = angular.element(event.currentTarget)
|
||||
newRole = {
|
||||
project: $scope.projectId
|
||||
name: target.val()
|
||||
permissions: DEFAULT_PERMISSIONS
|
||||
order: _.max($scope.roles, (r) -> r.order).order + 1
|
||||
computable: false
|
||||
}
|
||||
|
||||
$el.find(".new").addClass("hidden")
|
||||
$el.find(".new").val('')
|
||||
|
||||
$tgrepo.create("roles", newRole).then (role) ->
|
||||
$scope.roles.push(role)
|
||||
$ctrl.setRole(role)
|
||||
$el.find(".add-button").show()
|
||||
|
||||
else if event.keyCode == 27 # ESC key
|
||||
target = angular.element(event.currentTarget)
|
||||
$el.find(".new").addClass("hidden")
|
||||
$el.find(".new").val('')
|
||||
$el.find(".add-button").show()
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgNewRole", ["$tgRepo", NewRoleDirective])
|
||||
|
||||
|
||||
# Use category-config.scss styles
|
||||
RolePermissionsDirective = ($repo, $confirm) ->
|
||||
resumeTemplate = _.template("""
|
||||
<div class="resume-title"><%- category.name %></div>
|
||||
<div class="count"><%- category.activePermissions %>/<%- category.permissions.length %></div>
|
||||
<div class="summary-role">
|
||||
<% _.each(category.permissions, function(permission) { %>
|
||||
<div class="role-summary-single <% if(permission.active) { %>active<% } %>" title="<%- permission.description %>"></div>
|
||||
<% }) %>
|
||||
</div>
|
||||
<div class="icon icon-arrow-bottom"></div>
|
||||
""")
|
||||
|
||||
categoryTemplate = _.template("""
|
||||
<div class="category-config" data-id="<%- index %>">
|
||||
<div class="resume">
|
||||
</div>
|
||||
<div class="category-items">
|
||||
<div class="items-container">
|
||||
<% _.each(category.permissions, function(permission) { %>
|
||||
<div class="category-item" data-id="<%- permission.key %>"> <%- permission.description %>
|
||||
<div class="check">
|
||||
<input type="checkbox" <% if(permission.active) { %>checked="checked"<% } %>/>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
""")
|
||||
|
||||
baseTemplate = _.template("""
|
||||
<div class="category-config-list">
|
||||
</div>
|
||||
""")
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$ctrl = $el.controller()
|
||||
|
||||
generateCategoriesFromRole = (role) ->
|
||||
setActivePermissions = (permissions) ->
|
||||
return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions}))
|
||||
|
||||
setActivePermissionsPerCategory = (category) ->
|
||||
return _.map(category, (x) ->
|
||||
_.extend({}, x, {
|
||||
activePermissions: _.filter(x["permissions"], "active").length
|
||||
})
|
||||
)
|
||||
|
||||
categories = []
|
||||
|
||||
projectPermissions = [
|
||||
{ key: "view_project", description: "View project" }
|
||||
]
|
||||
categories.push({ name: "Project", permissions: setActivePermissions(projectPermissions) })
|
||||
|
||||
milestonePermissions = [
|
||||
{ key: "view_milestones", description: "View milestones" }
|
||||
{ key: "add_milestone", description: "Add milestone" }
|
||||
{ key: "modify_milestone", description: "Modify milestone" }
|
||||
{ key: "delete_last_milestone", description: "Delete last milestone" }
|
||||
{ key: "delete_milestone", description: "Delete milestone" }
|
||||
{ key: "add_us_to_milestone", description: "Add use to milestone" }
|
||||
{ key: "remove_us_from_milestone", description: "Remove us from milestone" }
|
||||
{ key: "reorder_us_on_milestone", description: "Reorder us on milestone" }
|
||||
]
|
||||
categories.push({ name: "Milestones", permissions: setActivePermissions(milestonePermissions) })
|
||||
|
||||
userStoryPermissions = [
|
||||
{ key: "view_us", description: "View user story" }
|
||||
{ key: "add_us", description: "Add user story" }
|
||||
{ key: "modify_us", description: "Modify user story" }
|
||||
{ key: "delete_us", description: "Delete user story" }
|
||||
]
|
||||
categories.push({ name: "User Stories", permissions: setActivePermissions(userStoryPermissions) })
|
||||
|
||||
taskPermissions = [
|
||||
{ key: "view_tasks", description: "View tasks" }
|
||||
{ key: "add_task", description: "Add task" }
|
||||
{ key: "modify_task", description: "Modify task" }
|
||||
{ key: "delete_task", description: "Delete task" }
|
||||
]
|
||||
categories.push({ name: "Tasks", permissions: setActivePermissions(taskPermissions) })
|
||||
|
||||
issuePermissions = [
|
||||
{ key: "view_issues", description: "View issues" }
|
||||
{ key: "add_issue", description: "Add issue" }
|
||||
{ key: "modify_issue", description: "Modify issue" }
|
||||
{ key: "delete_issue", description: "Delete issue" }
|
||||
{ key: "vote_issues", description: "Vote issues" }
|
||||
]
|
||||
categories.push({ name: "Issues", permissions: setActivePermissions(issuePermissions) })
|
||||
|
||||
wikiPermissions = [
|
||||
{ key: "view_wiki_pages", description: "View wiki pages" }
|
||||
{ key: "add_wiki_page", description: "Add wiki page" }
|
||||
{ key: "modify_wiki_page", description: "Modify wiki page" }
|
||||
{ key: "delete_wiki_page", description: "Delete wiki page" }
|
||||
{ key: "view_wiki_links", description: "View wiki links" }
|
||||
{ key: "add_wiki_link", description: "Add wiki link" }
|
||||
{ key: "modify_wiki_link", description: "Modify wiki link" }
|
||||
{ key: "delete_wiki_link", description: "Delete wiki link" }
|
||||
]
|
||||
categories.push({ name: "Wiki", permissions: setActivePermissions(wikiPermissions) })
|
||||
|
||||
return setActivePermissionsPerCategory(categories)
|
||||
|
||||
renderResume = (element, category) ->
|
||||
element.find(".resume").html(resumeTemplate({category: category}))
|
||||
|
||||
renderCategory = (category, index) ->
|
||||
html = categoryTemplate({category: category, index: index})
|
||||
html = angular.element(html)
|
||||
renderResume(html, category)
|
||||
return html
|
||||
|
||||
renderPermissions = () ->
|
||||
$el.off()
|
||||
html = baseTemplate()
|
||||
_.each generateCategoriesFromRole($scope.role), (category, index) ->
|
||||
html = angular.element(html).append(renderCategory(category, index))
|
||||
|
||||
$el.html(html)
|
||||
$el.on "click", ".resume", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
target.next().toggleClass("open")
|
||||
|
||||
$el.on "change", ".category-item input", (event) ->
|
||||
getActivePermissions = ->
|
||||
activePermissions = _.filter($el.find(".category-item input"), (t) -> angular.element(t).is(":checked"))
|
||||
activePermissions = _.sortBy(_.map(activePermissions, (t) ->
|
||||
permission = angular.element(t).parents(".category-item").data("id")
|
||||
))
|
||||
return activePermissions
|
||||
|
||||
target = angular.element(event.currentTarget)
|
||||
$scope.role.permissions = getActivePermissions()
|
||||
|
||||
onSuccess = (role) ->
|
||||
$confirm.notify('success')
|
||||
categories = generateCategoriesFromRole(role)
|
||||
categoryId = target.parents(".category-config").data("id")
|
||||
renderResume(target.parents(".category-config"), categories[categoryId])
|
||||
|
||||
onError = ->
|
||||
$confirm.notify("error")
|
||||
target.prop "checked", !target.prop("checked")
|
||||
$scope.role.permissions = getActivePermissions()
|
||||
|
||||
$repo.save($scope.role).then onSuccess, onError
|
||||
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$scope.$on "role:changed", ->
|
||||
renderPermissions()
|
||||
|
||||
bindOnce($scope, $attrs.ngModel, renderPermissions)
|
||||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgRolePermissions", ['$tgRepo', '$tgConfirm', RolePermissionsDirective])
|
|
@ -85,6 +85,7 @@ urls = {
|
|||
"project-admin-project-values-issue-priorities": "/project/:project/admin/project-values/issue-priorities"
|
||||
"project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities"
|
||||
"project-admin-memberships": "/project/:project/admin/memberships"
|
||||
"project-admin-roles": "/project/:project/admin/roles"
|
||||
"project-admin-project-profile-features": "/project/:project/admin/project-profile/features"
|
||||
"project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status"
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ module.run([
|
|||
"$tgProjectsResourcesProvider",
|
||||
"$tgMembershipsResourcesProvider",
|
||||
"$tgInvitationsResourcesProvider",
|
||||
"$tgRolesResourcesProvider",
|
||||
"$tgSprintsResourcesProvider",
|
||||
"$tgUserstoriesResourcesProvider",
|
||||
"$tgTasksResourcesProvider",
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# This program 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, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program 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/>.
|
||||
#
|
||||
# File: modules/resources/memberships.coffee
|
||||
###
|
||||
|
||||
|
||||
taiga = @.taiga
|
||||
|
||||
resourceProvider = ($repo, $http, $urls) ->
|
||||
service = {}
|
||||
|
||||
service.get = (id) ->
|
||||
return $repo.queryOne("roles", id)
|
||||
|
||||
service.list = (projectId) ->
|
||||
return $repo.queryMany("roles", {project: projectId})
|
||||
|
||||
return (instance) ->
|
||||
instance.roles = service
|
||||
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgRolesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
|
|
@ -1,48 +1,30 @@
|
|||
extends layout
|
||||
extends dummy-layout
|
||||
|
||||
block head
|
||||
title Taiga Project management web application with scrum in mind!
|
||||
|
||||
block content
|
||||
div.wrapper
|
||||
sidebar.menu-secondary.sidebar
|
||||
div.wrapper.roles(ng-controller="RolesController as ctrl",
|
||||
ng-init="section='admin'", tg-roles)
|
||||
sidebar.menu-secondary.sidebar(tg-admin-navigation="roles")
|
||||
include views/modules/admin-menu
|
||||
sidebar.menu-tertiary.sidebar
|
||||
include views/modules/admin-submenu
|
||||
include views/modules/admin-submenu-roles
|
||||
|
||||
section.main.admin-roles
|
||||
header
|
||||
include views/components/mainTitle
|
||||
|
||||
a.button.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") Delete
|
||||
|
||||
p.total
|
||||
| UX
|
||||
span (6 members with this role)
|
||||
| {{ role.name }}
|
||||
span ({{ role.members_count }} members with this role)
|
||||
|
||||
include views/modules/category-config
|
||||
div.general-category
|
||||
| Can do estimations?
|
||||
div.check
|
||||
input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()")
|
||||
div
|
||||
|
||||
script(type='text/javascript').
|
||||
function randomIntFromInterval(min,max) {
|
||||
return Math.floor(Math.random()*(max-min+1)+min);
|
||||
}
|
||||
(function() {
|
||||
if(randomIntFromInterval(0, 4) !== 4) return true;
|
||||
|
||||
var inputs = document.querySelectorAll('input');
|
||||
|
||||
function change(input) {
|
||||
var num = randomIntFromInterval(100, 600);
|
||||
|
||||
setTimeout(function() {
|
||||
if(input.hasAttribute('checked')) {
|
||||
input.removeAttribute('checked');
|
||||
} else {
|
||||
input.setAttribute('checked', 'checked');
|
||||
}
|
||||
}, num);
|
||||
}
|
||||
setInterval(function() {
|
||||
for(var i = 0; i < inputs.length; i++) {
|
||||
change(inputs[i]);
|
||||
}
|
||||
}, 500);
|
||||
})()
|
||||
div(tg-role-permissions, ng-model="role")
|
||||
|
|
|
@ -17,6 +17,6 @@ section.admin-menu
|
|||
span.title Memberships
|
||||
span.icon.icon-arrow-right
|
||||
li#adminmenu-roles
|
||||
a(href="")
|
||||
a(href="" tg-nav="project-admin-roles:project=project.slug")
|
||||
span.title Roles
|
||||
span.icon.icon-arrow-right
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
section.admin-submenu-roles
|
||||
header
|
||||
h1 Roles
|
||||
|
||||
nav
|
||||
ul
|
||||
li(ng-repeat="item in roles")
|
||||
a(href="" ng-click="ctrl.setRole(item)", ng-class="{active: role.id == item.id}") {{ item.name }}
|
||||
span.icon.icon-arrow-right
|
||||
|
||||
div(tg-new-role)
|
||||
a.button.button-gray.add-button(href="", title="Add New Role")
|
||||
span.text + New role
|
||||
input(type="text", class="hidden new")
|
|
@ -1,3 +1,7 @@
|
|||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Included in the admin-roles.jade and in the RolePermissionsDirective //
|
||||
// in the modules/admin/roles.coffee file. //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
div.general-category
|
||||
| Can do estimations?
|
||||
div.check
|
||||
|
|
|
@ -96,6 +96,7 @@ $prefix-for-spec: true;
|
|||
//modules admin
|
||||
@import 'modules/admin/admin-menu';
|
||||
@import 'modules/admin/admin-submenu';
|
||||
@import 'modules/admin/admin-submenu-roles';
|
||||
@import 'modules/admin/admin-roles';
|
||||
@import 'modules/admin/admin-functionalities';
|
||||
@import 'modules/admin/admin-membership-table';
|
||||
|
|
|
@ -50,4 +50,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.delete-role {
|
||||
position: absolute;
|
||||
right: 2rem;
|
||||
top: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
.admin-submenu-roles {
|
||||
h1 {
|
||||
@extend %xlarge;
|
||||
color: $white;
|
||||
}
|
||||
li {
|
||||
@extend %larger;
|
||||
@extend %title;
|
||||
border-bottom: 1px solid #a6b2a7;
|
||||
text-transform: uppercase;
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $white;
|
||||
display: block;
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
&.active,
|
||||
&:hover {
|
||||
color: $blackish;
|
||||
.icon {
|
||||
@include transition (opacity .3s linear);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: $white;
|
||||
float: right;
|
||||
opacity: 0;
|
||||
}
|
||||
.button-gray {
|
||||
padding: .5rem 0;
|
||||
text-align: center;
|
||||
&:hover {
|
||||
background-color: darken($button-gray-hover, 15%);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,9 +38,12 @@
|
|||
text-align: right;
|
||||
}
|
||||
.category-items {
|
||||
@include slide(400px, overflow-y);
|
||||
background-color: $whitish;
|
||||
padding: 2rem 1rem;
|
||||
width: 100%;
|
||||
.items-container {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
.category-item {
|
||||
border-bottom: 1px dotted $gray;
|
||||
|
|
Loading…
Reference in New Issue