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.

преди 11 години
преди 12 години
преди 11 години
преди 11 години
преди 12 години
преди 11 години
преди 11 години
преди 11 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. @Override
  146. public void populateItem(final Item<RepositoryModel> item) {
  147. final RepositoryModel entry = item.getModelObject();
  148. if (entry instanceof GroupRepositoryModel) {
  149. GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;
  150. currGroupName = entry.name;
  151. Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
  152. item.add(row);
  153. String name = groupRow.name;
  154. if (name.startsWith(ModelUtils.getUserRepoPrefix())) {
  155. // user page
  156. String username = ModelUtils.getUserNameFromRepoPath(name);
  157. UserModel user = GitBlit.self().getUserModel(username);
  158. row.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()) + " (" + groupRow.count + ")", UserPage.class, WicketUtils.newUsernameParameter(username)));
  159. row.add(new Label("groupDescription", getString("gb.personalRepositories")));
  160. } else {
  161. // project page
  162. row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));
  163. row.add(new Label("groupDescription", entry.description == null ? "":entry.description));
  164. }
  165. WicketUtils.setCssClass(item, "group");
  166. // reset counter so that first row is light background
  167. counter = 0;
  168. return;
  169. }
  170. Fragment row = new Fragment("rowContent", "repositoryRow", this);
  171. item.add(row);
  172. // try to strip group name for less cluttered list
  173. String repoName = entry.toString();
  174. if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
  175. repoName = repoName.substring(currGroupName.length() + 1);
  176. }
  177. // repository swatch
  178. Component swatch;
  179. if (entry.isBare){
  180. swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
  181. } else {
  182. swatch = new Label("repositorySwatch", "!");
  183. WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
  184. }
  185. WicketUtils.setCssBackground(swatch, entry.toString());
  186. row.add(swatch);
  187. swatch.setVisible(showSwatch);
  188. if (linksActive) {
  189. Class<? extends BasePage> linkPage;
  190. if (entry.hasCommits) {
  191. // repository has content
  192. linkPage = SummaryPage.class;
  193. } else {
  194. // new/empty repository OR proposed repository
  195. linkPage = EmptyRepositoryPage.class;
  196. }
  197. PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
  198. row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));
  199. row.add(new LinkPanel("repositoryDescription", "list", entry.description,
  200. linkPage, pp));
  201. } else {
  202. // no links like on a federation page
  203. row.add(new Label("repositoryName", repoName));
  204. row.add(new Label("repositoryDescription", entry.description));
  205. }
  206. if (entry.hasCommits) {
  207. // Existing repository
  208. row.add(new Label("repositorySize", entry.size).setVisible(showSize));
  209. } else {
  210. // New repository
  211. row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>")
  212. .setEscapeModelStrings(false));
  213. }
  214. if (entry.isSparkleshared()) {
  215. row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",
  216. getString("gb.isSparkleshared")));
  217. } else {
  218. row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
  219. }
  220. if (entry.isMirror) {
  221. row.add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png",
  222. getString("gb.isMirror")));
  223. } else {
  224. row.add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
  225. }
  226. if (entry.isFork()) {
  227. row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
  228. getString("gb.isFork")));
  229. } else {
  230. row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
  231. }
  232. if (entry.isFrozen) {
  233. row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
  234. getString("gb.isFrozen")));
  235. } else {
  236. row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
  237. }
  238. if (entry.isFederated) {
  239. row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
  240. getString("gb.isFederated")));
  241. } else {
  242. row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
  243. }
  244. switch (entry.accessRestriction) {
  245. case NONE:
  246. row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
  247. break;
  248. case PUSH:
  249. row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
  250. accessRestrictionTranslations.get(entry.accessRestriction)));
  251. break;
  252. case CLONE:
  253. row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
  254. accessRestrictionTranslations.get(entry.accessRestriction)));
  255. break;
  256. case VIEW:
  257. row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
  258. accessRestrictionTranslations.get(entry.accessRestriction)));
  259. break;
  260. default:
  261. row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
  262. }
  263. String owner = "";
  264. if (!ArrayUtils.isEmpty(entry.owners)) {
  265. // display first owner
  266. for (String username : entry.owners) {
  267. UserModel ownerModel = GitBlit.self().getUserModel(username);
  268. if (ownerModel != null) {
  269. owner = ownerModel.getDisplayName();
  270. break;
  271. }
  272. }
  273. if (entry.owners.size() > 1) {
  274. owner += ", ...";
  275. }
  276. }
  277. Label ownerLabel = new Label("repositoryOwner", owner);
  278. WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
  279. row.add(ownerLabel);
  280. String lastChange;
  281. if (entry.lastChange.getTime() == 0) {
  282. lastChange = "--";
  283. } else {
  284. lastChange = getTimeUtils().timeAgo(entry.lastChange);
  285. }
  286. Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
  287. row.add(lastChangeLabel);
  288. WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
  289. if (!StringUtils.isEmpty(entry.lastChangeAuthor)) {
  290. WicketUtils.setHtmlTooltip(lastChangeLabel, getString("gb.author") + ": " + entry.lastChangeAuthor);
  291. }
  292. boolean showOwner = user != null && entry.isOwner(user.username);
  293. boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username);
  294. if (showAdmin || myPersonalRepository) {
  295. Fragment repositoryLinks = new Fragment("repositoryLinks",
  296. "repositoryAdminLinks", this);
  297. repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
  298. EditRepositoryPage.class, WicketUtils
  299. .newRepositoryParameter(entry.name)));
  300. Link<Void> deleteLink = new Link<Void>("deleteRepository") {
  301. private static final long serialVersionUID = 1L;
  302. @Override
  303. public void onClick() {
  304. if (GitBlit.self().deleteRepositoryModel(entry)) {
  305. if (dp instanceof SortableRepositoriesProvider) {
  306. info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
  307. ((SortableRepositoriesProvider) dp).remove(entry);
  308. } else {
  309. setResponsePage(getPage().getClass(), getPage().getPageParameters());
  310. }
  311. } else {
  312. error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
  313. }
  314. }
  315. };
  316. deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
  317. getString("gb.deleteRepository"), entry)));
  318. repositoryLinks.add(deleteLink);
  319. row.add(repositoryLinks);
  320. } else if (showOwner) {
  321. Fragment repositoryLinks = new Fragment("repositoryLinks",
  322. "repositoryOwnerLinks", this);
  323. repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
  324. EditRepositoryPage.class, WicketUtils
  325. .newRepositoryParameter(entry.name)));
  326. row.add(repositoryLinks);
  327. } else {
  328. row.add(new Label("repositoryLinks"));
  329. }
  330. row.add(new ExternalLink("syndication", SyndicationServlet.asLink(baseUrl,
  331. entry.name, null, 0)).setVisible(linksActive));
  332. WicketUtils.setAlternatingBackground(item, counter);
  333. counter++;
  334. }
  335. };
  336. add(dataView);
  337. if (dp instanceof SortableDataProvider<?>) {
  338. // add sortable header
  339. SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
  340. Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
  341. fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
  342. fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
  343. fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
  344. fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
  345. add(fragment);
  346. } else {
  347. // not sortable
  348. Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
  349. add(fragment);
  350. }
  351. }
  352. private static class GroupRepositoryModel extends RepositoryModel {
  353. private static final long serialVersionUID = 1L;
  354. int count;
  355. String title;
  356. GroupRepositoryModel(String name, int count) {
  357. super(name, "", "", new Date(0));
  358. this.count = count;
  359. }
  360. @Override
  361. public String toString() {
  362. return (StringUtils.isEmpty(title) ? name : title) + " (" + count + ")";
  363. }
  364. }
  365. protected enum SortBy {
  366. repository, description, owner, date;
  367. }
  368. protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,
  369. final DataView<?> dataView) {
  370. return new OrderByBorder(wicketId, field.name(), dp) {
  371. private static final long serialVersionUID = 1L;
  372. @Override
  373. protected void onSortChanged() {
  374. dataView.setCurrentPage(0);
  375. }
  376. };
  377. }
  378. private static class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
  379. private static final long serialVersionUID = 1L;
  380. public RepositoriesProvider(List<RepositoryModel> list) {
  381. super(list);
  382. }
  383. @Override
  384. public List<RepositoryModel> getData() {
  385. return super.getData();
  386. }
  387. public void remove(RepositoryModel model) {
  388. int index = getData().indexOf(model);
  389. RepositoryModel groupModel = null;
  390. if (index == (getData().size() - 1)) {
  391. // last element
  392. if (index > 0) {
  393. // previous element is group header, then this is last
  394. // repository in group. remove group too.
  395. if (getData().get(index - 1) instanceof GroupRepositoryModel) {
  396. groupModel = getData().get(index - 1);
  397. }
  398. }
  399. } else if (index < (getData().size() - 1)) {
  400. // not last element. check next element for group match.
  401. if (getData().get(index - 1) instanceof GroupRepositoryModel
  402. && getData().get(index + 1) instanceof GroupRepositoryModel) {
  403. // repository is sandwiched by group headers so this
  404. // repository is the only element in the group. remove
  405. // group.
  406. groupModel = getData().get(index - 1);
  407. }
  408. }
  409. if (groupModel == null) {
  410. // Find the group and decrement the count
  411. for (int i = index; i >= 0; i--) {
  412. if (getData().get(i) instanceof GroupRepositoryModel) {
  413. ((GroupRepositoryModel) getData().get(i)).count--;
  414. break;
  415. }
  416. }
  417. } else {
  418. // Remove the group header
  419. getData().remove(groupModel);
  420. }
  421. getData().remove(model);
  422. }
  423. }
  424. private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
  425. private static final long serialVersionUID = 1L;
  426. private List<RepositoryModel> list;
  427. protected SortableRepositoriesProvider(List<RepositoryModel> list) {
  428. this.list = list;
  429. setSort(SortBy.date.name(), false);
  430. }
  431. public void remove(RepositoryModel model) {
  432. list.remove(model);
  433. }
  434. @Override
  435. public int size() {
  436. if (list == null) {
  437. return 0;
  438. }
  439. return list.size();
  440. }
  441. @Override
  442. public IModel<RepositoryModel> model(RepositoryModel header) {
  443. return new Model<RepositoryModel>(header);
  444. }
  445. @Override
  446. public Iterator<RepositoryModel> iterator(int first, int count) {
  447. SortParam sp = getSort();
  448. String prop = sp.getProperty();
  449. final boolean asc = sp.isAscending();
  450. if (prop == null || prop.equals(SortBy.date.name())) {
  451. Collections.sort(list, new Comparator<RepositoryModel>() {
  452. @Override
  453. public int compare(RepositoryModel o1, RepositoryModel o2) {
  454. if (asc) {
  455. return o1.lastChange.compareTo(o2.lastChange);
  456. }
  457. return o2.lastChange.compareTo(o1.lastChange);
  458. }
  459. });
  460. } else if (prop.equals(SortBy.repository.name())) {
  461. Collections.sort(list, new Comparator<RepositoryModel>() {
  462. @Override
  463. public int compare(RepositoryModel o1, RepositoryModel o2) {
  464. if (asc) {
  465. return o1.name.compareTo(o2.name);
  466. }
  467. return o2.name.compareTo(o1.name);
  468. }
  469. });
  470. } else if (prop.equals(SortBy.owner.name())) {
  471. Collections.sort(list, new Comparator<RepositoryModel>() {
  472. @Override
  473. public int compare(RepositoryModel o1, RepositoryModel o2) {
  474. String own1 = ArrayUtils.toString(o1.owners);
  475. String own2 = ArrayUtils.toString(o2.owners);
  476. if (asc) {
  477. return own1.compareTo(own2);
  478. }
  479. return own2.compareTo(own1);
  480. }
  481. });
  482. } else if (prop.equals(SortBy.description.name())) {
  483. Collections.sort(list, new Comparator<RepositoryModel>() {
  484. @Override
  485. public int compare(RepositoryModel o1, RepositoryModel o2) {
  486. if (asc) {
  487. return o1.description.compareTo(o2.description);
  488. }
  489. return o2.description.compareTo(o1.description);
  490. }
  491. });
  492. }
  493. return list.subList(first, first + count).iterator();
  494. }
  495. }
  496. }