You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RepositoriesPanel.java 20KB

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