mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-10 15:35:59 +01:00
Merge branch 'improve-group'
This commit is contained in:
1
src/main/resources/update/1_12.sql
Normal file
1
src/main/resources/update/1_12.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
||||||
@@ -20,7 +20,7 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
context.mount(new DashboardController, "/*")
|
context.mount(new DashboardController, "/*")
|
||||||
context.mount(new UserManagementController, "/*")
|
context.mount(new UserManagementController, "/*")
|
||||||
context.mount(new SystemSettingsController, "/*")
|
context.mount(new SystemSettingsController, "/*")
|
||||||
context.mount(new CreateRepositoryController, "/*")
|
context.mount(new CreateController, "/*")
|
||||||
context.mount(new AccountController, "/*")
|
context.mount(new AccountController, "/*")
|
||||||
context.mount(new RepositoryViewerController, "/*")
|
context.mount(new RepositoryViewerController, "/*")
|
||||||
context.mount(new WikiController, "/*")
|
context.mount(new WikiController, "/*")
|
||||||
|
|||||||
@@ -51,14 +51,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
getActivitiesByUser(userName, true))
|
getActivitiesByUser(userName, true))
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
case "members" if(account.isGroupAccount) =>
|
case "members" if(account.isGroupAccount) => {
|
||||||
_root_.account.html.members(account, getGroupMembers(account.userName))
|
val members = getGroupMembers(account.userName)
|
||||||
|
_root_.account.html.members(account, members.map(_._1),
|
||||||
|
context.loginAccount.exists(x => members.exists { case (userName, isManager) => userName == x.userName && isManager }))
|
||||||
|
}
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
case _ =>
|
case _ => {
|
||||||
|
val members = getGroupMembers(account.userName)
|
||||||
_root_.account.html.repositories(account,
|
_root_.account.html.repositories(account,
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)))
|
getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)),
|
||||||
|
context.loginAccount.exists(x => members.exists { case (userName, isManager) => userName == x.userName && isManager }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,35 +9,53 @@ import jp.sf.amateras.scalatra.forms._
|
|||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
import org.eclipse.jgit.lib.{FileMode, Constants}
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
import org.eclipse.jgit.dircache.DirCache
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
class CreateRepositoryController extends CreateRepositoryControllerBase
|
class CreateController extends CreateControllerBase
|
||||||
with RepositoryService with AccountService with WikiService with LabelsService with ActivityService
|
with RepositoryService with AccountService with WikiService with LabelsService with ActivityService
|
||||||
with UsersAuthenticator with ReadableUsersAuthenticator
|
with UsersAuthenticator with ReadableUsersAuthenticator with GroupManagerAuthenticator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new repository.
|
* Creates new repository or group.
|
||||||
*/
|
*/
|
||||||
trait CreateRepositoryControllerBase extends ControllerBase {
|
trait CreateControllerBase extends AccountManagementControllerBase {
|
||||||
self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService
|
self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService
|
||||||
with UsersAuthenticator with ReadableUsersAuthenticator =>
|
with UsersAuthenticator with ReadableUsersAuthenticator with GroupManagerAuthenticator =>
|
||||||
|
|
||||||
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
|
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
|
||||||
|
|
||||||
case class ForkRepositoryForm(owner: String, name: String)
|
case class ForkRepositoryForm(owner: String, name: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newRepositoryForm = mapping(
|
||||||
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
|
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
|
||||||
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))),
|
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))),
|
||||||
"description" -> trim(label("Description" , optional(text()))),
|
"description" -> trim(label("Description" , optional(text()))),
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||||
"createReadme" -> trim(label("Create README" , boolean()))
|
"createReadme" -> trim(label("Create README" , boolean()))
|
||||||
)(RepositoryCreationForm.apply)
|
)(RepositoryCreationForm.apply)
|
||||||
|
|
||||||
val forkForm = mapping(
|
val forkRepositoryForm = mapping(
|
||||||
"owner" -> trim(label("Repository owner", text(required))),
|
"owner" -> trim(label("Repository owner", text(required))),
|
||||||
"name" -> trim(label("Repository name", text(required)))
|
"name" -> trim(label("Repository name", text(required)))
|
||||||
)(ForkRepositoryForm.apply)
|
)(ForkRepositoryForm.apply)
|
||||||
|
|
||||||
|
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
|
||||||
|
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||||
|
|
||||||
|
val newGroupForm = mapping(
|
||||||
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
|
"members" -> trim(label("Members" ,text(required, members)))
|
||||||
|
)(NewGroupForm.apply)
|
||||||
|
|
||||||
|
val editGroupForm = mapping(
|
||||||
|
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
|
||||||
|
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||||
|
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||||
|
"members" -> trim(label("Members" ,text(required, members))),
|
||||||
|
"clearImage" -> trim(label("Clear image" ,boolean()))
|
||||||
|
)(EditGroupForm.apply)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the new repository form.
|
* Show the new repository form.
|
||||||
*/
|
*/
|
||||||
@@ -48,7 +66,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* Create new repository.
|
* Create new repository.
|
||||||
*/
|
*/
|
||||||
post("/new", newForm)(usersOnly { form =>
|
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||||
LockUtil.lock(s"${form.owner}/${form.name}/create"){
|
LockUtil.lock(s"${form.owner}/${form.name}/create"){
|
||||||
if(getRepository(form.owner, form.name, baseUrl).isEmpty){
|
if(getRepository(form.owner, form.name, baseUrl).isEmpty){
|
||||||
val ownerAccount = getAccountByUserName(form.owner).get
|
val ownerAccount = getAccountByUserName(form.owner).get
|
||||||
@@ -60,7 +78,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// Add collaborators for group repository
|
// Add collaborators for group repository
|
||||||
if(ownerAccount.isGroupAccount){
|
if(ownerAccount.isGroupAccount){
|
||||||
getGroupMembers(form.owner).foreach { userName =>
|
getGroupMembers(form.owner).foreach { case (userName, isManager) =>
|
||||||
addCollaborator(form.owner, form.name, userName)
|
addCollaborator(form.owner, form.name, userName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +190,68 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/groups/new")(usersOnly {
|
||||||
|
html.group(None, List((context.loginAccount.get.userName, true)))
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||||
|
createGroup(form.groupName, form.url)
|
||||||
|
updateGroupMembers(form.groupName, form.members.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList)
|
||||||
|
updateImage(form.groupName, form.fileId, false)
|
||||||
|
redirect(s"/${form.groupName}")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:groupName/_editgroup")(managersOnly {
|
||||||
|
defining(params("groupName")){ groupName =>
|
||||||
|
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:groupName/_deletegroup")(managersOnly {
|
||||||
|
defining(params("groupName")){ groupName =>
|
||||||
|
// Remove from GROUP_MEMBER
|
||||||
|
updateGroupMembers(groupName, Nil)
|
||||||
|
// Remove repositories
|
||||||
|
getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||||
|
deleteRepository(groupName, repositoryName)
|
||||||
|
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||||
|
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redirect("/")
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
||||||
|
defining(params("groupName"), form.members.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList){ case (groupName, members) =>
|
||||||
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
|
updateGroup(groupName, form.url, false)
|
||||||
|
|
||||||
|
// Update GROUP_MEMBER
|
||||||
|
updateGroupMembers(form.groupName, members)
|
||||||
|
// Update COLLABORATOR for group repositories
|
||||||
|
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
|
removeCollaborators(form.groupName, repositoryName)
|
||||||
|
members.foreach { case (userName, isManager) =>
|
||||||
|
addCollaborator(form.groupName, repositoryName, userName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
|
redirect(s"/${form.groupName}")
|
||||||
|
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
||||||
createLabel(userName, repositoryName, "bug", "fc2929")
|
createLabel(userName, repositoryName, "bug", "fc2929")
|
||||||
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
||||||
@@ -186,14 +266,20 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
|||||||
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private def uniqueRepository: Constraint = new Constraint(){
|
||||||
* Duplicate check for the repository name.
|
|
||||||
*/
|
|
||||||
private def unique: Constraint = new Constraint(){
|
|
||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||||
params.get("owner").flatMap { userName =>
|
params.get("owner").flatMap { userName =>
|
||||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def members: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
|
if(value.split(",").exists {
|
||||||
|
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||||
|
}) None else Some("Must select one manager at least.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
val users = getAllUsers(includeRemoved)
|
val users = getAllUsers(includeRemoved)
|
||||||
|
|
||||||
val members = users.collect { case account if(account.isGroupAccount) =>
|
val members = users.collect { case account if(account.isGroupAccount) =>
|
||||||
account.userName -> getGroupMembers(account.userName)
|
account.userName -> getGroupMembers(account.userName).map(_._1)
|
||||||
}.toMap
|
}.toMap
|
||||||
admin.users.html.list(users, members, includeRemoved)
|
admin.users.html.list(users, members, includeRemoved)
|
||||||
})
|
})
|
||||||
@@ -127,7 +127,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
||||||
createGroup(form.groupName, form.url)
|
createGroup(form.groupName, form.url)
|
||||||
updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").toList).getOrElse(Nil))
|
updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList).getOrElse(Nil))
|
||||||
updateImage(form.groupName, form.fileId, false)
|
updateImage(form.groupName, form.fileId, false)
|
||||||
redirect("/admin/users")
|
redirect("/admin/users")
|
||||||
})
|
})
|
||||||
@@ -139,7 +143,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||||
defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) =>
|
defining(params("groupName"), form.memberNames.map(_.split(",").map {
|
||||||
|
_.split(":") match {
|
||||||
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
|
}
|
||||||
|
}.toList).getOrElse(Nil)){ case (groupName, members) =>
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
updateGroup(groupName, form.url, form.isRemoved)
|
updateGroup(groupName, form.url, form.isRemoved)
|
||||||
|
|
||||||
@@ -155,11 +163,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Update GROUP_MEMBER
|
// Update GROUP_MEMBER
|
||||||
updateGroupMembers(form.groupName, memberNames)
|
updateGroupMembers(form.groupName, members)
|
||||||
// Update COLLABORATOR for group repositories
|
// Update COLLABORATOR for group repositories
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
removeCollaborators(form.groupName, repositoryName)
|
||||||
memberNames.foreach { userName =>
|
members.foreach { case (userName, isManager) =>
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
addCollaborator(form.groupName, repositoryName, userName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import scala.slick.driver.H2Driver.simple._
|
|||||||
object GroupMembers extends Table[GroupMember]("GROUP_MEMBER") {
|
object GroupMembers extends Table[GroupMember]("GROUP_MEMBER") {
|
||||||
def groupName = column[String]("GROUP_NAME", O PrimaryKey)
|
def groupName = column[String]("GROUP_NAME", O PrimaryKey)
|
||||||
def userName = column[String]("USER_NAME", O PrimaryKey)
|
def userName = column[String]("USER_NAME", O PrimaryKey)
|
||||||
def * = groupName ~ userName <> (GroupMember, GroupMember.unapply _)
|
def isManager = column[Boolean]("MANAGER")
|
||||||
|
def * = groupName ~ userName ~ isManager <> (GroupMember, GroupMember.unapply _)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class GroupMember(
|
case class GroupMember(
|
||||||
groupName: String,
|
groupName: String,
|
||||||
userName: String
|
userName: String,
|
||||||
|
isManager: Boolean
|
||||||
)
|
)
|
||||||
@@ -122,18 +122,18 @@ trait AccountService {
|
|||||||
def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit =
|
def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit =
|
||||||
Accounts.filter(_.userName is groupName.bind).map(t => t.url.? ~ t.removed).update(url, removed)
|
Accounts.filter(_.userName is groupName.bind).map(t => t.url.? ~ t.removed).update(url, removed)
|
||||||
|
|
||||||
def updateGroupMembers(groupName: String, members: List[String]): Unit = {
|
def updateGroupMembers(groupName: String, members: List[(String, Boolean)]): Unit = {
|
||||||
Query(GroupMembers).filter(_.groupName is groupName.bind).delete
|
Query(GroupMembers).filter(_.groupName is groupName.bind).delete
|
||||||
members.foreach { userName =>
|
members.foreach { case (userName, isManager) =>
|
||||||
GroupMembers insert GroupMember (groupName, userName)
|
GroupMembers insert GroupMember (groupName, userName, isManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getGroupMembers(groupName: String): List[String] =
|
def getGroupMembers(groupName: String): List[(String, Boolean)] =
|
||||||
Query(GroupMembers)
|
Query(GroupMembers)
|
||||||
.filter(_.groupName is groupName.bind)
|
.filter(_.groupName is groupName.bind)
|
||||||
.sortBy(_.userName)
|
.sortBy(_.userName)
|
||||||
.map(_.userName)
|
.map(m => m.userName ~ m.isManager)
|
||||||
.list
|
.list
|
||||||
|
|
||||||
def getGroupsByUserName(userName: String): List[String] =
|
def getGroupsByUserName(userName: String): List[String] =
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ object AutoUpdate {
|
|||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
Version(1, 12),
|
||||||
Version(1, 11),
|
Version(1, 11),
|
||||||
Version(1, 10),
|
Version(1, 10),
|
||||||
Version(1, 9),
|
Version(1, 9),
|
||||||
|
|||||||
@@ -155,3 +155,22 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows only the group managers.
|
||||||
|
*/
|
||||||
|
trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
|
||||||
|
protected def managersOnly(action: => Any) = { authenticate(action) }
|
||||||
|
protected def managersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) }
|
||||||
|
|
||||||
|
private def authenticate(action: => Any) = {
|
||||||
|
{
|
||||||
|
defining(request.paths){ paths =>
|
||||||
|
context.loginAccount match {
|
||||||
|
case Some(x) if(getGroupMembers(paths(0)).exists { case (userName, isManager) => userName == x.userName && isManager }) => action
|
||||||
|
case _ => Unauthorized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@(account: model.Account, groupNames: List[String], active: String)(body: Html)(implicit context: app.Context)
|
@(account: model.Account, groupNames: List[String], active: String,
|
||||||
|
isGroupManager: Boolean = false)(body: Html)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(account.userName){
|
@html.main(account.userName){
|
||||||
@@ -41,6 +42,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
@if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){
|
||||||
|
<li class="pull-right">
|
||||||
|
<div class="button-group">
|
||||||
|
<a href="@url(account.userName)/_editgroup" class="btn">Edit Group</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
@body
|
@body
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@(account: model.Account, members: List[String])(implicit context: app.Context)
|
@(account: model.Account, members: List[String], isGroupManager: Boolean)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@main(account, Nil, "members"){
|
@main(account, Nil, "members", isGroupManager){
|
||||||
@if(members.isEmpty){
|
@if(members.isEmpty){
|
||||||
No members
|
No members
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@(account: model.Account, groupNames: List[String], repositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context)
|
@(account: model.Account, groupNames: List[String],
|
||||||
|
repositories: List[service.RepositoryService.RepositoryInfo],
|
||||||
|
isGroupManager: Boolean)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@main(account, groupNames, "repositories"){
|
@main(account, groupNames, "repositories", isGroupManager){
|
||||||
@if(repositories.isEmpty){
|
@if(repositories.isEmpty){
|
||||||
No repositories
|
No repositories
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@(account: Option[model.Account], members: List[String])(implicit context: app.Context)
|
@(account: Option[model.Account], members: List[(String, Boolean)])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(if(account.isEmpty) "New Group" else "Update Group"){
|
@html.main(if(account.isEmpty) "New Group" else "Update Group"){
|
||||||
@@ -34,9 +34,10 @@
|
|||||||
<div class="span5">
|
<div class="span5">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label class="strong">Members</label>
|
<label class="strong">Members</label>
|
||||||
<ul id="members" class="collaborator">
|
<ul id="member-list" class="collaborator">
|
||||||
@members.map { userName =>
|
@members.map { case (userName, isManager) =>
|
||||||
<li data-name="@userName">
|
<li data-name="@userName">
|
||||||
|
<input type="checkbox" class="is_manager" id="is_manager_@userName" @if(isManager){checked}/>
|
||||||
<a href="@path/@url(userName)">@userName</a>
|
<a href="@path/@url(userName)">@userName</a>
|
||||||
<a href="#" class="remove">(remove)</a>
|
<a href="#" class="remove">(remove)</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -44,9 +45,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
@helper.html.account("memberName", 200)
|
@helper.html.account("memberName", 200)
|
||||||
<input type="button" class="btn" value="Add" id="addMember"/>
|
<input type="button" class="btn" value="Add" id="addMember"/>
|
||||||
<input type="hidden" id="memberNames" name="memberNames" value="@members.mkString(",")"/>
|
<input type="hidden" id="members" name="members" value="@members.map(x => x._1 + ":" + x._2).mkString(",")"/>
|
||||||
<div>
|
<div>
|
||||||
<span class="error" id="error-memberName"></span>
|
<span class="error" id="error-members"></span>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +71,7 @@ $(function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check duplication
|
// check duplication
|
||||||
var exists = $('#members li').filter(function(){
|
var exists = $('#member-list li').filter(function(){
|
||||||
return $(this).data('name') == userName;
|
return $(this).data('name') == userName;
|
||||||
}).length > 0;
|
}).length > 0;
|
||||||
if(exists){
|
if(exists){
|
||||||
@@ -84,18 +85,16 @@ $(function(){
|
|||||||
}, function(data, status){
|
}, function(data, status){
|
||||||
if(data == 'true'){
|
if(data == 'true'){
|
||||||
// add member
|
// add member
|
||||||
$('#members').append($('<li>')
|
$('#member-list').append($('<li>')
|
||||||
.data('name', userName)
|
.data('name', userName)
|
||||||
|
.append($('<input type="checkbox" class="is_manager">').attr('id', 'is_manager_' + userName))
|
||||||
.append($('<a>').attr('href', '@path/' + userName).text(userName))
|
.append($('<a>').attr('href', '@path/' + userName).text(userName))
|
||||||
.append(' ')
|
.append(' ')
|
||||||
.append($('<a>').attr('href', '#').addClass('remove').text('(remove)')));
|
.append($('<a>').attr('href', '#').addClass('remove').text('(remove)')));
|
||||||
$('#memberName').val('');
|
$('#members').val('');
|
||||||
|
|
||||||
// update hidden value
|
// update hidden value
|
||||||
var userNames = $('#members li').map(function(i, e){
|
updateMembers();
|
||||||
return $(e).data('name');
|
|
||||||
}).get().join(',');
|
|
||||||
$('#memberNames').val(userNames);
|
|
||||||
} else {
|
} else {
|
||||||
$('#error-memberName').text('User does not exist.');
|
$('#error-memberName').text('User does not exist.');
|
||||||
}
|
}
|
||||||
@@ -105,12 +104,9 @@ $(function(){
|
|||||||
$(document).on('click', '.remove', function(){
|
$(document).on('click', '.remove', function(){
|
||||||
// remove member
|
// remove member
|
||||||
$(this).parent().remove();
|
$(this).parent().remove();
|
||||||
|
|
||||||
// update hidden value
|
// update hidden value
|
||||||
var userNames = $('#members li').map(function(i, e){
|
updateMembers();
|
||||||
return $(e).data('name');
|
$('#members').val(members);
|
||||||
}).get().join(',');
|
|
||||||
$('#memberNames').val(userNames);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't submit form by ENTER key
|
// Don't submit form by ENTER key
|
||||||
@@ -118,5 +114,17 @@ $(function(){
|
|||||||
console.log(e.keyCode);
|
console.log(e.keyCode);
|
||||||
return !(e.keyCode == 13);
|
return !(e.keyCode == 13);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.is_manager').change(function(){
|
||||||
|
updateMembers();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateMembers(){
|
||||||
|
var members = $('#member-list li').map(function(i, e){
|
||||||
|
var userName = $(e).data('name');
|
||||||
|
return userName + ':' + $('#is_manager_' + userName).prop('checked');
|
||||||
|
}).get().join(',');
|
||||||
|
$('#members').val(members);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
139
src/main/twirl/group.scala.html
Normal file
139
src/main/twirl/group.scala.html
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
@(account: Option[model.Account], members: List[(String, Boolean)])(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@main(if(account.isEmpty) "Create group" else "Edit group"){
|
||||||
|
<div>
|
||||||
|
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span5">
|
||||||
|
<fieldset>
|
||||||
|
<label for="groupName" class="strong">Group name</label>
|
||||||
|
<div>
|
||||||
|
<span id="error-groupName" class="error"></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label class="strong">URL (Optional)</label>
|
||||||
|
<div>
|
||||||
|
<span id="error-url" class="error"></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" name="url" id="url" style="width: 300px;" value="@account.map(_.url)"/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="avatar" class="strong">Image (Optional)</label>
|
||||||
|
@helper.html.uploadavatar(account)
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="span7">
|
||||||
|
<fieldset>
|
||||||
|
<label class="strong">Members</label>
|
||||||
|
<ul id="member-list" class="collaborator">
|
||||||
|
</ul>
|
||||||
|
@helper.html.account("memberName", 200)
|
||||||
|
<input type="button" class="btn" value="Add" id="addMember"/>
|
||||||
|
<input type="hidden" id="members" name="members" value="@members.map(x => x._1 + ":" + x._2).mkString(",")"/>
|
||||||
|
<div>
|
||||||
|
<span class="error" id="error-members"></span>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<fieldset class="margin">
|
||||||
|
@if(account.isDefined){
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||||
|
@if(account.isDefined){
|
||||||
|
<a href="@url(account.get.userName)" class="btn">Cancel</a>
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('input[type=submit]').click(function(){
|
||||||
|
updateMembers();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#addMember').click(function(){
|
||||||
|
$('#error-memberName').text('');
|
||||||
|
var userName = $('#memberName').val();
|
||||||
|
|
||||||
|
// check empty
|
||||||
|
if($.trim(userName) == ''){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check duplication
|
||||||
|
var exists = $('#member-list li').filter(function(){
|
||||||
|
return $(this).data('name') == userName;
|
||||||
|
}).length > 0;
|
||||||
|
if(exists){
|
||||||
|
$('#error-memberName').text('User has been already added.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check existence
|
||||||
|
$.post('@path/admin/users/_usercheck', {
|
||||||
|
'userName': userName
|
||||||
|
}, function(data, status){
|
||||||
|
if(data == 'true'){
|
||||||
|
addMemberHTML(userName, false);
|
||||||
|
} else {
|
||||||
|
$('#error-memberName').text('User does not exist.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.remove', function(){
|
||||||
|
$(this).parent().remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't submit form by ENTER key
|
||||||
|
$('#memberName').keypress(function(e){
|
||||||
|
return !(e.keyCode == 13);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#delete').click(function(){
|
||||||
|
return confirm('Once you delete this group, there is no going back.\nAre you sure?');
|
||||||
|
});
|
||||||
|
|
||||||
|
@members.map { case (userName, isManager) =>
|
||||||
|
addMemberHTML('@userName', @isManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMemberHTML(userName, isManager){
|
||||||
|
var memberButton = $('<button type="button" class="btn btn-default btn-mini" value="false">Member</button>').addClass('is_manager_' + userName);
|
||||||
|
if(!isManager){
|
||||||
|
memberButton.addClass('active');
|
||||||
|
}
|
||||||
|
var managerButton = $('<button type="button" class="btn btn-default btn-mini" value="true">Manager</button>').addClass('is_manager_' + userName);
|
||||||
|
if(isManager){
|
||||||
|
memberButton.addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#member-list').append($('<li>')
|
||||||
|
.data('name', userName)
|
||||||
|
.append($('<div class="btn-group is_manager" data-toggle="buttons-radio">')
|
||||||
|
.append(memberButton)
|
||||||
|
.append(managerButton))
|
||||||
|
.append(' ')
|
||||||
|
.append($('<a>').attr('href', '@path/' + userName).text(userName))
|
||||||
|
.append(' ')
|
||||||
|
.append($('<a href="#" class="remove pull-right">(remove)</a>')));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMembers(){
|
||||||
|
var members = $('#member-list li').map(function(i, e){
|
||||||
|
var userName = $(e).data('name');
|
||||||
|
return userName + ':' + $('.is_manager_' + userName + '.active').attr('value');
|
||||||
|
}).get().join(',');
|
||||||
|
$('#members').val(members);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -54,7 +54,11 @@
|
|||||||
}
|
}
|
||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
<a href="@url(loginAccount.get.userName)" class="username menu">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</a>
|
<a href="@url(loginAccount.get.userName)" class="username menu">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</a>
|
||||||
<a href="@path/new" class="menu" data-toggle="tooltip" data-placement="bottom" title="Create a new repo"><i class="icon-plus"></i></a>
|
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#"><i class="icon-plus"></i></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="@path/new">New repository</a></li>
|
||||||
|
<li><a href="@path/groups/new">New group</a></li>
|
||||||
|
</ul>
|
||||||
<a href="@url(loginAccount.get.userName)/_edit" class="menu" data-toggle="tooltip" data-placement="bottom" title="Account settings"><i class="icon-user"></i></a>
|
<a href="@url(loginAccount.get.userName)/_edit" class="menu" data-toggle="tooltip" data-placement="bottom" title="Account settings"><i class="icon-user"></i></a>
|
||||||
@if(loginAccount.get.isAdmin){
|
@if(loginAccount.get.isAdmin){
|
||||||
<a href="@path/admin/users" class="menu" data-toggle="tooltip" data-placement="bottom" title="Administration"><i class="icon-wrench"></i></a>
|
<a href="@path/admin/users" class="menu" data-toggle="tooltip" data-placement="bottom" title="Administration"><i class="icon-wrench"></i></a>
|
||||||
|
|||||||
Reference in New Issue
Block a user