diff --git a/appjar/src/main/java/com/google/gerrit/Gerrit.gwt.xml b/appjar/src/main/java/com/google/gerrit/Gerrit.gwt.xml
index 77e4058afa..71b0040f58 100644
--- a/appjar/src/main/java/com/google/gerrit/Gerrit.gwt.xml
+++ b/appjar/src/main/java/com/google/gerrit/Gerrit.gwt.xml
@@ -30,6 +30,8 @@
class='com.google.gerrit.server.GroupAdminServiceSrv'/>
+
> callback);
+
+ @SignInRequired
+ void projectDetail(Project.Id projectId, AsyncCallback callback);
+
+ @SignInRequired
+ void changeProjectDescription(Project.Id projectId, String description,
+ AsyncCallback callback);
+
+ @SignInRequired
+ void changeProjectOwner(Project.Id projectId, String newOwnerName,
+ AsyncCallback callback);
+}
diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java
new file mode 100644
index 0000000000..cbf95dcbde
--- /dev/null
+++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java
@@ -0,0 +1,33 @@
+// Copyright 2008 Google Inc.
+//
+// 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.reviewdb.AccountGroup;
+import com.google.gerrit.client.reviewdb.Project;
+import com.google.gerrit.client.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
+
+public class ProjectDetail {
+ protected Project project;
+ protected AccountGroup ownerGroup;
+
+ public ProjectDetail() {
+ }
+
+ public void load(final ReviewDb db, final Project g) throws OrmException {
+ project = g;
+ ownerGroup = db.accountGroups().get(project.getOwnerGroupId());
+ }
+}
diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
new file mode 100644
index 0000000000..2a21ebadba
--- /dev/null
+++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -0,0 +1,122 @@
+// Copyright 2008 Google Inc.
+//
+// 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.Link;
+import com.google.gerrit.client.reviewdb.Project;
+import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.ui.AccountScreen;
+import com.google.gerrit.client.ui.FancyFlexTable;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.ui.Hyperlink;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.SourcesTableEvents;
+import com.google.gwt.user.client.ui.TableListener;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
+
+import java.util.List;
+
+public class ProjectListScreen extends AccountScreen {
+ private ProjectTable projects;
+
+ public ProjectListScreen() {
+ super(Util.C.projectListTitle());
+ }
+
+ @Override
+ public Object getScreenCacheToken() {
+ return getClass();
+ }
+
+ @Override
+ public void onLoad() {
+ if (projects == null) {
+ initUI();
+ }
+
+ Util.PROJECT_SVC.ownedProjects(new GerritCallback>() {
+ public void onSuccess(final List result) {
+ if (isAttached()) {
+ projects.display(result);
+ projects.finishDisplay(true);
+ }
+ }
+ });
+ }
+
+ private void initUI() {
+ projects = new ProjectTable();
+ projects.setSavePointerId(Link.ADMIN_PROJECTS);
+ add(projects);
+
+ final VerticalPanel fp = new VerticalPanel();
+ fp.setStyleName("gerrit-AddSshKeyPanel");
+ final Label hdr = new Label(Util.C.headingCreateGroup());
+ hdr.setStyleName("gerrit-SmallHeading");
+ fp.add(hdr);
+ }
+
+ private class ProjectTable extends FancyFlexTable {
+ ProjectTable() {
+ table.setText(0, 1, Util.C.columnGroupName());
+ table.setText(0, 2, Util.C.columnGroupDescription());
+ table.addTableListener(new TableListener() {
+ public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
+ if (cell != 1 && getRowItem(row) != null) {
+ movePointerTo(row);
+ }
+ }
+ });
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(0, 1, S_DATA_HEADER);
+ fmt.addStyleName(0, 2, S_DATA_HEADER);
+ }
+
+ @Override
+ protected Object getRowItemKey(final Project item) {
+ return item.getId();
+ }
+
+ @Override
+ protected void onOpenItem(final Project item) {
+ History.newItem(Link.toProjectAdmin(item.getId()));
+ }
+
+ void display(final List result) {
+ while (1 < table.getRowCount())
+ table.removeRow(table.getRowCount() - 1);
+
+ for (final Project k : result) {
+ final int row = table.getRowCount();
+ table.insertRow(row);
+ populate(row, k);
+ }
+ }
+
+ void populate(final int row, final Project k) {
+ table.setWidget(row, 1, new Hyperlink(k.getName(), Link.toProjectAdmin(k
+ .getId())));
+ table.setText(row, 2, k.getDescription());
+
+ final FlexCellFormatter fmt = table.getFlexCellFormatter();
+ fmt.addStyleName(row, 1, S_DATA_CELL);
+ fmt.addStyleName(row, 2, S_DATA_CELL);
+
+ setRowItem(row, k);
+ }
+ }
+}
diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/Util.java b/appjar/src/main/java/com/google/gerrit/client/admin/Util.java
index b03e52885c..5de4377c80 100644
--- a/appjar/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/appjar/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -21,9 +21,13 @@ public class Util {
public static final AdminConstants C = GWT.create(AdminConstants.class);
public static final AdminMessages M = GWT.create(AdminMessages.class);
public static final GroupAdminService GROUP_SVC;
+ public static final ProjectAdminService PROJECT_SVC;
static {
GROUP_SVC = GWT.create(GroupAdminService.class);
JsonUtil.bind(GROUP_SVC, "rpc/GroupAdminService");
+
+ PROJECT_SVC = GWT.create(ProjectAdminService.class);
+ JsonUtil.bind(PROJECT_SVC, "rpc/ProjectAdminService");
}
}
diff --git a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectAccess.java b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectAccess.java
index e4194982a5..566c178fe0 100644
--- a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectAccess.java
+++ b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectAccess.java
@@ -31,6 +31,9 @@ public interface ProjectAccess extends Access {
@Query("ORDER BY name")
ResultSet all() throws OrmException;
+ @Query("WHERE ownerGroupId = ?")
+ ResultSet ownedByGroup(AccountGroup.Id groupId) throws OrmException;
+
@Query("WHERE name.name >= ? AND name.name <= ? ORDER BY name LIMIT ?")
ResultSet suggestByName(String nameA, String nameB, int limit)
throws OrmException;
diff --git a/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java
new file mode 100644
index 0000000000..03bfbf1a5b
--- /dev/null
+++ b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java
@@ -0,0 +1,151 @@
+// Copyright 2008 Google Inc.
+//
+// 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.server;
+
+import com.google.gerrit.client.admin.ProjectAdminService;
+import com.google.gerrit.client.admin.ProjectDetail;
+import com.google.gerrit.client.reviewdb.AccountGroup;
+import com.google.gerrit.client.reviewdb.AccountGroupMember;
+import com.google.gerrit.client.reviewdb.Project;
+import com.google.gerrit.client.reviewdb.ReviewDb;
+import com.google.gerrit.client.rpc.BaseServiceImplementation;
+import com.google.gerrit.client.rpc.NoSuchEntityException;
+import com.google.gerrit.client.rpc.RpcUtil;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwtjsonrpc.client.VoidResult;
+import com.google.gwtorm.client.OrmException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ProjectAdminServiceImpl extends BaseServiceImplementation
+ implements ProjectAdminService {
+ private final AccountGroup.Id adminId;
+
+ public ProjectAdminServiceImpl(final GerritServer server) {
+ super(server.getDatabase());
+ adminId = server.getAdminGroupId();
+ }
+
+ public void ownedProjects(final AsyncCallback> callback) {
+ run(callback, new Action>() {
+ public List run(ReviewDb db) throws OrmException {
+ final List result;
+ if (amAdmin(db)) {
+ result = db.projects().all().toList();
+ } else {
+ result = myOwnedProjects(db);
+ Collections.sort(result, new Comparator() {
+ public int compare(final Project a, final Project b) {
+ return a.getName().compareTo(b.getName());
+ }
+ });
+ }
+ return result;
+ }
+ });
+ }
+
+ public void projectDetail(final Project.Id projectId,
+ final AsyncCallback callback) {
+ run(callback, new Action() {
+ public ProjectDetail run(ReviewDb db) throws OrmException, Failure {
+ assertAmProjectOwner(db, projectId);
+ final Project proj = db.projects().get(projectId);
+ if (proj == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ final ProjectDetail d = new ProjectDetail();
+ d.load(db, proj);
+ return d;
+ }
+ });
+ }
+
+ public void changeProjectDescription(final Project.Id projectId,
+ final String description, final AsyncCallback callback) {
+ run(callback, new Action() {
+ public VoidResult run(final ReviewDb db) throws OrmException, Failure {
+ assertAmProjectOwner(db, projectId);
+ final Project proj = db.projects().get(projectId);
+ if (proj == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ proj.setDescription(description);
+ db.projects().update(Collections.singleton(proj));
+ return VoidResult.INSTANCE;
+ }
+ });
+ }
+
+ public void changeProjectOwner(final Project.Id projectId,
+ final String newOwnerName, final AsyncCallback callback) {
+ run(callback, new Action() {
+ public VoidResult run(final ReviewDb db) throws OrmException, Failure {
+ assertAmProjectOwner(db, projectId);
+ final Project project = db.projects().get(projectId);
+ if (project == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ final AccountGroup owner =
+ db.accountGroups().get(new AccountGroup.NameKey(newOwnerName));
+ if (owner == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+
+ project.setOwnerGroupId(owner.getId());
+ db.projects().update(Collections.singleton(project));
+ return VoidResult.INSTANCE;
+ }
+ });
+ }
+
+ private static boolean amInGroup(final ReviewDb db,
+ final AccountGroup.Id groupId) throws OrmException {
+ return db.accountGroupMembers().get(
+ new AccountGroupMember.Key(RpcUtil.getAccountId(), groupId)) != null;
+ }
+
+ private boolean amAdmin(final ReviewDb db) throws OrmException {
+ return adminId != null && amInGroup(db, adminId);
+ }
+
+ private void assertAmProjectOwner(final ReviewDb db,
+ final Project.Id projectId) throws OrmException, Failure {
+ final Project project = db.projects().get(projectId);
+ if (project == null) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ if (!amInGroup(db, project.getOwnerGroupId()) && !amAdmin(db)) {
+ throw new Failure(new NoSuchEntityException());
+ }
+ }
+
+ private static List myOwnedProjects(final ReviewDb db)
+ throws OrmException {
+ final List own = new ArrayList();
+ for (final AccountGroupMember m : db.accountGroupMembers().byAccount(
+ RpcUtil.getAccountId()).toList()) {
+ for (final Project g : db.projects().ownedByGroup(m.getAccountGroupId())) {
+ own.add(g);
+ }
+ }
+ return own;
+ }
+}
diff --git a/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceSrv.java b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceSrv.java
new file mode 100644
index 0000000000..dc004651e3
--- /dev/null
+++ b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceSrv.java
@@ -0,0 +1,24 @@
+// Copyright 2008 Google Inc.
+//
+// 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.server;
+
+
+/** Publishes {@link ProjectAdminServiceImpl} over JSON. */
+public class ProjectAdminServiceSrv extends GerritJsonServlet {
+ @Override
+ protected Object createServiceHandle() throws Exception {
+ return new ProjectAdminServiceImpl(GerritServer.getInstance());
+ }
+}
diff --git a/appwar/src/main/webapp/WEB-INF/web.xml b/appwar/src/main/webapp/WEB-INF/web.xml
index 85b4828aa1..e9eea299b7 100644
--- a/appwar/src/main/webapp/WEB-INF/web.xml
+++ b/appwar/src/main/webapp/WEB-INF/web.xml
@@ -122,6 +122,16 @@
PatchDetailService
/rpc/PatchDetailService
+
+
+ ProjectAdminService
+ com.google.gerrit.server.ProjectAdminServiceSrv
+ 1
+
+
+ ProjectAdminService
+ /rpc/ProjectAdminService
+