Add account permission page

This commit is contained in:
steve 2013-10-13 11:27:48 +08:00
parent 4d85f8275a
commit 77f9f2633e
16 changed files with 461 additions and 89 deletions

View File

@ -12,9 +12,11 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.Application;
import org.apache.wicket.Page;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.Session;
import org.apache.wicket.bean.validation.BeanValidationConfiguration;
import org.apache.wicket.core.request.mapper.MountedMapper;
import org.apache.wicket.devutils.stateless.StatelessChecker;
import org.apache.wicket.markup.html.IPackageResourceGuard;
import org.apache.wicket.markup.html.SecurePackageResourceGuard;
import org.apache.wicket.request.IRequestMapper;
@ -39,6 +41,8 @@ import com.pmease.gitop.web.page.account.AccountHomePage;
import com.pmease.gitop.web.page.account.RegisterPage;
import com.pmease.gitop.web.page.account.setting.password.AccountPasswordPage;
import com.pmease.gitop.web.page.account.setting.permission.AccountPermissionPage;
import com.pmease.gitop.web.page.account.setting.permission.AddTeamPage;
import com.pmease.gitop.web.page.account.setting.permission.EditTeamPage;
import com.pmease.gitop.web.page.account.setting.profile.AccountProfilePage;
import com.pmease.gitop.web.page.account.setting.repos.AccountReposPage;
import com.pmease.gitop.web.page.home.HomePage;
@ -102,15 +106,17 @@ public class GitopWebApp extends AbstractWicketConfig {
loadDefaultUserAvatarData();
new ShiroWicketPlugin().mountLoginPage("login", LoginPage.class)
.mountLogoutPage("logout", LogoutPage.class).install(this);
new ShiroWicketPlugin()
.mountLoginPage("login", LoginPage.class)
.mountLogoutPage("logout", LogoutPage.class)
.install(this);
mountPages();
configureResources();
// if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) {
// getComponentPreOnBeforeRenderListeners().add(new StatelessChecker());
// }
if (getConfigurationType() == RuntimeConfigurationType.DEVELOPMENT) {
getComponentPreOnBeforeRenderListeners().add(new StatelessChecker());
}
}
public byte[] getDefaultUserAvatar() {
@ -173,6 +179,8 @@ public class GitopWebApp extends AbstractWicketConfig {
mountPage("settings/password", AccountPasswordPage.class);
mountPage("settings/permission", AccountPermissionPage.class);
mountPage("settings/repos", AccountReposPage.class);
mountPage("teams/add", AddTeamPage.class);
mountPage("teams/edit/${teamId}", EditTeamPage.class);
mountPage("/test", TestPage.class);
mountPage("test2", TestPage2.class);

View File

@ -38,6 +38,14 @@ ul.disc { list-style:disc outside; }
.piped:after { clear: both; }
ul.piped, ol.piped { margin: 0; padding: 0; }
/* LINKS */
.checkable-link { color: #333; cursor: pointer; }
.checkable-link:hover { text-decoration: none; }
.checkable-link > .icon-checkbox:before { content: "\f096"; width: 16px; }
.checkable-link.checked > .icon-checkbox:before { content: "\f046"; width: 16px; }
.checkable-link.disabled { color: #888; }
em.checkable-link { color: #888 !important; }
/** LIST GROUP */
.list-navs > .list-group-item { border-width: 1px 0; }
.list-navs > .list-group-item:first-child,
@ -173,6 +181,9 @@ a > .icon-null, .icon-null { display: inline-block; width: 10px; }
background: #2ba6cb;
}
.btn-radio-group .btn .icon-ok { display: none; }
.btn-radio-group .btn.active { background: #999; color: white; }
.btn-radio-group .btn.active .icon-ok, .btn-radio-group .btn:hover .icon-ok { display: inline-block; }
/**
* TABLE
*/

View File

@ -4,7 +4,6 @@ import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.html.form.Form;
@ -20,13 +19,11 @@ public class AjaxConfirmButton extends AjaxButton {
}
public AjaxConfirmButton(String id, Form<?> form, IModel<String> textModel,
IModel<VexIcon> iconModel, IModel<String> yesLabelModel,
final IModel<VexIcon> iconModel, IModel<String> yesLabelModel,
IModel<String> noLabelModel, IModel<String> confirmCssClassModel) {
super(id, form);
add(new VexLinkBehavior(textModel, iconModel, yesLabelModel,
noLabelModel, confirmCssClassModel));
add(AttributeAppender.append("class", "confirm-link"));
}
@Override

View File

@ -0,0 +1,12 @@
package com.pmease.gitop.web.model;
import com.pmease.gitop.core.model.Team;
public class TeamModel extends EntityModel<Team> {
private static final long serialVersionUID = 1L;
public TeamModel(Team team) {
super(team);
}
}

View File

@ -15,7 +15,7 @@
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/ico/apple-touch-icon-144-precomposed.png">
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
</head>
<body wicket:id="body">
<body>
<wicket:container wicket:id="globalResourceBinder"></wicket:container>
<!--[if lt IE 7]>

View File

@ -1,26 +1,16 @@
package com.pmease.gitop.web.page;
import java.util.Iterator;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.MarkupResourceStream;
import org.apache.wicket.markup.html.TransparentWebMarkupContainer;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.flow.RedirectToUrlException;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.google.common.base.Strings;
import com.pmease.commons.util.StringUtils;
import com.pmease.gitop.core.Gitop;
import com.pmease.gitop.web.assets.BaseResourcesBehavior;
@ -33,7 +23,7 @@ import com.pmease.gitop.web.page.init.ServerInitPage;
@SuppressWarnings("serial")
public abstract class BasePage extends WebPage {
private WebMarkupContainer body;
// private WebMarkupContainer body;
private boolean shouldInitialize = true;
@ -54,17 +44,17 @@ public abstract class BasePage extends WebPage {
}
private void commonInit() {
body = new TransparentWebMarkupContainer("body");
add(body);
body.add(AttributeAppender.append("class",
new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
String css = getPageCssClass();
return Strings.isNullOrEmpty(css) ? "" : css;
}
}));
// body = new TransparentWebMarkupContainer("body");
// add(body);
// body.add(AttributeAppender.append("class",
// new AbstractReadOnlyModel<String>() {
//
// @Override
// public String getObject() {
// String css = getPageCssClass();
// return Strings.isNullOrEmpty(css) ? "" : css;
// }
// }));
modal = new Modal("modal");
add(modal);
@ -77,58 +67,58 @@ public abstract class BasePage extends WebPage {
shouldInitialize = true;
}
@Override
public IMarkupFragment getMarkup(Component child) {
if (child != null) {
IMarkupFragment markup = super.getMarkup(child);
if (markup != null)
return markup;
else if (body != null)
return body.getMarkup(child);
else
return null;
} else {
final IMarkupFragment markup = super.getMarkup(child);
return new IMarkupFragment() {
@Override
public String toString(boolean markupOnly) {
return markup.toString();
}
@Override
public int size() {
return markup.size();
}
@Override
public MarkupResourceStream getMarkupResourceStream() {
return markup.getMarkupResourceStream();
}
@Override
public MarkupElement get(int index) {
return markup.get(index);
}
@Override
public IMarkupFragment find(String id) {
IMarkupFragment found = markup.find(id);
if (found != null)
return found;
else if (body != null)
return body.getMarkup().find(id);
else
return null;
}
@Override
public Iterator<MarkupElement> iterator() {
return markup.iterator();
}
};
}
}
// @Override
// public IMarkupFragment getMarkup(Component child) {
// if (child != null) {
// IMarkupFragment markup = super.getMarkup(child);
// if (markup != null)
// return markup;
// else if (body != null)
// return body.getMarkup(child);
// else
// return null;
// } else {
// final IMarkupFragment markup = super.getMarkup(child);
// return new IMarkupFragment() {
//
// @Override
// public String toString(boolean markupOnly) {
// return markup.toString();
// }
//
// @Override
// public int size() {
// return markup.size();
// }
//
// @Override
// public MarkupResourceStream getMarkupResourceStream() {
// return markup.getMarkupResourceStream();
// }
//
// @Override
// public MarkupElement get(int index) {
// return markup.get(index);
// }
//
// @Override
// public IMarkupFragment find(String id) {
// IMarkupFragment found = markup.find(id);
// if (found != null)
// return found;
// else if (body != null)
// return body.getMarkup().find(id);
// else
// return null;
// }
//
// @Override
// public Iterator<MarkupElement> iterator() {
// return markup.iterator();
// }
// };
// }
// }
public final void redirectWithInterception(final Class<? extends Page> clazz) {
shouldInitialize = true;
@ -183,7 +173,7 @@ public abstract class BasePage extends WebPage {
protected void onPageInitialize() {
if (!isPermitted()) {
throw new AccessDeniedException();
throw new AccessDeniedException("Access denied");
}
add(new Label("title", getPageTitle()));

View File

@ -6,11 +6,19 @@
</div>
<div class="body">
<h4>Anonymous Users</h4>
<p><a>Anonymous users can browse and pull any repositories under this account </a></p>
<p><a class="checkable-link" wicket:id="anonymouslink"><i class="icon-checkbox"></i> Anonymous users can browse and pull any repositories under this account </a></p>
<hr/>
<h4>Logged-in Users</h4>
<p class="text-muted">Logged-in users can access any repositories under this account with permission:</p>
<p>Logged-in users can access any repositories under this account with permission:</p>
<div class="btn-group btn-group-justified btn-radio-group" wicket:id="loggedInPermissions">
<wicket:container wicket:id="permissions">
<a wicket:id="permission" class="btn">
<span class="icon-ok"></span>
<span wicket:id="name"></span>
</a>
</wicket:container>
</div>
<hr/>
<h4>Teams</h4>

View File

@ -1,5 +1,19 @@
package com.pmease.gitop.web.page.account.setting.permission;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import com.google.common.collect.ImmutableList;
import com.pmease.gitop.core.Gitop;
import com.pmease.gitop.core.manager.UserManager;
import com.pmease.gitop.core.model.User;
import com.pmease.gitop.core.permission.operation.GeneralOperation;
import com.pmease.gitop.web.page.account.setting.AccountSettingPage;
@SuppressWarnings("serial")
@ -15,4 +29,79 @@ public class AccountPermissionPage extends AccountSettingPage {
return Category.PERMISSION;
}
@Override
protected void onPageInitialize() {
super.onPageInitialize();
AjaxLink<?> link = new AjaxLink<Void>("anonymouslink") {
@Override
public void onClick(AjaxRequestTarget target) {
User account = getAccount();
account.setPubliclyAccessible(!account.isPubliclyAccessible());
Gitop.getInstance(UserManager.class).save(account);
target.add(this);
}
};
add(link);
link.setOutputMarkupId(true);
link.add(AttributeAppender.append("class", new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
return getAccount().isPubliclyAccessible() ?
"checked" : "";
}
}));
WebMarkupContainer loggedInPermissions = new WebMarkupContainer("loggedInPermissions");
add(loggedInPermissions);
loggedInPermissions.setOutputMarkupId(true);
loggedInPermissions.add(new ListView<GeneralOperation>("permissions",
ImmutableList.<GeneralOperation>of(GeneralOperation.NO_ACCESS, GeneralOperation.READ, GeneralOperation.WRITE)) {
@Override
protected void populateItem(ListItem<GeneralOperation> item) {
AjaxLink<?> link = new PermissionLink("permission", item.getModelObject());
item.add(link);
link.add(new Label("name", item.getModelObject().toString()));
}
});
}
class PermissionLink extends AjaxLink<Void> {
final GeneralOperation permssion;
PermissionLink(String id, final GeneralOperation permission) {
super(id);
this.permssion = permission;
add(AttributeAppender.append("class", new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
return getAccount().getDefaultAuthorizedOperation() == permission ?
"btn-default active" : "btn-default";
}
}));
}
@Override
public void onClick(AjaxRequestTarget target) {
User account = getAccount();
if (account.getDefaultAuthorizedOperation() == permssion) {
return;
}
account.setDefaultAuthorizedOperation(permssion);
Gitop.getInstance(UserManager.class).save(account);
target.add(AccountPermissionPage.this.get("loggedInPermissions"));
}
}
}

View File

@ -0,0 +1,15 @@
package com.pmease.gitop.web.page.account.setting.permission;
import com.pmease.gitop.core.model.Team;
@SuppressWarnings("serial")
public class AddTeamPage extends EditTeamPage {
public AddTeamPage() {
}
@Override
protected Team getTeam() {
return new Team();
}
}

View File

@ -0,0 +1,5 @@
<html xmlns:wicket>
<wicket:extend>
<div wicket:id="editor"></div>
</wicket:extend>
</html>

View File

@ -0,0 +1,49 @@
package com.pmease.gitop.web.page.account.setting.permission;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.pmease.gitop.core.Gitop;
import com.pmease.gitop.core.manager.TeamManager;
import com.pmease.gitop.core.model.Team;
import com.pmease.gitop.web.model.TeamModel;
import com.pmease.gitop.web.page.account.setting.AccountSettingPage;
@SuppressWarnings("serial")
public class EditTeamPage extends AccountSettingPage {
private final Long teamId;
protected EditTeamPage() {
teamId = null;
}
public EditTeamPage(PageParameters params) {
Long teamId = params.get("teamId").toLongObject();
this.teamId = teamId;
}
@Override
protected Category getSettingCategory() {
return Category.PERMISSION;
}
@Override
protected String getPageTitle() {
return "Edit Team";
}
@Override
protected void onPageInitialize() {
super.onPageInitialize();
add(new TeamEditor("editor", new TeamModel(getTeam())));
}
protected Team getTeam() {
if (teamId == null) {
throw new IllegalStateException("Team id cannot be null when editing team");
} else {
return Gitop.getInstance(TeamManager.class).get(teamId);
}
}
}

View File

@ -0,0 +1,52 @@
<html xmlns:wicket>
<wicket:panel>
<form class="form-inline" wicket:id="infoForm">
<div wicket:id="feedback" class="form-feedback"></div>
<input class="form-control focusable" type="text" placeholder="Team Name" wicket:id="name" />
<div class="btn-group btn-radio-group btn-group-permissions" wicket:id="permissionContainer">
<wicket:container wicket:id="permissions">
<a wicket:id="link" class="btn btn-default">
<span class="icon-ok"></span>
<span wicket:id="name"></span>
</a>
</wicket:container>
</div>
<button type="submit" class="btn btn-primary" wicket:id="submit">Save Team</button>
</form>
<hr />
<h4>Members</h4>
<form class="form-inline" wicket:id="memberForm">
<div class="form-feedback" wicket:id="feedback"></div>
<input wicket:id="userchoice" class="user-choice" type="hidden" style="width: 300px"></input>
<button type="submit" class="btn btn-default" wicket:id="submit">Add</button>
</form>
<hr/>
<div>
<h4 class="text-muted"><b wicket:id="total"></b> members total</h4>
<div class="member-list clearfix">
<div class="members first" wicket:id="oddlist">
</div>
<div class="members last" wicket:id="evenlist">
</div>
</div>
</div>
<wicket:fragment wicket:id="membersview">
<ul class="list-group list-view list-group-hoverable">
<li class="list-group-item" wicket:id="member">
<span wicket:id="avatar"></span>
<a class="name" wicket:id="link">
<span wicket:id="name"></span>
(<em wicket:id="fullname"></em>)
</a>
<div class="item-actions">
<a class="img-link" wicket:id="remove"><i class="icon-remove-sign"></i></a>
</div>
</li>
</ul>
</wicket:fragment>
</wicket:panel>
</html>

View File

@ -0,0 +1,122 @@
package com.pmease.gitop.web.page.account.setting.permission;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import com.google.common.collect.ImmutableList;
import com.pmease.gitop.core.Gitop;
import com.pmease.gitop.core.manager.TeamManager;
import com.pmease.gitop.core.model.Team;
import com.pmease.gitop.core.permission.operation.GeneralOperation;
import com.pmease.gitop.web.common.component.messenger.Messenger;
import com.pmease.gitop.web.common.form.FeedbackPanel;
@SuppressWarnings("serial")
public class TeamEditor extends Panel {
public TeamEditor(String id, IModel<Team> model) {
super(id, model);
}
private Team getTeam() {
return (Team) getDefaultModelObject();
}
@Override
protected void onInitialize() {
super.onInitialize();
add(createInfoForm());
}
private Form<?> createInfoForm() {
@SuppressWarnings("unchecked")
IModel<Team> teamModel = (IModel<Team>) getDefaultModel();
Form<Team> infoForm = new Form<Team>("infoForm", teamModel);
add(infoForm);
infoForm.add(new FeedbackPanel("feedback"));
infoForm.add(new TextField<String>("name", new PropertyModel<String>(teamModel, "name")).setRequired(true));
WebMarkupContainer permissionContainer =
new WebMarkupContainer("permissionContainer");
infoForm.add(permissionContainer);
permissionContainer.add(
new ListView<GeneralOperation>("permissions",
ImmutableList.<GeneralOperation>of(
GeneralOperation.READ,
GeneralOperation.WRITE,
GeneralOperation.ADMIN)) {
@Override
protected void populateItem(ListItem<GeneralOperation> item) {
GeneralOperation permission = item.getModelObject();
AjaxLink<?> link = new PermissionLink("link", permission);
link.add(new Label("name", permission.toString()));
item.add(link);
}
});
infoForm.add(new AjaxButton("submit", infoForm) {
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(form);
}
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
Team team = getTeam();
boolean isNew = team.isNew();
Gitop.getInstance(TeamManager.class).save(team);
Messenger
.success(String.format("Team has been %s successfully.",
isNew ? "created" : "updated"))
.run(target);
}
});
return infoForm;
}
class PermissionLink extends AjaxLink<Void> {
final GeneralOperation permssion;
PermissionLink(String id, final GeneralOperation permission) {
super(id);
this.permssion = permission;
add(AttributeAppender.append("class", new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
return getTeam().getAuthorizedOperation() == permission ?
"active" : "";
}
}));
}
@Override
public void onClick(AjaxRequestTarget target) {
Team team = getTeam();
if (team.getAuthorizedOperation() == permssion) {
return;
}
team.setAuthorizedOperation(permssion);
Gitop.getInstance(TeamManager.class).save(team);
target.add(TeamEditor.this.get("infoForm"));
}
}
}

View File

@ -28,7 +28,7 @@
<input type="file" wicket:id="fileInput" />
</p>
<p class="text-muted">
Supported image types are png, gif, jpg and jpeg. The image size should be less than 128K bytes.
Supported image types are png, gif, jpg and jpeg. The image size should be less than 2M bytes.
</p>
<hr/>
<div class="btn-group btn-group-justified">

View File

@ -13,6 +13,7 @@ import org.apache.wicket.markup.html.form.upload.FileUploadField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.util.lang.Bytes;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
@ -106,8 +107,13 @@ public class AccountProfilePage extends AccountSettingPage {
protected void onInitialize() {
super.onInitialize();
// limit avatar size to 2M bytes
setMaxSize(Bytes.megabytes(2));
setMultiPart(true);
final FileUploadField uploadField = new FileUploadField("fileInput");
uploadField.setRequired(false);
add(uploadField);
add(new FeedbackPanel("feedback"));

View File

@ -5,6 +5,7 @@ import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
@ -60,7 +61,14 @@ public class HomePage extends AbstractLayoutPage {
form.add(new AjaxConfirmButton("submit", form,
Model.of("Are you sure you want to submit the form?"),
Model.of(VexIcon.ERROR),
new AbstractReadOnlyModel<VexIcon>() {
@Override
public VexIcon getObject() {
return vexIcon;
}
},
Model.of("Yes"),
Model.of("No"),
null) {