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 18KB

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