New ProjectAccessScreen to edit access controls

Change-Id: Ica21a1b3bb8b3b1f3e2e4f4964f25f9c8a1741be
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2011-01-13 16:54:19 -08:00
parent 4b5191e689
commit 08ad16693e
48 changed files with 2851 additions and 43 deletions

View File

@@ -110,6 +110,17 @@ public class AccessSection implements Comparable<AccessSection> {
}
}
public void mergeFrom(AccessSection section) {
for (Permission src : section.getPermissions()) {
Permission dst = getPermission(src.getName());
if (dst != null) {
dst.mergeFrom(src);
} else {
permissions.add(dst);
}
}
}
@Override
public int compareTo(AccessSection o) {
return comparePattern().compareTo(o.comparePattern());

View File

@@ -21,8 +21,8 @@ import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.client.RpcImpl;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtjsonrpc.client.RpcImpl.Version;
import com.google.gwtjsonrpc.client.VoidResult;
import java.util.List;
import java.util.Set;
@@ -36,7 +36,8 @@ public interface GroupAdminService extends RemoteJsonService {
void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
@SignInRequired
void groupDetail(AccountGroup.Id groupId, AsyncCallback<GroupDetail> callback);
void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID uuid,
AsyncCallback<GroupDetail> callback);
@SignInRequired
void changeGroupDescription(AccountGroup.Id groupId, String description,

View File

@@ -39,7 +39,7 @@ public class GroupReference implements Comparable<GroupReference> {
}
public void setUUID(AccountGroup.UUID newUUID) {
uuid = newUUID.get();
uuid = newUUID != null ? newUUID.get() : null;
}
public String getName() {

View File

@@ -94,14 +94,14 @@ public class Permission implements Comparable<Permission> {
return null;
}
public boolean getExclusiveGroup() {
public Boolean getExclusiveGroup() {
// Only permit exclusive group behavior on non OWNER permissions,
// otherwise an owner might lose access to a delegated subspace.
//
return exclusiveGroup && !OWNER.equals(getName());
}
public void setExclusiveGroup(boolean newExclusiveGroup) {
public void setExclusiveGroup(Boolean newExclusiveGroup) {
exclusiveGroup = newExclusiveGroup;
}
@@ -157,6 +157,17 @@ public class Permission implements Comparable<Permission> {
}
}
void mergeFrom(Permission src) {
for (PermissionRule srcRule : src.getRules()) {
PermissionRule dstRule = getRule(srcRule.getGroup());
if (dstRule != null) {
dstRule.mergeFrom(srcRule);
} else {
add(srcRule);
}
}
}
private static boolean sameGroup(PermissionRule rule, GroupReference group) {
if (group.getUUID() != null) {
return group.getUUID().equals(rule.getGroup().getUUID());

View File

@@ -15,6 +15,10 @@
package com.google.gerrit.common.data;
public class PermissionRule implements Comparable<PermissionRule> {
public static enum Action {
ALLOW, DENY;
}
protected boolean deny;
protected boolean force;
protected int min;
@@ -28,6 +32,17 @@ public class PermissionRule implements Comparable<PermissionRule> {
this.group = group;
}
public Action getAction() {
return deny ? Action.DENY : Action.ALLOW;
}
public void setAction(Action action) {
if (action == null) {
throw new NullPointerException("action");
}
setDeny(action == Action.DENY);
}
public boolean getDeny() {
return deny;
}
@@ -36,11 +51,11 @@ public class PermissionRule implements Comparable<PermissionRule> {
deny = newDeny;
}
public boolean getForce() {
public Boolean getForce() {
return force;
}
public void setForce(boolean newForce) {
public void setForce(Boolean newForce) {
force = newForce;
}
@@ -78,6 +93,12 @@ public class PermissionRule implements Comparable<PermissionRule> {
group = newGroup;
}
void mergeFrom(PermissionRule src) {
setDeny(getDeny() || src.getDeny());
setForce(getForce() || src.getForce());
setRange(Math.min(getMin(), src.getMin()), Math.max(getMax(), src.getMax()));
}
@Override
public int compareTo(PermissionRule o) {
int cmp = deny(this) - deny(o);

View File

@@ -0,0 +1,66 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.Project;
import java.util.List;
import java.util.Set;
public class ProjectAccess {
protected String revision;
protected Project.NameKey inheritsFrom;
protected List<AccessSection> local;
protected Set<String> ownerOf;
public ProjectAccess() {
}
public String getRevision() {
return revision;
}
public void setRevision(String name) {
revision = name;
}
public Project.NameKey getInheritsFrom() {
return inheritsFrom;
}
public void setInheritsFrom(Project.NameKey name) {
inheritsFrom = name;
}
public List<AccessSection> getLocal() {
return local;
}
public void setLocal(List<AccessSection> as) {
local = as;
}
public boolean isOwnerOf(AccessSection section) {
return getOwnerOf().contains(section.getRefPattern());
}
public Set<String> getOwnerOf() {
return ownerOf;
}
public void setOwnerOf(Set<String> refs) {
ownerOf = refs;
}
}

View File

@@ -32,10 +32,18 @@ public interface ProjectAdminService extends RemoteJsonService {
void projectDetail(Project.NameKey projectName,
AsyncCallback<ProjectDetail> callback);
void projectAccess(Project.NameKey projectName,
AsyncCallback<ProjectAccess> callback);
@SignInRequired
void changeProjectSettings(Project update,
AsyncCallback<ProjectDetail> callback);
@SignInRequired
void changeProjectAccess(Project.NameKey projectName, String baseRevision,
String message, List<AccessSection> sections,
AsyncCallback<ProjectAccess> callback);
void listBranches(Project.NameKey projectName,
AsyncCallback<ListBranchesResult> callback);

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.Project;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
@@ -32,5 +31,5 @@ public interface SuggestService extends RemoteJsonService {
AsyncCallback<List<AccountInfo>> callback);
void suggestAccountGroup(String query, int limit,
AsyncCallback<List<AccountGroupName>> callback);
AsyncCallback<List<GroupReference>> callback);
}

View File

@@ -14,6 +14,7 @@
limitations under the License.
-->
<module rename-to="gerrit">
<inherits name='com.google.gwt.editor.Editor'/>
<inherits name='com.google.gwt.user.User'/>
<inherits name='com.google.gwt.resources.Resources'/>
<inherits name='com.google.gwt.user.theme.chrome.Chrome'/>

View File

@@ -44,6 +44,7 @@ import com.google.gerrit.client.account.RegisterScreen;
import com.google.gerrit.client.account.ValidateEmailScreen;
import com.google.gerrit.client.admin.AccountGroupScreen;
import com.google.gerrit.client.admin.GroupListScreen;
import com.google.gerrit.client.admin.ProjectAccessScreen;
import com.google.gerrit.client.admin.ProjectBranchesScreen;
import com.google.gerrit.client.admin.ProjectInfoScreen;
import com.google.gerrit.client.admin.ProjectListScreen;
@@ -86,6 +87,10 @@ public class Dispatcher {
return "admin,group," + id.toString();
}
public static String toGroup(final AccountGroup.UUID uuid) {
return "admin,group,uuid-" + uuid.toString();
}
public static String toProjectAdmin(final Project.NameKey n, final String tab) {
return "admin,project," + n.toString() + "," + tab;
}
@@ -410,6 +415,10 @@ public class Dispatcher {
private Screen select() {
String p;
p = "admin,group,uuid-";
if (token.startsWith(p))
return new AccountGroupScreen(AccountGroup.UUID.parse(skip(p, token)));
p = "admin,group,";
if (token.startsWith(p))
return new AccountGroupScreen(AccountGroup.Id.parse(skip(p, token)));
@@ -431,7 +440,7 @@ public class Dispatcher {
}
if (ProjectScreen.ACCESS.equals(p)) {
return new NotFoundScreen();
return new ProjectAccessScreen(k);
}
return new NotFoundScreen();

View File

@@ -0,0 +1,233 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.ValueAwareEditor;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ValueListBox;
import java.util.ArrayList;
import java.util.List;
public class AccessSectionEditor extends Composite implements
Editor<AccessSection>, ValueAwareEditor<AccessSection> {
interface Binder extends UiBinder<HTMLPanel, AccessSectionEditor> {
}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField
ValueEditor<String> refPattern;
@UiField
FlowPanel permissionContainer;
ListEditor<Permission, PermissionEditor> permissions;
@UiField
DivElement addContainer;
@UiField(provided = true)
@Editor.Ignore
ValueListBox<String> permissionSelector;
@UiField
SpanElement deletedName;
@UiField
Anchor deleteSection;
@UiField
DivElement normal;
@UiField
DivElement deleted;
private final ProjectAccess projectAccess;
private AccessSection value;
private boolean readOnly;
private boolean isDeleted;
public AccessSectionEditor(ProjectAccess access) {
projectAccess = access;
permissionSelector =
new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
if (!Util.C.addPermission().equals(event.getValue())) {
onAddPermission(event.getValue());
}
}
});
initWidget(uiBinder.createAndBindUi(this));
permissions = ListEditor.of(new PermissionEditorSource());
}
@UiHandler("deleteSection")
void onDeleteHover(MouseOverEvent event) {
normal.addClassName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deleteSection")
void onDeleteNonHover(MouseOutEvent event) {
normal.removeClassName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deleteSection")
void onDeleteSection(ClickEvent event) {
isDeleted = true;
deletedName.setInnerText(refPattern.getValue());
normal.getStyle().setDisplay(Display.NONE);
deleted.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("undoDelete")
void onUndoDelete(ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
}
void onAddPermission(String varName) {
Permission p = value.getPermission(varName, true);
permissions.getList().add(p);
rebuildPermissionSelector();
}
void editRefPattern() {
refPattern.edit();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
refPattern.setFocus(true);
}});
}
void enableEditing() {
readOnly = false;
addContainer.getStyle().setDisplay(Display.BLOCK);
rebuildPermissionSelector();
}
boolean isDeleted() {
return isDeleted;
}
@Override
public void setValue(AccessSection value) {
this.value = value;
this.readOnly = !projectAccess.isOwnerOf(value);
refPattern.setEnabled(!readOnly);
deleteSection.setVisible(!readOnly);
if (readOnly) {
addContainer.getStyle().setDisplay(Display.NONE);
} else {
enableEditing();
}
}
private void rebuildPermissionSelector() {
List<String> perms = new ArrayList<String>();
for (ApprovalType t : Gerrit.getConfig().getApprovalTypes()
.getApprovalTypes()) {
String varName = Permission.LABEL + t.getCategory().getLabelName();
if (value.getPermission(varName) == null) {
perms.add(varName);
}
}
for (String varName : Util.C.permissionNames().keySet()) {
if (value.getPermission(varName) == null) {
perms.add(varName);
}
}
if (perms.isEmpty()) {
addContainer.getStyle().setDisplay(Display.NONE);
} else {
addContainer.getStyle().setDisplay(Display.BLOCK);
perms.add(0, Util.C.addPermission());
permissionSelector.setValue(Util.C.addPermission());
permissionSelector.setAcceptableValues(perms);
}
}
@Override
public void flush() {
List<Permission> src = permissions.getList();
List<Permission> keep = new ArrayList<Permission>(src.size());
for (int i = 0; i < src.size(); i++) {
PermissionEditor e = (PermissionEditor) permissionContainer.getWidget(i);
if (!e.isDeleted()) {
keep.add(src.get(i));
}
}
value.setPermissions(keep);
}
@Override
public void onPropertyChange(String... paths) {
}
@Override
public void setDelegate(EditorDelegate<AccessSection> delegate) {
}
private class PermissionEditorSource extends EditorSource<PermissionEditor> {
@Override
public PermissionEditor create(int index) {
PermissionEditor subEditor = new PermissionEditor(readOnly, value);
permissionContainer.insert(subEditor, index);
return subEditor;
}
@Override
public void dispose(PermissionEditor subEditor) {
subEditor.removeFromParent();
}
@Override
public void setIndex(PermissionEditor subEditor, int index) {
permissionContainer.insert(subEditor, index);
}
}
}

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:e='urn:import:com.google.gwt.editor.ui.client'
xmlns:my='urn:import:com.google.gerrit.client.admin'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
ui:generateLocales='default,en'
>
<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
<ui:style>
@eval selectionColor com.google.gerrit.client.Gerrit.getConfig().getSelectionColor();
@eval trimColor com.google.gerrit.client.Gerrit.getConfig().getTrimColor();
.panel {
position: relative;
}
.content {
margin-top: 4px;
margin-bottom: 4px;
padding-bottom: 2px;
}
.normal {
background-color: trimColor;
}
.deleted {
padding-left: 7px;
padding-bottom: 2px;
}
.header {
padding-left: 5px;
padding-right: 5px;
}
.headerText {
vertical-align: top;
white-space: nowrap;
font-weight: bold;
}
.headerTable {
border: 0;
width: 100%;
padding-right: 40px;
}
.header:hover {
background-color: selectionColor;
}
.refName {
width: 100%;
}
.refNameEdit {
width: 100%;
}
.permissionList {
margin-left: 5px;
margin-right: 5px;
}
.addContainer {
padding-left: 16px;
padding-right: 16px;
font-size: 80%;
}
.addContainer:hover {
background-color: selectionColor;
}
.addSelector {
font-size: 80%;
}
.deleteIcon {
position: absolute;
top: 5px;
right: 17px;
}
.undoIcon {
position: absolute;
top: 2px;
right: 17px;
}
</ui:style>
<g:HTMLPanel styleName='{style.panel}'>
<div ui:field='normal' class='{style.normal} {style.content}'>
<div class='{style.header}'>
<table class='{style.headerTable}'><tr>
<td class='{style.headerText}'><ui:msg>Reference:</ui:msg></td>
<td width='100%'>
<my:ValueEditor
ui:field='refPattern'
addStyleNames='{style.refName}'
editTitle='Edit reference pattern'>
<ui:attribute name='editTitle'/>
<my:editor>
<my:RefPatternBox styleName='{style.refNameEdit}'/>
</my:editor>
</my:ValueEditor>
</td>
</tr></table>
<g:Anchor
ui:field='deleteSection'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.deleteIcon}'
title='Delete this section (and nested rules)'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
<g:FlowPanel
ui:field='permissionContainer'
styleName='{style.permissionList}'/>
<div ui:field='addContainer' class='{style.addContainer}'>
<g:ValueListBox
ui:field='permissionSelector'
styleName='{style.addSelector}' />
</div>
</div>
<div
ui:field='deleted'
class='{style.deleted} {res.css.deleted}'
style='display: none'>
<ui:msg>Reference <span ui:field='deletedName'/> was deleted</ui:msg>
<g:Anchor
ui:field='undoDelete'
href='javascript:void'
styleName='{style.undoIcon} {res.css.undoIcon}'
title='Undo deletion'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -63,7 +63,9 @@ import java.util.HashSet;
import java.util.List;
public class AccountGroupScreen extends AccountScreen {
private final AccountGroup.Id groupId;
private AccountGroup.Id groupId;
private AccountGroup.UUID groupUUID;
private AccountInfoCache accounts = AccountInfoCache.empty();
private GroupInfoCache groups = GroupInfoCache.empty();
private MemberTable members;
@@ -106,13 +108,21 @@ public class AccountGroupScreen extends AccountScreen {
groupId = toShow;
}
public AccountGroupScreen(final AccountGroup.UUID toShow) {
groupUUID = toShow;
}
@Override
protected void onLoad() {
super.onLoad();
Util.GROUP_SVC.groupDetail(groupId, new ScreenLoadCallback<GroupDetail>(
this) {
Util.GROUP_SVC.groupDetail(groupId, groupUUID,
new ScreenLoadCallback<GroupDetail>(this) {
@Override
protected void preDisplay(final GroupDetail result) {
groupId = result.group.getId();
groupUUID = result.group.getGroupUUID();
display(result);
enableForm(result.canModify);
saveName.setVisible(result.canModify);
saveOwner.setVisible(result.canModify);
@@ -121,7 +131,6 @@ public class AccountGroupScreen extends AccountScreen {
delMember.setVisible(result.canModify);
saveType.setVisible(result.canModify);
delInclude.setVisible(result.canModify);
display(result);
}
});
}

View File

@@ -16,6 +16,8 @@ package com.google.gerrit.client.admin;
import com.google.gwt.i18n.client.Constants;
import java.util.Map;
public interface AdminConstants extends Constants {
String defaultAccountName();
String defaultAccountGroupName();
@@ -101,4 +103,14 @@ public interface AdminConstants extends Constants {
String noGroupSelected();
String errorNoMatchingGroups();
String errorNoGitRepository();
String addPermission();
Map<String,String> permissionNames();
String refErrorEmpty();
String refErrorBeginSlash();
String refErrorDoubleSlash();
String refErrorNoSpace();
String refErrorPrintable();
String errorsMustBeFixed();
}

View File

@@ -83,3 +83,36 @@ projectAdminTabAccess = Access
noGroupSelected = (No group selected)
errorNoMatchingGroups = No Matching Groups
errorNoGitRepository = No Git Repository
addPermission = Add Permission ...
# Permission Names
permissionNames = \
create, \
forgeAuthor, \
forgeCommitter, \
forgeServerAsCommitter, \
owner, \
push, \
pushMerge, \
pushTag, \
read, \
submit
create = Create Reference
forgeAuthor = Forge Author Identity
forgeCommitter = Forge Committer Identity
forgeServerAsCommitter = Forge Server Identity
owner = Owner
push = Push
pushMerge = Push Merge Commit
pushTag = Push Annotated Tag
read = Read
submit = Submit
refErrorEmpty = Reference must be supplied
refErrorBeginSlash = Reference must not start with '/'
refErrorDoubleSlash = References cannot contain '//'
refErrorNoSpace = References cannot contain spaces
refErrorPrintable = References may contain only printable characters
errorsMustBeFixed = Errors must be fixed before committing changes.

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gwt.resources.client.CssResource;
public interface AdminCss extends CssResource {
String deleteIcon();
String undoIcon();
String deleted();
String deletedBorder();
String deleteSectionHover();
}

View File

@@ -18,6 +18,7 @@ import com.google.gwt.i18n.client.Messages;
public interface AdminMessages extends Messages {
String group(String name);
String label(String name);
String project(String name);
String deletedGroup(int id);
}

View File

@@ -1,3 +1,4 @@
group = Group {0}
label = Label {0}
project = Project {0}
deletedGroup = Deleted Group {0}

View File

@@ -0,0 +1,38 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
public interface AdminResources extends ClientBundle {
public static final AdminResources I = GWT.create(AdminResources.class);
@Source("admin.css")
AdminCss css();
@Source("editText.png")
public ImageResource editText();
@Source("deleteNormal.png")
public ImageResource deleteNormal();
@Source("deleteHover.png")
public ImageResource deleteHover();
@Source("undoNormal.png")
public ImageResource undoNormal();
}

View File

@@ -0,0 +1,143 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.common.data.GroupReference;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestBox.DefaultSuggestionDisplay;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwtexpui.globalkey.client.NpTextBox;
public class GroupReferenceBox extends Composite implements
LeafValueEditor<GroupReference>, HasSelectionHandlers<GroupReference>,
HasCloseHandlers<GroupReferenceBox>, Focusable {
private final DefaultSuggestionDisplay suggestions;
private final NpTextBox textBox;
private final AccountGroupSuggestOracle oracle;
private final SuggestBox suggestBox;
private boolean submitOnSelection;
public GroupReferenceBox() {
suggestions = new DefaultSuggestionDisplay();
textBox = new NpTextBox();
oracle = new AccountGroupSuggestOracle();
suggestBox = new SuggestBox( //
new RPCSuggestOracle(oracle), //
textBox, //
suggestions);
initWidget(suggestBox);
suggestBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
submitOnSelection = false;
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
if (suggestions.isSuggestionListShowing()) {
submitOnSelection = true;
} else {
SelectionEvent.fire(GroupReferenceBox.this, getValue());
}
}
}
});
suggestBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
suggestBox.setText("");
CloseEvent.fire(GroupReferenceBox.this, GroupReferenceBox.this);
}
}
});
suggestBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
@Override
public void onSelection(SelectionEvent<Suggestion> event) {
if (submitOnSelection) {
submitOnSelection = false;
SelectionEvent.fire(GroupReferenceBox.this, getValue());
}
}
});
}
public void setVisibleLength(int len) {
textBox.setVisibleLength(len);
}
@Override
public HandlerRegistration addSelectionHandler(
SelectionHandler<GroupReference> handler) {
return addHandler(handler, SelectionEvent.getType());
}
@Override
public HandlerRegistration addCloseHandler(
CloseHandler<GroupReferenceBox> handler) {
return addHandler(handler, CloseEvent.getType());
}
@Override
public GroupReference getValue() {
String name = suggestBox.getText();
if (name != null && !name.isEmpty()) {
return new GroupReference(oracle.getUUID(name), name);
} else {
return null;
}
}
@Override
public void setValue(GroupReference value) {
suggestBox.setText(value != null ? value.getName() : "");
}
@Override
public int getTabIndex() {
return suggestBox.getTabIndex();
}
@Override
public void setTabIndex(int index) {
suggestBox.setTabIndex(index);
}
public void setFocus(boolean focused) {
suggestBox.setFocus(focused);
}
@Override
public void setAccessKey(char key) {
suggestBox.setAccessKey(key);
}
}

View File

@@ -0,0 +1,297 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.SuggestUtil;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.ValueAwareEditor;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ValueLabel;
import java.util.ArrayList;
import java.util.List;
public class PermissionEditor extends Composite implements Editor<Permission>,
ValueAwareEditor<Permission> {
interface Binder extends UiBinder<HTMLPanel, PermissionEditor> {
}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField(provided = true)
@Path("name")
ValueLabel<String> normalName;
@UiField(provided = true)
@Path("name")
ValueLabel<String> deletedName;
@UiField
CheckBox exclusiveGroup;
@UiField
FlowPanel ruleContainer;
ListEditor<PermissionRule, PermissionRuleEditor> rules;
@UiField
DivElement addContainer;
@UiField
DivElement addStage1;
@UiField
DivElement addStage2;
@UiField
Anchor beginAddRule;
@UiField
@Editor.Ignore
GroupReferenceBox groupToAdd;
@UiField
Button addRule;
@UiField
Anchor deletePermission;
@UiField
DivElement normal;
@UiField
DivElement deleted;
private final boolean readOnly;
private final AccessSection section;
private Permission value;
private ApprovalType rangeType;
private boolean isDeleted;
public PermissionEditor(boolean readOnly, AccessSection section) {
this.readOnly = readOnly;
this.section = section;
normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
initWidget(uiBinder.createAndBindUi(this));
rules = ListEditor.of(new RuleEditorSource());
exclusiveGroup.setEnabled(!readOnly);
if (readOnly) {
addContainer.removeFromParent();
addContainer = null;
deletePermission.removeFromParent();
deletePermission = null;
}
}
@UiHandler("deletePermission")
void onDeleteHover(MouseOverEvent event) {
addStyleName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deletePermission")
void onDeleteNonHover(MouseOutEvent event) {
removeStyleName(AdminResources.I.css().deleteSectionHover());
}
@UiHandler("deletePermission")
void onDeletePermission(ClickEvent event) {
isDeleted = true;
normal.getStyle().setDisplay(Display.NONE);
deleted.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("undoDelete")
void onUndoDelete(ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("beginAddRule")
void onBeginAddRule(ClickEvent event) {
addStage1.getStyle().setDisplay(Display.NONE);
addStage2.getStyle().setDisplay(Display.BLOCK);
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
groupToAdd.setFocus(true);
}
});
}
@UiHandler("addRule")
void onAddGroupByClick(ClickEvent event) {
GroupReference ref = groupToAdd.getValue();
if (ref != null) {
addGroup(ref);
} else {
groupToAdd.setFocus(true);
}
}
@UiHandler("groupToAdd")
void onAddGroupByEnter(SelectionEvent<GroupReference> event) {
GroupReference ref = event.getSelectedItem();
if (ref != null) {
addGroup(ref);
}
}
@UiHandler("groupToAdd")
void onAbortAddGroup(CloseEvent<GroupReferenceBox> event) {
hideAddGroup();
}
@UiHandler("hideAddGroup")
void hideAddGroup(ClickEvent event) {
hideAddGroup();
}
private void hideAddGroup() {
addStage1.getStyle().setDisplay(Display.BLOCK);
addStage2.getStyle().setDisplay(Display.NONE);
}
private void addGroup(GroupReference ref) {
if (ref.getUUID() != null) {
if (value.getRule(ref) == null) {
PermissionRule newRule = value.getRule(ref, true);
if (rangeType != null) {
int min = rangeType.getMin().getValue();
int max = rangeType.getMax().getValue();
newRule.setRange(min, max);
}
rules.getList().add(newRule);
}
groupToAdd.setValue(null);
groupToAdd.setFocus(true);
} else {
// If the oracle didn't get to complete a UUID, resolve it now.
//
addRule.setEnabled(false);
SuggestUtil.SVC.suggestAccountGroup(ref.getName(), 1,
new GerritCallback<List<GroupReference>>() {
@Override
public void onSuccess(List<GroupReference> result) {
addRule.setEnabled(true);
if (result.size() == 1) {
addGroup(result.get(0));
} else {
groupToAdd.setFocus(true);
}
}
@Override
public void onFailure(Throwable caught) {
addRule.setEnabled(true);
super.onFailure(caught);
}
});
}
}
boolean isDeleted() {
return isDeleted;
}
@Override
public void setValue(Permission value) {
this.value = value;
if (value.isLabel()) {
rangeType =
Gerrit.getConfig().getApprovalTypes().byLabel(value.getLabel());
} else {
rangeType = null;
}
if (value != null && Permission.OWNER.equals(value.getName())) {
exclusiveGroup.setEnabled(false);
} else {
exclusiveGroup.setEnabled(!readOnly);
}
}
@Override
public void flush() {
List<PermissionRule> src = rules.getList();
List<PermissionRule> keep = new ArrayList<PermissionRule>(src.size());
for (int i = 0; i < src.size(); i++) {
PermissionRuleEditor e =
(PermissionRuleEditor) ruleContainer.getWidget(i);
if (!e.isDeleted()) {
keep.add(src.get(i));
}
}
value.setRules(keep);
}
@Override
public void onPropertyChange(String... paths) {
}
@Override
public void setDelegate(EditorDelegate<Permission> delegate) {
}
private class RuleEditorSource extends EditorSource<PermissionRuleEditor> {
@Override
public PermissionRuleEditor create(int index) {
PermissionRuleEditor subEditor =
new PermissionRuleEditor(readOnly, section, value, rangeType);
ruleContainer.insert(subEditor, index);
return subEditor;
}
@Override
public void dispose(PermissionRuleEditor subEditor) {
subEditor.removeFromParent();
}
@Override
public void setIndex(PermissionRuleEditor subEditor, int index) {
ruleContainer.insert(subEditor, index);
}
}
}

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:e='urn:import:com.google.gwt.editor.ui.client'
xmlns:my='urn:import:com.google.gerrit.client.admin'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
ui:generateLocales='default,en'
>
<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
<ui:style>
@eval selectionColor com.google.gerrit.client.Gerrit.getConfig().getSelectionColor();
@eval backgroundColor com.google.gerrit.client.Gerrit.getConfig().getBackgroundColor();
.panel {
position: relative;
}
.normal {
border: 1px solid backgroundColor;
margin-top: -1px;
margin-bottom: -1px;
}
.header {
padding-left: 5px;
padding-right: 5px;
padding-bottom: 1px;
white-space: nowrap;
}
.header:hover {
background-color: selectionColor;
}
.name {
font-style: italic;
}
.exclusiveGroup {
position: absolute;
top: 0;
right: 36px;
width: 7em;
font-size: 80%;
}
.addContainer {
padding-left: 10px;
position: relative;
}
.addContainer:hover {
background-color: selectionColor;
}
.addLink {
font-size: 80%;
}
.deleteIcon {
position: absolute;
top: 1px;
right: 12px;
}
</ui:style>
<g:HTMLPanel stylePrimaryName='{style.panel}'>
<div ui:field='normal' class='{style.normal}'>
<div class='{style.header}'>
<g:ValueLabel styleName='{style.name}' ui:field='normalName'/>
<g:CheckBox
ui:field='exclusiveGroup'
addStyleNames='{style.exclusiveGroup}'
text='Exclusive'>
<ui:attribute name='text'/>
</g:CheckBox>
<g:Anchor
ui:field='deletePermission'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.deleteIcon}'
title='Delete this permission (and nested rules)'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
<g:FlowPanel ui:field='ruleContainer'/>
<div ui:field='addContainer' class='{style.addContainer}'>
<div ui:field='addStage1'>
<g:Anchor
ui:field='beginAddRule'
styleName='{style.addLink}'
href='javascript:void'
text='Add Group'>
<ui:attribute name='text'/>
</g:Anchor>
</div>
<div ui:field='addStage2' style='display: none'>
<ui:msg>Group Name: <my:GroupReferenceBox
ui:field='groupToAdd'
visibleLength='45'/></ui:msg>
<g:Button
ui:field='addRule'
text='Add'>
<ui:attribute name='text'/>
</g:Button>
<g:Anchor
ui:field='hideAddGroup'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.deleteIcon}'
title='Cancel additional group'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
</div>
</div>
<div
ui:field='deleted'
class='{res.css.deleted} {res.css.deletedBorder}'
style='display: none'>
<ui:msg>Permission <g:ValueLabel styleName='{style.name}' ui:field='deletedName'/> was deleted</ui:msg>
<g:Anchor
ui:field='undoDelete'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.undoIcon}'
title='Undo deletion'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.common.data.Permission;
import com.google.gwt.text.shared.Renderer;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
class PermissionNameRenderer implements Renderer<String> {
static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
private static Map<String, String> LC;
@Override
public String render(String varName) {
if (Permission.isLabel(varName)) {
return Util.M.label(new Permission(varName).getLabel());
}
Map<String, String> m = Util.C.permissionNames();
String desc = m.get(varName);
if (desc == null) {
if (LC == null) {
LC = new HashMap<String, String>();
for (Map.Entry<String, String> e : m.entrySet()) {
LC.put(e.getKey().toLowerCase(), e.getValue());
}
}
desc = LC.get(varName.toLowerCase());
}
return desc != null ? desc : varName;
}
@Override
public void render(String object, Appendable appendable) throws IOException {
appendable.append(render(object));
}
}

View File

@@ -0,0 +1,200 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import static com.google.gerrit.common.data.Permission.PUSH;
import static com.google.gerrit.common.data.Permission.PUSH_TAG;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.ValueAwareEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.ValueListBox;
import java.io.IOException;
import java.util.Arrays;
public class PermissionRuleEditor extends Composite implements
Editor<PermissionRule>, ValueAwareEditor<PermissionRule> {
interface Binder extends UiBinder<HTMLPanel, PermissionRuleEditor> {
}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField(provided = true)
ValueListBox<PermissionRule.Action> action;
@UiField(provided = true)
ValueListBox<Integer> min;
@UiField(provided = true)
ValueListBox<Integer> max;
@UiField
CheckBox force;
@UiField
Hyperlink normalGroupName;
@UiField
SpanElement deletedGroupName;
@UiField
Anchor deleteRule;
@UiField
DivElement normal;
@UiField
DivElement deleted;
@UiField
SpanElement rangeEditor;
private boolean isDeleted;
public PermissionRuleEditor(boolean readOnly, AccessSection section,
Permission permission, ApprovalType labelRange) {
action = new ValueListBox<PermissionRule.Action>(actionRenderer);
min = new ValueListBox<Integer>(rangeRenderer);
max = new ValueListBox<Integer>(rangeRenderer);
if (labelRange != null){
min.setValue((int) labelRange.getMin().getValue());
max.setValue((int) labelRange.getMax().getValue());
min.setAcceptableValues(labelRange.getValuesAsList());
max.setAcceptableValues(labelRange.getValuesAsList());
} else {
action.setValue(PermissionRule.Action.ALLOW);
action.setAcceptableValues(Arrays.asList(PermissionRule.Action.values()));
}
initWidget(uiBinder.createAndBindUi(this));
String name = permission.getName();
boolean canForce = PUSH.equals(name) || PUSH_TAG.equals(name);
if (canForce) {
String ref = section.getRefPattern();
canForce = !ref.startsWith("refs/for/") && !ref.startsWith("^refs/for/");
}
force.setVisible(canForce);
force.setEnabled(!readOnly);
if (labelRange != null) {
action.getElement().getStyle().setDisplay(Display.NONE);
DOM.setElementPropertyBoolean(min.getElement(), "disabled", readOnly);
DOM.setElementPropertyBoolean(max.getElement(), "disabled", readOnly);
} else {
rangeEditor.getStyle().setDisplay(Display.NONE);
DOM.setElementPropertyBoolean(action.getElement(), "disabled", readOnly);
}
if (readOnly) {
deleteRule.removeFromParent();
deleteRule = null;
}
}
boolean isDeleted() {
return isDeleted;
}
@UiHandler("deleteRule")
void onDeleteRule(ClickEvent event) {
isDeleted = true;
normal.getStyle().setDisplay(Display.NONE);
deleted.getStyle().setDisplay(Display.BLOCK);
}
@UiHandler("undoDelete")
void onUndoDelete(ClickEvent event) {
isDeleted = false;
deleted.getStyle().setDisplay(Display.NONE);
normal.getStyle().setDisplay(Display.BLOCK);
}
@Override
public void setValue(PermissionRule value) {
GroupReference ref = value.getGroup();
normalGroupName.setTargetHistoryToken(Dispatcher.toGroup(ref.getUUID()));
normalGroupName.setText(ref.getName());
deletedGroupName.setInnerText(ref.getName());
}
@Override
public void setDelegate(EditorDelegate<PermissionRule> delegate) {
}
@Override
public void flush() {
}
@Override
public void onPropertyChange(String... paths) {
}
private static class ActionRenderer implements
Renderer<PermissionRule.Action> {
@Override
public String render(PermissionRule.Action object) {
return object != null ? object.toString() : "";
}
@Override
public void render(PermissionRule.Action object, Appendable appendable)
throws IOException {
appendable.append(render(object));
}
}
private static class RangeRenderer implements Renderer<Integer> {
@Override
public String render(Integer object) {
if (0 <= object) {
return "+" + object;
} else {
return String.valueOf(object);
}
}
@Override
public void render(Integer object, Appendable appendable)
throws IOException {
appendable.append(render(object));
}
}
private static final ActionRenderer actionRenderer = new ActionRenderer();
private static final RangeRenderer rangeRenderer = new RangeRenderer();
}

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:e='urn:import:com.google.gwt.editor.ui.client'
xmlns:my='urn:import:com.google.gerrit.client.admin'
xmlns:q='urn:import:com.google.gerrit.client.ui'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
ui:generateLocales='default,en'
>
<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
<ui:style>
@eval selectionColor com.google.gerrit.client.Gerrit.getConfig().getSelectionColor();
.panel {
position: relative;
height: 1.5em;
}
.panel:hover {
background-color: selectionColor;
}
.normal {
padding-left: 10px;
white-space: nowrap;
height: 100%;
}
.deleted {
height: 100%;
}
.actionList, .minmax {
font-size: 80%;
}
.forcePush {
position: absolute;
top: 0;
right: 36px;
width: 7em;
font-size: 80%;
}
.deleteIcon {
position: absolute;
top: 2px;
right: 11px;
}
.groupName {
display: inline;
}
</ui:style>
<g:HTMLPanel styleName='{style.panel}'>
<div ui:field='normal' class='{style.normal}'>
<g:ValueListBox ui:field='action' styleName='{style.actionList}'/>
<span ui:field='rangeEditor'>
<g:ValueListBox ui:field='min' styleName='{style.minmax}'/>
<g:ValueListBox ui:field='max' styleName='{style.minmax}'/>
</span>
<q:Hyperlink ui:field='normalGroupName' styleName='{style.groupName}'/>
<g:CheckBox
ui:field='force'
addStyleNames='{style.forcePush}'
text='Force Push'>
<ui:attribute name='text'/>
</g:CheckBox>
<g:Anchor
ui:field='deleteRule'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.deleteIcon}'
title='Delete this rule'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
<div
ui:field='deleted'
class='{res.css.deleted} {style.deleted}'
style='display: none'>
<ui:msg>Group <span ui:field='deletedGroupName'/> was deleted</ui:msg>
<g:Anchor
ui:field='undoDelete'
href='javascript:void'
styleName='{style.deleteIcon} {res.css.undoIcon}'
title='Undo deletion'>
<ui:attribute name='title'/>
</g:Anchor>
</div>
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -0,0 +1,142 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorDelegate;
import com.google.gwt.editor.client.ValueAwareEditor;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import java.util.ArrayList;
import java.util.List;
public class ProjectAccessEditor extends Composite implements
Editor<ProjectAccess>, ValueAwareEditor<ProjectAccess> {
interface Binder extends UiBinder<HTMLPanel, ProjectAccessEditor> {
}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField
DivElement inheritsFrom;
@UiField
Hyperlink parentProject;
@UiField
FlowPanel localContainer;
ListEditor<AccessSection, AccessSectionEditor> local;
@UiField
Anchor addSection;
private ProjectAccess value;
public ProjectAccessEditor() {
initWidget(uiBinder.createAndBindUi(this));
local = ListEditor.of(new Source(localContainer));
}
@UiHandler("addSection")
void onAddSection(ClickEvent event) {
int index = local.getList().size();
local.getList().add(new AccessSection("refs/heads/*"));
AccessSectionEditor editor = local.getEditors().get(index);
editor.enableEditing();
editor.editRefPattern();
}
@Override
public void setValue(ProjectAccess value) {
this.value = value;
Project.NameKey parent = value.getInheritsFrom();
if (parent != null) {
inheritsFrom.getStyle().setDisplay(Display.BLOCK);
parentProject.setText(parent.get());
parentProject.setTargetHistoryToken( //
Dispatcher.toProjectAdmin(parent, ProjectScreen.ACCESS));
} else {
inheritsFrom.getStyle().setDisplay(Display.NONE);
}
addSection.setVisible(value != null && !value.getOwnerOf().isEmpty());
}
@Override
public void flush() {
List<AccessSection> src = local.getList();
List<AccessSection> keep = new ArrayList<AccessSection>(src.size());
for (int i = 0; i < src.size(); i++) {
AccessSectionEditor e = (AccessSectionEditor) localContainer.getWidget(i);
if (!e.isDeleted()) {
keep.add(src.get(i));
}
}
value.setLocal(keep);
}
@Override
public void onPropertyChange(String... paths) {
}
@Override
public void setDelegate(EditorDelegate<ProjectAccess> delegate) {
}
private class Source extends EditorSource<AccessSectionEditor> {
private final FlowPanel container;
Source(FlowPanel container) {
this.container = container;
}
@Override
public AccessSectionEditor create(int index) {
AccessSectionEditor subEditor = new AccessSectionEditor(value);
container.insert(subEditor, index);
return subEditor;
}
@Override
public void dispose(AccessSectionEditor subEditor) {
subEditor.removeFromParent();
}
@Override
public void setIndex(AccessSectionEditor subEditor, int index) {
container.insert(subEditor, index);
}
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:q='urn:import:com.google.gerrit.client.ui'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
ui:generateLocales='default,en'
>
<ui:style>
.inheritsFrom {
margin-bottom: 0.5em;
}
.parentTitle {
font-weight: bold;
}
.parentLink {
display: inline;
}
.addContainer {
margin-top: 5px;
font-size: 80%;
}
.addContainer:hover {
background-color: selectionColor;
}
</ui:style>
<g:HTMLPanel>
<div ui:field='inheritsFrom' class='{style.inheritsFrom}'>
<span class='{style.parentTitle}'><ui:msg>Rights Inherit From:</ui:msg></span>
<q:Hyperlink ui:field='parentProject' styleName='{style.parentLink}'/>
</div>
<g:FlowPanel ui:field='localContainer'/>
<div class='{style.addContainer}'>
<g:Anchor
ui:field='addSection'
href='javascript:void'
text='Add Reference'>
<ui:attribute name='text'/>
</g:Anchor>
</div>
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -0,0 +1,129 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.globalkey.client.NpTextArea;
public class ProjectAccessScreen extends ProjectScreen {
interface Binder extends UiBinder<HTMLPanel, ProjectAccessScreen> {
}
private static final Binder uiBinder = GWT.create(Binder.class);
interface Driver extends SimpleBeanEditorDriver< //
ProjectAccess, //
ProjectAccessEditor> {
}
@UiField
ProjectAccessEditor accessEditor;
@UiField
DivElement commitTools;
@UiField
NpTextArea commitMessage;
@UiField
Button commit;
private Driver driver;
public ProjectAccessScreen(final Project.NameKey toShow) {
super(toShow);
}
@Override
protected void onInitUI() {
super.onInitUI();
add(uiBinder.createAndBindUi(this));
driver = GWT.create(Driver.class);
driver.initialize(accessEditor);
}
@Override
protected void onLoad() {
super.onLoad();
Util.PROJECT_SVC.projectAccess(getProjectKey(),
new ScreenLoadCallback<ProjectAccess>(this) {
@Override
public void preDisplay(ProjectAccess access) {
edit(access);
}
});
}
void edit(ProjectAccess access) {
driver.edit(access);
UIObject.setVisible(commitTools, !access.getOwnerOf().isEmpty());
}
@UiHandler("commit")
void onCommit(ClickEvent event) {
ProjectAccess access = driver.flush();
if (driver.hasErrors()) {
Window.alert(Util.C.errorsMustBeFixed());
return;
}
String message = commitMessage.getText().trim();
if ("".equals(message)) {
message = null;
}
enable(false);
Util.PROJECT_SVC.changeProjectAccess( //
getProjectKey(), //
access.getRevision(), //
message, //
access.getLocal(), //
new GerritCallback<ProjectAccess>() {
@Override
public void onSuccess(ProjectAccess access) {
commitMessage.setText("");
edit(access);
enable(true);
}
@Override
public void onFailure(Throwable caught) {
enable(true);
super.onFailure(caught);
}
});
}
private void enable(boolean enabled) {
commitMessage.setEnabled(enabled);
commit.setEnabled(enabled);
}
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:my='urn:import:com.google.gerrit.client.admin'
xmlns:expui='urn:import:com.google.gwtexpui.globalkey.client'
ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
ui:generateKeys='com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator'
ui:generateLocales='default,en'
>
<ui:style>
@external .gwt-TextArea;
.commitMessage {
margin-top: 2em;
}
.commitMessage .gwt-TextArea {
margin: 5px 5px 5px 5px;
}
</ui:style>
<g:HTMLPanel>
<my:ProjectAccessEditor ui:field='accessEditor'/>
<div ui:field='commitTools'>
<div class='{style.commitMessage}'>
<ui:msg>Commit Message (optional):</ui:msg><br/>
<expui:NpTextArea
ui:field='commitMessage'
visibleLines='4'
characterWidth='60'
spellCheck='true'
/>
</div>
<g:Button
ui:field='commit'
text='Save Changes'>
<ui:attribute name='text'/>
</g:Button>
</div>
<div style='width: 35em; visibility: hidden;' />
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.text.shared.Parser;
import com.google.gwt.text.shared.Renderer;
import com.google.gwt.user.client.ui.ValueBox;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import java.io.IOException;
import java.text.ParseException;
public class RefPatternBox extends ValueBox<String> {
private static final Renderer<String> RENDERER = new Renderer<String>() {
public String render(String ref) {
return ref;
}
public void render(String ref, Appendable dst) throws IOException {
dst.append(render(ref));
}
};
private static final Parser<String> PARSER = new Parser<String>() {
public String parse(CharSequence text) throws ParseException {
String ref = text.toString();
if (ref.isEmpty()) {
throw new ParseException(Util.C.refErrorEmpty(), 0);
}
if (ref.charAt(0) == '/') {
throw new ParseException(Util.C.refErrorBeginSlash(), 0);
}
final boolean re = ref.charAt(0) == '^';
if (re && !ref.startsWith("^refs/")) {
ref = "^refs/heads/" + ref.substring(1);
} else if (!ref.startsWith("refs/")) {
ref = "refs/heads/" + ref;
}
for (int i = 0; i < ref.length(); i++) {
final char c = ref.charAt(i);
if (c == '/' && 0 < i && ref.charAt(i - 1) == '/') {
throw new ParseException(Util.C.refErrorDoubleSlash(), i);
}
if (c == ' ') {
throw new ParseException(Util.C.refErrorNoSpace(), i);
}
if (c < ' ') {
throw new ParseException(Util.C.refErrorPrintable(), i);
}
}
return ref;
}
};
public RefPatternBox() {
super(Document.get().createTextInputElement(), RENDERER, PARSER);
addKeyPressHandler(GlobalKey.STOP_PROPAGATION);
addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
if (event.getCharCode() == ' ') {
event.preventDefault();
}
}
});
}
}

View File

@@ -32,6 +32,8 @@ public class Util {
PROJECT_SVC = GWT.create(ProjectAdminService.class);
JsonUtil.bind(PROJECT_SVC, "rpc/ProjectAdminService");
AdminResources.I.css().ensureInjected();
}
public static String toLongString(final Project.SubmitType type) {

View File

@@ -0,0 +1,206 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.admin;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.editor.client.HasEditorErrors;
import com.google.gwt.editor.client.IsEditor;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.ui.client.adapters.ValueBoxEditor;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiChild;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.ValueBoxBase;
import com.google.gwt.user.client.ui.Widget;
import java.text.ParseException;
import java.util.List;
public class ValueEditor<T> extends Composite implements HasEditorErrors<T>,
IsEditor<ValueBoxEditor<T>>, LeafValueEditor<T>, Focusable {
interface Binder extends UiBinder<Widget, ValueEditor<?>> {
}
static final Binder uiBinder = GWT.create(Binder.class);
@UiField
SimplePanel textPanel;
private Label textLabel;
private StartEditHandlers startHandlers;
@UiField
Image editIcon;
@UiField
SimplePanel editPanel;
@UiField
DivElement errorLabel;
private ValueBoxBase<T> editChild;
private ValueBoxEditor<T> editProxy;
public ValueEditor() {
startHandlers = new StartEditHandlers();
initWidget(uiBinder.createAndBindUi(this));
editPanel.setVisible(false);
editIcon.addClickHandler(startHandlers);
}
public void edit() {
textPanel.removeFromParent();
textPanel = null;
textLabel = null;
editIcon.removeFromParent();
editIcon = null;
startHandlers = null;
editPanel.setVisible(true);
}
public ValueBoxEditor<T> asEditor() {
if (editProxy == null) {
editProxy = new EditorProxy();
}
return editProxy;
}
@Override
public T getValue() {
return asEditor().getValue();
}
@Override
public void setValue(T value) {
asEditor().setValue(value);
}
public void setEditTitle(String title) {
editIcon.setTitle(title);
}
@UiChild(limit = 1, tagname = "display")
public void setDisplay(Label widget) {
textLabel = widget;
textPanel.add(textLabel);
textLabel.addClickHandler(startHandlers);
textLabel.addDoubleClickHandler(startHandlers);
}
@UiChild(limit = 1, tagname = "editor")
public void setEditor(ValueBoxBase<T> widget) {
editChild = widget;
editPanel.add(editChild);
editProxy = null;
}
public void setEnabled(boolean enabled) {
editIcon.setVisible(enabled);
startHandlers.enabled = enabled;
}
public void showErrors(List<EditorError> errors) {
StringBuilder buf = new StringBuilder();
for (EditorError error : errors) {
if (error.getEditor().equals(editProxy)) {
buf.append("\n");
if (error.getUserData() instanceof ParseException) {
buf.append(((ParseException) error.getUserData()).getMessage());
} else {
buf.append(error.getMessage());
}
}
}
if (0 < buf.length()) {
errorLabel.setInnerText(buf.substring(1));
errorLabel.getStyle().setDisplay(Display.BLOCK);
} else {
errorLabel.setInnerText("");
errorLabel.getStyle().setDisplay(Display.NONE);
}
}
@Override
public void setAccessKey(char key) {
editChild.setAccessKey(key);
}
@Override
public void setFocus(boolean focused) {
editChild.setFocus(focused);
if (focused) {
editChild.setCursorPos(editChild.getText().length());
}
}
@Override
public int getTabIndex() {
return editChild.getTabIndex();
}
@Override
public void setTabIndex(int index) {
editChild.setTabIndex(index);
}
private class StartEditHandlers implements ClickHandler, DoubleClickHandler {
boolean enabled;
@Override
public void onClick(ClickEvent event) {
if (enabled && event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
edit();
}
}
@Override
public void onDoubleClick(DoubleClickEvent event) {
if (enabled && event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
edit();
}
}
}
private class EditorProxy extends ValueBoxEditor<T> {
EditorProxy() {
super(editChild);
}
@Override
public void setValue(T value) {
super.setValue(value);
if (textLabel == null) {
setDisplay(new Label());
}
textLabel.setText(editChild.getText());
}
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
>
<ui:with field='res' type='com.google.gerrit.client.admin.AdminResources'/>
<ui:style>
.panel {
position: relative;
white-space: nowrap;
}
.textPanel {
width: 100%;
padding-right: 21px;
}
.editIcon {
position: absolute;
top: 0;
right: 5px;
}
.editPanel {
width: 100%;
}
.errorLabel {
display: none;
color: red;
white-space: pre;
}
</ui:style>
<g:HTMLPanel stylePrimaryName='{style.panel}'>
<g:Image
ui:field='editIcon'
resource='{res.editText}'
stylePrimaryName='{style.editIcon}'
title='Edit'>
<ui:attribute name='title'/>
</g:Image>
<g:SimplePanel ui:field='textPanel' stylePrimaryName='{style.textPanel}'/>
<g:SimplePanel ui:field='editPanel' stylePrimaryName='{style.editPanel}'/>
<div
ui:field='errorLabel'
class='{style.errorLabel}'/>
</g:HTMLPanel>
</ui:UiBinder>

View File

@@ -0,0 +1,53 @@
/* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@eval selectionColor com.google.gerrit.client.Gerrit.getConfig().getSelectionColor();
@eval textColor com.google.gerrit.client.Gerrit.getConfig().getTextColor();
@def deletedBackground #a9a9a9;
@sprite .deleteIcon {
gwt-image: 'deleteNormal';
border: none;
}
@sprite .deleteIcon:hover {
gwt-image: 'deleteHover';
border: none;
}
@sprite .undoIcon {
gwt-image: 'undoNormal';
border: none;
}
.deleted {
background-color: deletedBackground;
color: #ffffff;
white-space: nowrap;
padding-left: 50px;
}
.deleted:hover {
background-color: selectionColor;
color: textColor;
}
.deletedBorder {
background: 1px solid deletedBackground;
}
.deleteSectionHover {
background-color: selectionColor !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -16,26 +16,34 @@ package com.google.gerrit.client.ui;
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Suggestion Oracle for AccountGroup entities. */
public class AccountGroupSuggestOracle extends HighlightSuggestOracle {
private Map<String, AccountGroup.UUID> priorResults =
new HashMap<String, AccountGroup.UUID>();
@Override
public void onRequestSuggestions(final Request req, final Callback callback) {
RpcStatus.hide(new Runnable() {
public void run() {
SuggestUtil.SVC.suggestAccountGroup(req.getQuery(), req.getLimit(),
new GerritCallback<List<AccountGroupName>>() {
public void onSuccess(final List<AccountGroupName> result) {
new GerritCallback<List<GroupReference>>() {
public void onSuccess(final List<GroupReference> result) {
priorResults.clear();
final ArrayList<AccountGroupSuggestion> r =
new ArrayList<AccountGroupSuggestion>(result.size());
for (final AccountGroupName p : result) {
for (final GroupReference p : result) {
r.add(new AccountGroupSuggestion(p));
priorResults.put(p.getName(), p.getUUID());
}
callback.onSuggestionsReady(req, new Response(r));
}
@@ -46,9 +54,9 @@ public class AccountGroupSuggestOracle extends HighlightSuggestOracle {
private static class AccountGroupSuggestion implements
SuggestOracle.Suggestion {
private final AccountGroupName info;
private final GroupReference info;
AccountGroupSuggestion(final AccountGroupName k) {
AccountGroupSuggestion(final GroupReference k) {
info = k;
}
@@ -60,4 +68,9 @@ public class AccountGroupSuggestOracle extends HighlightSuggestOracle {
return info.getName();
}
}
/** @return the group UUID, or null if it cannot be found. */
public AccountGroup.UUID getUUID(String name) {
return priorResults.get(name);
}
}

View File

@@ -25,6 +25,10 @@ import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
public class Hyperlink extends com.google.gwt.user.client.ui.Hyperlink {
static final HyperlinkImpl impl = GWT.create(HyperlinkImpl.class);
/** Initialize a default hyperlink with no target and no text. */
public Hyperlink() {
}
/**
* Creates a hyperlink with its text and target history token specified.
*

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.rpc;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.SuggestService;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.Account;
@@ -29,6 +30,7 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
@@ -59,6 +61,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<CurrentUser> currentUser;
private final SuggestAccountsEnum suggestAccounts;
private final GroupCache groupCache;
@Inject
SuggestServiceImpl(final Provider<ReviewDb> schema,
@@ -68,7 +71,8 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
final GroupControl.Factory groupControlFactory,
final IdentifiedUser.GenericFactory userFactory,
final Provider<CurrentUser> currentUser,
@GerritServerConfig final Config cfg) {
@GerritServerConfig final Config cfg,
final GroupCache groupCache) {
super(schema, currentUser);
this.authConfig = authConfig;
this.projectControlFactory = projectControlFactory;
@@ -79,6 +83,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
this.currentUser = currentUser;
this.suggestAccounts =
cfg.getEnum("suggest", null, "accounts", SuggestAccountsEnum.ALL);
this.groupCache = groupCache;
}
public void suggestProjectNameKey(final String query, final int limit,
@@ -182,27 +187,30 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
}
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<AccountGroupName>> callback) {
run(callback, new Action<List<AccountGroupName>>() {
public List<AccountGroupName> run(final ReviewDb db) throws OrmException {
final AsyncCallback<List<GroupReference>> callback) {
run(callback, new Action<List<GroupReference>>() {
public List<GroupReference> run(final ReviewDb db) throws OrmException {
final String a = query;
final String b = a + MAX_SUFFIX;
final int max = 10;
final int n = limit <= 0 ? max : Math.min(limit, max);
Set<AccountGroup.UUID> memberOf = currentUser.get().getEffectiveGroups();
List<AccountGroupName> names = new ArrayList<AccountGroupName>(n);
List<GroupReference> r = new ArrayList<GroupReference>(n);
for (AccountGroupName group : db.accountGroupNames()
.suggestByName(a, b, n)) {
try {
if (memberOf.contains(group.getId())
|| groupControlFactory.controlFor(group.getId()).isVisible()) {
names.add(group);
AccountGroup g = groupCache.get(group.getId());
if (g != null && g.getGroupUUID() != null) {
r.add(GroupReference.forGroup(g));
}
}
} catch (NoSuchGroupException e) {
continue;
}
}
return names;
return r;
}
});
}

View File

@@ -119,8 +119,14 @@ class GroupAdminServiceImpl extends BaseServiceImplementation implements
createGroupFactory.create(newName).to(callback);
}
public void groupDetail(final AccountGroup.Id groupId,
final AsyncCallback<GroupDetail> callback) {
public void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID groupUUID,
AsyncCallback<GroupDetail> callback) {
if (groupId == null && groupUUID != null) {
AccountGroup g = groupCache.get(groupUUID);
if (g != null) {
groupId = g.getId();
}
}
groupDetailFactory.create(groupId).to(callback);
}

View File

@@ -0,0 +1,206 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmConcurrencyException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
class ChangeProjectAccess extends Handler<ProjectAccess> {
interface Factory {
ChangeProjectAccess create(@Assisted Project.NameKey projectName,
@Assisted ObjectId base, @Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message);
}
private final ProjectAccessFactory.Factory projectAccessFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final GroupCache groupCache;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final Project.NameKey projectName;
private final ObjectId base;
private List<AccessSection> sectionList;
private String message;
@Inject
ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final GroupCache groupCache,
final MetaDataUpdate.User metaDataUpdateFactory,
@Assisted final Project.NameKey projectName,
@Assisted final ObjectId base, @Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message) {
this.projectAccessFactory = projectAccessFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.groupCache = groupCache;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectName = projectName;
this.base = base;
this.sectionList = sectionList;
this.message = message;
}
@Override
public ProjectAccess call() throws NoSuchProjectException, IOException,
ConfigInvalidException, InvalidNameException, NoSuchGroupException,
OrmConcurrencyException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
final MetaDataUpdate md;
try {
md = metaDataUpdateFactory.create(projectName);
} catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(projectName);
}
try {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
for (AccessSection section : mergeSections(sectionList)) {
final String name = section.getRefPattern();
if (!projectControl.controlForRef(name).isOwner()) {
continue;
}
if (name.startsWith(AccessSection.REGEX_PREFIX)) {
if (!Repository.isValidRefName(RefControl.shortestExample(name))) {
throw new InvalidNameException();
}
} else if (name.equals(AccessSection.ALL)) {
// This is a special case we have to allow, it fails below.
} else if (name.endsWith("/*")) {
String prefix = name.substring(0, name.length() - 2);
if (!Repository.isValidRefName(prefix)) {
throw new InvalidNameException();
}
} else if (!Repository.isValidRefName(name)) {
throw new InvalidNameException();
}
for (Permission permission : section.getPermissions()) {
for (PermissionRule rule : permission.getRules()) {
lookupGroup(rule);
}
}
config.replace(section);
toDelete.remove(section.getRefPattern());
}
for (String name : toDelete) {
if (projectControl.controlForRef(name).isOwner()) {
config.remove(config.getAccessSection(name));
}
}
if (message != null && !message.isEmpty()) {
if (!message.endsWith("\n")) {
message += "\n";
}
md.setMessage(message);
} else {
md.setMessage("Modify access rules\n");
}
if (config.commit(md)) {
projectCache.evict(config.getProject());
return projectAccessFactory.create(projectName).call();
} else {
throw new OrmConcurrencyException("Cannot update " + projectName);
}
} finally {
md.close();
}
}
private static List<AccessSection> mergeSections(List<AccessSection> src) {
Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
for (AccessSection section : src) {
if (section.getPermissions().isEmpty()) {
continue;
}
AccessSection prior = map.get(section.getRefPattern());
if (prior != null) {
prior.mergeFrom(section);
} else {
map.put(section.getRefPattern(), section);
}
}
return new ArrayList<AccessSection>(map.values());
}
private static Set<String> scanSectionNames(ProjectConfig config) {
Set<String> names = new HashSet<String>();
for (AccessSection section : config.getAccessSections()) {
names.add(section.getRefPattern());
}
return names;
}
private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
GroupReference ref = rule.getGroup();
if (ref.getUUID() == null) {
AccountGroup.NameKey name = new AccountGroup.NameKey(ref.getName());
AccountGroup group = groupCache.get(name);
if (group == null) {
throw new NoSuchGroupException(name);
}
ref.setUUID(group.getGroupUUID());
}
}
}

View File

@@ -0,0 +1,135 @@
// Copyright (C) 2011 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class ProjectAccessFactory extends Handler<ProjectAccess> {
interface Factory {
ProjectAccessFactory create(@Assisted Project.NameKey name);
}
private final GroupCache groupCache;
private final ProjectCache projectCache;
private final ProjectControl.Factory projectControlFactory;
private final MetaDataUpdate.Server metaDataUpdateFactory;
private final Project.NameKey wildProject;
private final Project.NameKey projectName;
private ProjectControl pc;
@Inject
ProjectAccessFactory(final GroupCache groupCache,
final ProjectCache projectCache,
final ProjectControl.Factory projectControlFactory,
final MetaDataUpdate.Server metaDataUpdateFactory,
@WildProjectName final Project.NameKey wildProject,
@Assisted final Project.NameKey name) {
this.groupCache = groupCache;
this.projectCache = projectCache;
this.projectControlFactory = projectControlFactory;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.wildProject = wildProject;
this.projectName = name;
}
@Override
public ProjectAccess call() throws NoSuchProjectException, IOException,
ConfigInvalidException {
pc = open();
// Load the current configuration from the repository, ensuring its the most
// recent version available. If it differs from what was in the project
// state, force a cache flush now.
//
ProjectConfig config;
MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
try {
config = ProjectConfig.read(md);
if (config.updateGroupNames(groupCache)) {
md.setMessage("Update group names\n");
if (config.commit(md)) {
projectCache.evict(config.getProject());
pc = open();
}
} else if (config.getRevision() != null
&& !config.getRevision().equals(
pc.getProjectState().getConfig().getRevision())) {
projectCache.evict(config.getProject());
pc = open();
}
} finally {
md.close();
}
List<AccessSection> local = new ArrayList<AccessSection>();
Set<String> ownerOf = new HashSet<String>();
for (AccessSection section : config.getAccessSections()) {
RefControl rc = pc.controlForRef(section.getRefPattern());
if (rc.isOwner()) {
local.add(section);
ownerOf.add(section.getRefPattern());
} else if (rc.isVisible()) {
local.add(section);
}
}
final ProjectAccess detail = new ProjectAccess();
detail.setRevision(config.getRevision().name());
detail.setLocal(local);
detail.setOwnerOf(ownerOf);
if (projectName.equals(wildProject)) {
detail.setInheritsFrom(null);
} else if (config.getProject().getParent() != null) {
detail.setInheritsFrom(config.getProject().getParent());
} else {
detail.setInheritsFrom(wildProject);
}
return detail;
}
private ProjectControl open() throws NoSuchProjectException {
return projectControlFactory.validateFor( //
projectName, //
ProjectControl.OWNER | ProjectControl.VISIBLE);
}
}

View File

@@ -14,7 +14,9 @@
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.ProjectAdminService;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.Branch;
@@ -22,29 +24,37 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
import java.util.List;
import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final ChangeProjectAccess.Factory changeProjectAccessFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final ListBranches.Factory listBranchesFactory;
private final VisibleProjects.Factory visibleProjectsFactory;
private final ProjectAccessFactory.Factory projectAccessFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final ChangeProjectAccess.Factory changeProjectAccessFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final ListBranches.Factory listBranchesFactory,
final VisibleProjects.Factory visibleProjectsFactory,
final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectDetailFactory.Factory projectDetailFactory) {
this.addBranchFactory = addBranchFactory;
this.changeProjectAccessFactory = changeProjectAccessFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.listBranchesFactory = listBranchesFactory;
this.visibleProjectsFactory = visibleProjectsFactory;
this.projectAccessFactory = projectAccessFactory;
this.projectDetailFactory = projectDetailFactory;
}
@@ -59,12 +69,26 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
projectDetailFactory.create(projectName).to(callback);
}
@Override
public void projectAccess(final Project.NameKey projectName,
final AsyncCallback<ProjectAccess> callback) {
projectAccessFactory.create(projectName).to(callback);
}
@Override
public void changeProjectSettings(final Project update,
final AsyncCallback<ProjectDetail> callback) {
changeProjectSettingsFactory.create(update).to(callback);
}
@Override
public void changeProjectAccess(Project.NameKey projectName,
String baseRevision, String msg, List<AccessSection> sections,
AsyncCallback<ProjectAccess> cb) {
ObjectId base = ObjectId.fromString(baseRevision);
changeProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
}
@Override
public void listBranches(final Project.NameKey projectName,
final AsyncCallback<ListBranchesResult> callback) {

View File

@@ -29,10 +29,12 @@ public class ProjectModule extends RpcServletModule {
@Override
protected void configure() {
factory(AddBranch.Factory.class);
factory(ChangeProjectAccess.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
factory(ListBranches.Factory.class);
factory(VisibleProjects.Factory.class);
factory(ProjectAccessFactory.Factory.class);
factory(ProjectDetailFactory.Factory.class);
}
});

View File

@@ -70,6 +70,13 @@ public final class AccountGroup {
protected void set(String newValue) {
uuid = newValue;
}
/** Parse an AccountGroup.UUID out of a string representation. */
public static UUID parse(final String str) {
final UUID r = new UUID();
r.fromString(str);
return r;
}
}
/** Distinguished name, within organization directory server. */

View File

@@ -108,12 +108,24 @@ public class ProjectConfig extends VersionedMetaData {
}
public Collection<AccessSection> getAccessSections() {
return accessSections.values();
return sort(accessSections.values());
}
public void remove(AccessSection section) {
if (section != null) {
accessSections.remove(section.getRefPattern());
}
}
public void replace(AccessSection section) {
for (Permission permission : section.getPermissions()) {
for (PermissionRule rule : permission.getRules()) {
rule.setGroup(resolve(rule.getGroup()));
}
}
accessSections.put(section.getRefPattern(), section);
}
public GroupReference resolve(AccountGroup group) {
return resolve(GroupReference.forGroup(group));