James Moger
2012-10-22 eba89539a29deba954035056437279088c3e047b
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
a4d249 16 package com.gitblit.wicket.panels;
JM 17
8a2e9c 18 import java.text.MessageFormat;
a4d249 19 import java.util.ArrayList;
JM 20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
eb96ea 28 import org.apache.wicket.Component;
a4d249 29 import org.apache.wicket.PageParameters;
JM 30 import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
31 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
32 import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
33 import org.apache.wicket.markup.html.basic.Label;
34 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
8c9a20 35 import org.apache.wicket.markup.html.link.ExternalLink;
8a2e9c 36 import org.apache.wicket.markup.html.link.Link;
a4d249 37 import org.apache.wicket.markup.html.panel.Fragment;
JM 38 import org.apache.wicket.markup.repeater.Item;
39 import org.apache.wicket.markup.repeater.data.DataView;
40 import org.apache.wicket.markup.repeater.data.IDataProvider;
41 import org.apache.wicket.markup.repeater.data.ListDataProvider;
42 import org.apache.wicket.model.IModel;
43 import org.apache.wicket.model.Model;
44
45 import com.gitblit.Constants.AccessRestrictionType;
46 import com.gitblit.GitBlit;
47 import com.gitblit.Keys;
8c9a20 48 import com.gitblit.SyndicationServlet;
13a3f5 49 import com.gitblit.models.ProjectModel;
1f9dae 50 import com.gitblit.models.RepositoryModel;
JM 51 import com.gitblit.models.UserModel;
a4d249 52 import com.gitblit.utils.StringUtils;
JM 53 import com.gitblit.wicket.GitBlitWebSession;
54 import com.gitblit.wicket.WicketUtils;
82df52 55 import com.gitblit.wicket.pages.BasePage;
a4d249 56 import com.gitblit.wicket.pages.EditRepositoryPage;
82df52 57 import com.gitblit.wicket.pages.EmptyRepositoryPage;
13a3f5 58 import com.gitblit.wicket.pages.ProjectPage;
fee060 59 import com.gitblit.wicket.pages.RepositoriesPage;
a4d249 60 import com.gitblit.wicket.pages.SummaryPage;
1e1b85 61 import com.gitblit.wicket.pages.UserPage;
a4d249 62
JM 63 public class RepositoriesPanel extends BasePanel {
64
65     private static final long serialVersionUID = 1L;
8a2e9c 66
6662e3 67     public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement,
cb57ec 68             List<RepositoryModel> models, boolean enableLinks,
2a7306 69             final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
a4d249 70         super(wicketId);
8a2e9c 71
cb57ec 72         final boolean linksActive = enableLinks;
831469 73         final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
JM 74
a4d249 75         final UserModel user = GitBlitWebSession.get().getUser();
831469 76
8a2e9c 77         final IDataProvider<RepositoryModel> dp;
JM 78
6662e3 79         Fragment managementLinks;
JM 80         if (showAdmin) {
81             // user is admin
82             managementLinks = new Fragment("managementPanel", "adminLinks", this);
83             managementLinks.add(new Link<Void>("clearCache") {
fee060 84
6662e3 85                 private static final long serialVersionUID = 1L;
fee060 86
6662e3 87                 @Override
JM 88                 public void onClick() {
89                     GitBlit.self().resetRepositoryListCache();
90                     setResponsePage(RepositoriesPage.class);
91                 }
92             }.setVisible(GitBlit.getBoolean(Keys.git.cacheRepositoryList, true)));
93             managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
94             add(managementLinks);
7f7051 95         } else if (showManagement && user != null && user.canCreate()) {
6662e3 96             // user can create personal repositories
JM 97             managementLinks = new Fragment("managementPanel", "personalLinks", this);
98             managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
99             add(managementLinks);
100         } else {
101             // user has no management permissions
102             add (new Label("managementPanel").setVisible(false));
103         }
8a2e9c 104
2a7306 105         if (GitBlit.getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
5c2841 106             List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>();
a4d249 107             Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
JM 108             for (RepositoryModel model : models) {
109                 String rootPath = StringUtils.getRootPath(model.name);
88598b 110                 if (StringUtils.isEmpty(rootPath)) {
5c2841 111                     // root repository
88598b 112                     rootRepositories.add(model);
5c2841 113                 } else {
JM 114                     // non-root, grouped repository
115                     if (!groups.containsKey(rootPath)) {
116                         groups.put(rootPath, new ArrayList<RepositoryModel>());
117                     }
118                     groups.get(rootPath).add(model);
a4d249 119                 }
JM 120             }
121             List<String> roots = new ArrayList<String>(groups.keySet());
122             Collections.sort(roots);
88598b 123
5c2841 124             if (rootRepositories.size() > 0) {
JM 125                 // inject the root repositories at the top of the page
126                 String rootPath = GitBlit.getString(Keys.web.repositoryRootGroupName, " ");
127                 roots.add(0, rootPath);
128                 groups.put(rootPath, rootRepositories);
129             }
13a3f5 130                         
JM 131             Map<String, ProjectModel> projects = new HashMap<String, ProjectModel>();
1e1b85 132             for (ProjectModel project : GitBlit.self().getProjectModels(user, true)) {
13a3f5 133                 projects.put(project.name, project);
JM 134             }
a4d249 135             List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
JM 136             for (String root : roots) {
137                 List<RepositoryModel> subModels = groups.get(root);
13a3f5 138                 GroupRepositoryModel group = new GroupRepositoryModel(root, subModels.size());
JM 139                 if (projects.containsKey(root)) {
140                     group.title = projects.get(root).title;
141                     group.description = projects.get(root).description;
142                 }
143                 groupedModels.add(group);
831469 144                 Collections.sort(subModels);
a4d249 145                 groupedModels.addAll(subModels);
JM 146             }
8a2e9c 147             dp = new RepositoriesProvider(groupedModels);
a4d249 148         } else {
8a2e9c 149             dp = new SortableRepositoriesProvider(models);
a4d249 150         }
72633d 151
2179fb 152         final String baseUrl = WicketUtils.getGitblitURL(getRequest());
309c55 153         final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
1e1b85 154         
a4d249 155         DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
JM 156             private static final long serialVersionUID = 1L;
2a7306 157             int counter;
94750e 158             String currGroupName;
a4d249 159
8a2e9c 160             @Override
JM 161             protected void onBeforeRender() {
162                 super.onBeforeRender();
163                 counter = 0;
164             }
165
a4d249 166             public void populateItem(final Item<RepositoryModel> item) {
JM 167                 final RepositoryModel entry = item.getModelObject();
168                 if (entry instanceof GroupRepositoryModel) {
94750e 169                     currGroupName = entry.name;
a4d249 170                     Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
JM 171                     item.add(row);
1e1b85 172                     
JM 173                     String name = entry.toString();
174                     if (name.charAt(0) == '~') {
175                         // user page
176                         String username = name.substring(1);
177                         UserModel user = GitBlit.self().getUserModel(username);
178                         row.add(new LinkPanel("groupName", null, user == null ? username : user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(username)));
179                         row.add(new Label("groupDescription", getString("gb.personalRepositories")));
180                     } else {
181                         // project page
7efca2 182                         row.add(new LinkPanel("groupName", null, name, ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));
1e1b85 183                         row.add(new Label("groupDescription", entry.description == null ? "":entry.description));
JM 184                     }
a4d249 185                     WicketUtils.setCssClass(item, "group");
94750e 186                     // reset counter so that first row is light background
JM 187                     counter = 0;
a4d249 188                     return;
JM 189                 }
190                 Fragment row = new Fragment("rowContent", "repositoryRow", this);
191                 item.add(row);
309c55 192
94750e 193                 // try to strip group name for less cluttered list
6c6fbf 194                 String repoName = entry.toString();
94750e 195                 if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
JM 196                     repoName = repoName.substring(currGroupName.length() + 1);
197                 }
1c2215 198                                 
309c55 199                 // repository swatch
1c2215 200                 Component swatch;
JM 201                 if (entry.isBare){
202                     swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
203                 } else {
204                     swatch = new Label("repositorySwatch", "!");
ccab3a 205                     WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
1c2215 206                 }
4ce036 207                 WicketUtils.setCssBackground(swatch, entry.toString());
309c55 208                 row.add(swatch);
JM 209                 swatch.setVisible(showSwatch);
210
82df52 211                 if (linksActive) {
JM 212                     Class<? extends BasePage> linkPage;
213                     if (entry.hasCommits) {
214                         // repository has content
215                         linkPage = SummaryPage.class;
216                     } else {
217                         // new/empty repository OR proposed repository
218                         linkPage = EmptyRepositoryPage.class;
219                     }
220
a4d249 221                     PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
82df52 222                     row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));
2a7306 223                     row.add(new LinkPanel("repositoryDescription", "list", entry.description,
82df52 224                             linkPage, pp));
831469 225                 } else {
82df52 226                     // no links like on a federation page
94750e 227                     row.add(new Label("repositoryName", repoName));
831469 228                     row.add(new Label("repositoryDescription", entry.description));
JM 229                 }
230                 if (entry.hasCommits) {
231                     // Existing repository
72633d 232                     row.add(new Label("repositorySize", entry.size).setVisible(showSize));
a4d249 233                 } else {
JM 234                     // New repository
017749 235                     row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>")
88598b 236                             .setEscapeModelStrings(false));
a4d249 237                 }
JM 238
cf265a 239                 if (entry.isFork()) {
JM 240                     row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
241                             getString("gb.isFork")));
242                 } else {
243                     row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
244                 }
245
a4d249 246                 if (entry.useTickets) {
2a7306 247                     row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png",
JM 248                             getString("gb.tickets")));
a4d249 249                 } else {
JM 250                     row.add(WicketUtils.newBlankImage("ticketsIcon"));
251                 }
252
253                 if (entry.useDocs) {
2a7306 254                     row.add(WicketUtils
JM 255                             .newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
a4d249 256                 } else {
JM 257                     row.add(WicketUtils.newBlankImage("docsIcon"));
258                 }
259
260                 if (entry.isFrozen) {
2a7306 261                     row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
JM 262                             getString("gb.isFrozen")));
a4d249 263                 } else {
JM 264                     row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
831469 265                 }
JM 266
267                 if (entry.isFederated) {
268                     row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
269                             getString("gb.isFederated")));
270                 } else {
271                     row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
a4d249 272                 }
JM 273                 switch (entry.accessRestriction) {
274                 case NONE:
275                     row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
276                     break;
277                 case PUSH:
2a7306 278                     row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
JM 279                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 280                     break;
JM 281                 case CLONE:
2a7306 282                     row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
JM 283                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 284                     break;
JM 285                 case VIEW:
2a7306 286                     row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
JM 287                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 288                     break;
JM 289                 default:
290                     row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
291                 }
292
fb6bf3 293                 String owner = entry.owner;
JM 294                 if (!StringUtils.isEmpty(owner)) {
295                     UserModel ownerModel = GitBlit.self().getUserModel(owner);
296                     if (ownerModel != null) {
297                         owner = ownerModel.getDisplayName();
298                     }
299                 }
300                 row.add(new Label("repositoryOwner", owner));
a4d249 301
a2709d 302                 String lastChange;
JM 303                 if (entry.lastChange.getTime() == 0) {
304                     lastChange = "--";
305                 } else {
9adf62 306                     lastChange = getTimeUtils().timeAgo(entry.lastChange);
a2709d 307                 }
a4d249 308                 Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
JM 309                 row.add(lastChangeLabel);
9adf62 310                 WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
a4d249 311
6662e3 312                 boolean showOwner = user != null && entry.isOwner(user.username);
1e1b85 313                 boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username);
JM 314                 if (showAdmin || myPersonalRepository) {
2a7306 315                     Fragment repositoryLinks = new Fragment("repositoryLinks",
JM 316                             "repositoryAdminLinks", this);
317                     repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
318                             EditRepositoryPage.class, WicketUtils
319                                     .newRepositoryParameter(entry.name)));
8a2e9c 320                     Link<Void> deleteLink = new Link<Void>("deleteRepository") {
JM 321
322                         private static final long serialVersionUID = 1L;
323
324                         @Override
325                         public void onClick() {
326                             if (GitBlit.self().deleteRepositoryModel(entry)) {
327                                 if (dp instanceof SortableRepositoriesProvider) {
1f52c8 328                                     info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
8a2e9c 329                                     ((SortableRepositoriesProvider) dp).remove(entry);
JM 330                                 } else {
1f52c8 331                                     setResponsePage(getPage().getClass(), getPage().getPageParameters());
8a2e9c 332                                 }
JM 333                             } else {
017749 334                                 error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
8a2e9c 335                             }
JM 336                         }
337                     };
2a7306 338                     deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
017749 339                             getString("gb.deleteRepository"), entry)));
8a2e9c 340                     repositoryLinks.add(deleteLink);
a4d249 341                     row.add(repositoryLinks);
JM 342                 } else if (showOwner) {
2a7306 343                     Fragment repositoryLinks = new Fragment("repositoryLinks",
JM 344                             "repositoryOwnerLinks", this);
345                     repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
346                             EditRepositoryPage.class, WicketUtils
347                                     .newRepositoryParameter(entry.name)));
a4d249 348                     row.add(repositoryLinks);
JM 349                 } else {
350                     row.add(new Label("repositoryLinks"));
351                 }
2179fb 352                 row.add(new ExternalLink("syndication", SyndicationServlet.asLink(baseUrl,
JM 353                         entry.name, null, 0)).setVisible(linksActive));
a4d249 354                 WicketUtils.setAlternatingBackground(item, counter);
JM 355                 counter++;
356             }
357         };
358         add(dataView);
359
360         if (dp instanceof SortableDataProvider<?>) {
361             // add sortable header
362             SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
363             Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
364             fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
365             fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
366             fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
367             fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
368             add(fragment);
369         } else {
370             // not sortable
371             Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
372             add(fragment);
373         }
374     }
8a2e9c 375
2a7306 376     private static class GroupRepositoryModel extends RepositoryModel {
a4d249 377
JM 378         private static final long serialVersionUID = 1L;
379
2a7306 380         int count;
13a3f5 381         String title;
8a2e9c 382
JM 383         GroupRepositoryModel(String name, int count) {
a4d249 384             super(name, "", "", new Date(0));
8a2e9c 385             this.count = count;
JM 386         }
387
388         @Override
389         public String toString() {
13a3f5 390             return StringUtils.isEmpty(title) ? name  : title + " (" + count + ")";
a4d249 391         }
JM 392     }
8a2e9c 393
a4d249 394     protected enum SortBy {
JM 395         repository, description, owner, date;
396     }
397
2a7306 398     protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,
JM 399             final DataView<?> dataView) {
a4d249 400         return new OrderByBorder(wicketId, field.name(), dp) {
JM 401             private static final long serialVersionUID = 1L;
402
403             @Override
404             protected void onSortChanged() {
405                 dataView.setCurrentPage(0);
406             }
407         };
408     }
409
2a7306 410     private static class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
8a2e9c 411
JM 412         private static final long serialVersionUID = 1L;
413
414         public RepositoriesProvider(List<RepositoryModel> list) {
415             super(list);
416         }
417
418         @Override
419         public List<RepositoryModel> getData() {
420             return super.getData();
421         }
422
423         public void remove(RepositoryModel model) {
424             int index = getData().indexOf(model);
425             RepositoryModel groupModel = null;
426             if (index == (getData().size() - 1)) {
427                 // last element
428                 if (index > 0) {
429                     // previous element is group header, then this is last
430                     // repository in group. remove group too.
431                     if (getData().get(index - 1) instanceof GroupRepositoryModel) {
432                         groupModel = getData().get(index - 1);
433                     }
434                 }
435             } else if (index < (getData().size() - 1)) {
436                 // not last element. check next element for group match.
2a7306 437                 if (getData().get(index - 1) instanceof GroupRepositoryModel
JM 438                         && getData().get(index + 1) instanceof GroupRepositoryModel) {
8a2e9c 439                     // repository is sandwiched by group headers so this
JM 440                     // repository is the only element in the group. remove
441                     // group.
442                     groupModel = getData().get(index - 1);
443                 }
444             }
445
446             if (groupModel == null) {
447                 // Find the group and decrement the count
448                 for (int i = index; i >= 0; i--) {
449                     if (getData().get(i) instanceof GroupRepositoryModel) {
450                         ((GroupRepositoryModel) getData().get(i)).count--;
451                         break;
452                     }
453                 }
454             } else {
455                 // Remove the group header
456                 getData().remove(groupModel);
457             }
458
459             getData().remove(model);
460         }
461     }
462
2a7306 463     private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
JM 464
a4d249 465         private static final long serialVersionUID = 1L;
2a7306 466
JM 467         private List<RepositoryModel> list;
a4d249 468
8a2e9c 469         protected SortableRepositoriesProvider(List<RepositoryModel> list) {
a4d249 470             this.list = list;
JM 471             setSort(SortBy.date.name(), false);
472         }
473
8a2e9c 474         public void remove(RepositoryModel model) {
JM 475             list.remove(model);
476         }
477
a4d249 478         @Override
JM 479         public int size() {
2a7306 480             if (list == null) {
a4d249 481                 return 0;
2a7306 482             }
a4d249 483             return list.size();
JM 484         }
485
486         @Override
487         public IModel<RepositoryModel> model(RepositoryModel header) {
488             return new Model<RepositoryModel>(header);
489         }
490
491         @Override
492         public Iterator<RepositoryModel> iterator(int first, int count) {
493             SortParam sp = getSort();
494             String prop = sp.getProperty();
495             final boolean asc = sp.isAscending();
496
497             if (prop == null || prop.equals(SortBy.date.name())) {
498                 Collections.sort(list, new Comparator<RepositoryModel>() {
499                     @Override
500                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 501                         if (asc) {
a4d249 502                             return o1.lastChange.compareTo(o2.lastChange);
2a7306 503                         }
a4d249 504                         return o2.lastChange.compareTo(o1.lastChange);
JM 505                     }
506                 });
507             } else if (prop.equals(SortBy.repository.name())) {
508                 Collections.sort(list, new Comparator<RepositoryModel>() {
509                     @Override
510                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 511                         if (asc) {
a4d249 512                             return o1.name.compareTo(o2.name);
2a7306 513                         }
a4d249 514                         return o2.name.compareTo(o1.name);
JM 515                     }
516                 });
517             } else if (prop.equals(SortBy.owner.name())) {
518                 Collections.sort(list, new Comparator<RepositoryModel>() {
519                     @Override
520                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 521                         if (asc) {
a4d249 522                             return o1.owner.compareTo(o2.owner);
2a7306 523                         }
a4d249 524                         return o2.owner.compareTo(o1.owner);
JM 525                     }
526                 });
527             } else if (prop.equals(SortBy.description.name())) {
528                 Collections.sort(list, new Comparator<RepositoryModel>() {
529                     @Override
530                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 531                         if (asc) {
a4d249 532                             return o1.description.compareTo(o2.description);
2a7306 533                         }
a4d249 534                         return o2.description.compareTo(o1.description);
JM 535                     }
536                 });
537             }
538             return list.subList(first, first + count).iterator();
539         }
540     }
541 }