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.

RepositoryUrlPanel.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import javax.servlet.http.HttpServletRequest;
  23. import org.apache.wicket.Component;
  24. import org.apache.wicket.RequestCycle;
  25. import org.apache.wicket.markup.html.basic.Label;
  26. import org.apache.wicket.markup.html.image.ContextImage;
  27. import org.apache.wicket.markup.html.panel.Fragment;
  28. import org.apache.wicket.markup.repeater.Item;
  29. import org.apache.wicket.markup.repeater.data.DataView;
  30. import org.apache.wicket.markup.repeater.data.ListDataProvider;
  31. import org.apache.wicket.protocol.http.WebRequest;
  32. import org.apache.wicket.protocol.http.request.WebClientInfo;
  33. import com.gitblit.Constants.AccessPermission;
  34. import com.gitblit.Constants.AccessRestrictionType;
  35. import com.gitblit.GitBlit;
  36. import com.gitblit.Keys;
  37. import com.gitblit.models.GitClientApplication;
  38. import com.gitblit.models.RepositoryModel;
  39. import com.gitblit.models.RepositoryUrl;
  40. import com.gitblit.models.UserModel;
  41. import com.gitblit.utils.StringUtils;
  42. import com.gitblit.wicket.ExternalImage;
  43. import com.gitblit.wicket.GitBlitWebSession;
  44. import com.gitblit.wicket.WicketUtils;
  45. /**
  46. * Smart repository url panel which can display multiple Gitblit repository urls
  47. * and also supports 3rd party app clone links.
  48. *
  49. * @author James Moger
  50. *
  51. */
  52. public class RepositoryUrlPanel extends BasePanel {
  53. private static final long serialVersionUID = 1L;
  54. private final String externalPermission = "?";
  55. private boolean onlyUrls;
  56. private UserModel user;
  57. private RepositoryModel repository;
  58. private RepositoryUrl primaryUrl;
  59. private Map<String, String> urlPermissionsMap;
  60. private Map<AccessRestrictionType, String> accessRestrictionsMap;
  61. public RepositoryUrlPanel(String wicketId, boolean onlyUrls, UserModel user, RepositoryModel repository) {
  62. super(wicketId);
  63. this.onlyUrls = onlyUrls;
  64. this.user = user == null ? UserModel.ANONYMOUS : user;
  65. this.repository = repository;
  66. this.urlPermissionsMap = new HashMap<String, String>();
  67. }
  68. @Override
  69. protected void onInitialize() {
  70. super.onInitialize();
  71. HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
  72. List<RepositoryUrl> repositoryUrls = GitBlit.self().getRepositoryUrls(req, user, repository);
  73. // grab primary url from the top of the list
  74. primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
  75. boolean canClone = primaryUrl != null && ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
  76. if (repositoryUrls.size() == 0 || !canClone) {
  77. // no urls, nothing to show.
  78. add(new Label("repositoryUrlPanel").setVisible(false));
  79. add(new Label("applicationMenusPanel").setVisible(false));
  80. add(new Label("repositoryIndicators").setVisible(false));
  81. return;
  82. }
  83. // display primary url
  84. add(createPrimaryUrlPanel("repositoryUrlPanel", repository, repositoryUrls));
  85. if (onlyUrls) {
  86. add(new Label("repositoryIndicators").setVisible(false));
  87. } else {
  88. add(createRepositoryIndicators(repository));
  89. }
  90. boolean allowAppLinks = GitBlit.getBoolean(Keys.web.allowAppCloneLinks, true);
  91. if (onlyUrls || !canClone || !allowAppLinks) {
  92. // only display the url(s)
  93. add(new Label("applicationMenusPanel").setVisible(false));
  94. return;
  95. }
  96. // create the git client application menus
  97. add(createApplicationMenus("applicationMenusPanel", user, repository, repositoryUrls));
  98. }
  99. public String getPrimaryUrl() {
  100. return primaryUrl == null ? "" : primaryUrl.url;
  101. }
  102. protected Fragment createPrimaryUrlPanel(String wicketId, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {
  103. Fragment urlPanel = new Fragment(wicketId, "repositoryUrlFragment", this);
  104. urlPanel.setRenderBodyOnly(true);
  105. if (repositoryUrls.size() == 1) {
  106. //
  107. // Single repository url, no dropdown menu
  108. //
  109. urlPanel.add(new Label("menu").setVisible(false));
  110. } else {
  111. //
  112. // Multiple repository urls, show url drop down menu
  113. //
  114. ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);
  115. DataView<RepositoryUrl> repoUrlMenuItems = new DataView<RepositoryUrl>("repoUrls", urlsDp) {
  116. private static final long serialVersionUID = 1L;
  117. public void populateItem(final Item<RepositoryUrl> item) {
  118. RepositoryUrl repoUrl = item.getModelObject();
  119. // repository url
  120. Fragment fragment = new Fragment("repoUrl", "actionFragment", this);
  121. Component content = new Label("content", repoUrl.url).setRenderBodyOnly(true);
  122. WicketUtils.setCssClass(content, "commandMenuItem");
  123. fragment.add(content);
  124. item.add(fragment);
  125. Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
  126. WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
  127. String tooltip = getProtocolPermissionDescription(repository, repoUrl);
  128. WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
  129. fragment.add(permissionLabel);
  130. fragment.add(createCopyFragment(repoUrl.url));
  131. }
  132. };
  133. Fragment urlMenuFragment = new Fragment("menu", "urlProtocolMenuFragment", this);
  134. urlMenuFragment.setRenderBodyOnly(true);
  135. urlMenuFragment.add(new Label("menuText", getString("gb.url")));
  136. urlMenuFragment.add(repoUrlMenuItems);
  137. urlPanel.add(urlMenuFragment);
  138. }
  139. // access restriction icon and tooltip
  140. if (GitBlit.isServingRepositories()) {
  141. switch (repository.accessRestriction) {
  142. case NONE:
  143. urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
  144. break;
  145. case PUSH:
  146. urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
  147. getAccessRestrictions().get(repository.accessRestriction)));
  148. break;
  149. case CLONE:
  150. urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
  151. getAccessRestrictions().get(repository.accessRestriction)));
  152. break;
  153. case VIEW:
  154. urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
  155. getAccessRestrictions().get(repository.accessRestriction)));
  156. break;
  157. default:
  158. if (repositoryUrls.size() == 1) {
  159. // force left end cap to have some width
  160. urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
  161. } else {
  162. urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
  163. }
  164. }
  165. } else {
  166. if (repositoryUrls.size() == 1) {
  167. // force left end cap to have some width
  168. urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
  169. } else {
  170. urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
  171. }
  172. }
  173. urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
  174. Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());
  175. String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
  176. WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
  177. urlPanel.add(permissionLabel);
  178. urlPanel.add(createCopyFragment(primaryUrl.url));
  179. return urlPanel;
  180. }
  181. protected Fragment createApplicationMenus(String wicketId, UserModel user, final RepositoryModel repository, final List<RepositoryUrl> repositoryUrls) {
  182. final List<GitClientApplication> displayedApps = new ArrayList<GitClientApplication>();
  183. final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
  184. if (user.canClone(repository)) {
  185. for (GitClientApplication app : GitBlit.self().getClientApplications()) {
  186. if (app.isActive && app.allowsPlatform(userAgent)) {
  187. displayedApps.add(app);
  188. }
  189. }
  190. }
  191. final String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
  192. ListDataProvider<GitClientApplication> displayedAppsDp = new ListDataProvider<GitClientApplication>(displayedApps);
  193. DataView<GitClientApplication> appMenus = new DataView<GitClientApplication>("appMenus", displayedAppsDp) {
  194. private static final long serialVersionUID = 1L;
  195. public void populateItem(final Item<GitClientApplication> item) {
  196. final GitClientApplication clientApp = item.getModelObject();
  197. // filter the urls for the client app
  198. List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
  199. for (RepositoryUrl repoUrl : repositoryUrls) {
  200. if (clientApp.minimumPermission == null || repoUrl.permission == null) {
  201. // no minimum permission or external permissions, assume it is satisfactory
  202. if (clientApp.supportsTransport(repoUrl.url)) {
  203. urls.add(repoUrl);
  204. }
  205. } else if (repoUrl.permission.atLeast(clientApp.minimumPermission)) {
  206. // repo url meets minimum permission requirement
  207. if (clientApp.supportsTransport(repoUrl.url)) {
  208. urls.add(repoUrl);
  209. }
  210. }
  211. }
  212. if (urls.size() == 0) {
  213. // do not show this app menu because there are no urls
  214. item.add(new Label("appMenu").setVisible(false));
  215. return;
  216. }
  217. Fragment appMenu = new Fragment("appMenu", "appMenuFragment", this);
  218. appMenu.setRenderBodyOnly(true);
  219. item.add(appMenu);
  220. // menu button
  221. appMenu.add(new Label("applicationName", clientApp.name));
  222. // application icon
  223. Component img;
  224. if (StringUtils.isEmpty(clientApp.icon)) {
  225. img = WicketUtils.newClearPixel("applicationIcon").setVisible(false);
  226. } else {
  227. if (clientApp.icon.contains("://")) {
  228. // external image
  229. img = new ExternalImage("applicationIcon", clientApp.icon);
  230. } else {
  231. // context image
  232. img = WicketUtils.newImage("applicationIcon", clientApp.icon);
  233. }
  234. }
  235. appMenu.add(img);
  236. // application menu title, may be a link
  237. if (StringUtils.isEmpty(clientApp.productUrl)) {
  238. appMenu.add(new Label("applicationTitle", clientApp.toString()));
  239. } else {
  240. appMenu.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));
  241. }
  242. // brief application description
  243. if (StringUtils.isEmpty(clientApp.description)) {
  244. appMenu.add(new Label("applicationDescription").setVisible(false));
  245. } else {
  246. appMenu.add(new Label("applicationDescription", clientApp.description));
  247. }
  248. // brief application legal info, copyright, license, etc
  249. if (StringUtils.isEmpty(clientApp.legal)) {
  250. appMenu.add(new Label("applicationLegal").setVisible(false));
  251. } else {
  252. appMenu.add(new Label("applicationLegal", clientApp.legal));
  253. }
  254. // a nested repeater for all action items
  255. ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(urls);
  256. DataView<RepositoryUrl> actionItems = new DataView<RepositoryUrl>("actionItems", urlsDp) {
  257. private static final long serialVersionUID = 1L;
  258. public void populateItem(final Item<RepositoryUrl> repoLinkItem) {
  259. RepositoryUrl repoUrl = repoLinkItem.getModelObject();
  260. Fragment fragment = new Fragment("actionItem", "actionFragment", this);
  261. fragment.add(createPermissionBadge("permission", repoUrl));
  262. if (!StringUtils.isEmpty(clientApp.cloneUrl)) {
  263. // custom registered url
  264. String url = substitute(clientApp.cloneUrl, repoUrl.url, baseURL);
  265. fragment.add(new LinkPanel("content", "applicationMenuItem", getString("gb.clone") + " " + repoUrl.url, url));
  266. repoLinkItem.add(fragment);
  267. fragment.add(new Label("copyFunction").setVisible(false));
  268. } else if (!StringUtils.isEmpty(clientApp.command)) {
  269. // command-line
  270. String command = substitute(clientApp.command, repoUrl.url, baseURL);
  271. Label content = new Label("content", command);
  272. WicketUtils.setCssClass(content, "commandMenuItem");
  273. fragment.add(content);
  274. repoLinkItem.add(fragment);
  275. // copy function for command
  276. fragment.add(createCopyFragment(command));
  277. }
  278. }};
  279. appMenu.add(actionItems);
  280. }
  281. };
  282. Fragment applicationMenus = new Fragment(wicketId, "applicationMenusFragment", this);
  283. applicationMenus.add(appMenus);
  284. return applicationMenus;
  285. }
  286. protected String substitute(String pattern, String repoUrl, String baseUrl) {
  287. return pattern.replace("${repoUrl}", repoUrl).replace("${baseUrl}", baseUrl);
  288. }
  289. protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
  290. Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
  291. WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
  292. String tooltip = getProtocolPermissionDescription(repository, repoUrl);
  293. WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
  294. return permissionLabel;
  295. }
  296. protected Fragment createCopyFragment(String text) {
  297. if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
  298. // clippy: flash-based copy & paste
  299. Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
  300. String baseUrl = WicketUtils.getGitblitURL(getRequest());
  301. ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
  302. clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text));
  303. copyFragment.add(clippy);
  304. return copyFragment;
  305. } else {
  306. // javascript: manual copy & paste with modal browser prompt dialog
  307. Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
  308. ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
  309. img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", text));
  310. copyFragment.add(img);
  311. return copyFragment;
  312. }
  313. }
  314. protected String getProtocolPermissionDescription(RepositoryModel repository,
  315. RepositoryUrl repoUrl) {
  316. if (!urlPermissionsMap.containsKey(repoUrl.url)) {
  317. String note;
  318. if (repoUrl.isExternal()) {
  319. String protocol;
  320. int protocolIndex = repoUrl.url.indexOf("://");
  321. if (protocolIndex > -1) {
  322. // explicit protocol specified
  323. protocol = repoUrl.url.substring(0, protocolIndex);
  324. } else {
  325. // implicit SSH url
  326. protocol = "ssh";
  327. }
  328. note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
  329. } else {
  330. note = null;
  331. String key;
  332. switch (repoUrl.permission) {
  333. case OWNER:
  334. case REWIND:
  335. key = "gb.rewindPermission";
  336. break;
  337. case DELETE:
  338. key = "gb.deletePermission";
  339. break;
  340. case CREATE:
  341. key = "gb.createPermission";
  342. break;
  343. case PUSH:
  344. key = "gb.pushPermission";
  345. break;
  346. case CLONE:
  347. key = "gb.clonePermission";
  348. break;
  349. default:
  350. key = null;
  351. note = getString("gb.viewAccess");
  352. break;
  353. }
  354. if (note == null) {
  355. String pattern = getString(key);
  356. String description = MessageFormat.format(pattern, repoUrl.permission.toString());
  357. note = description;
  358. }
  359. }
  360. urlPermissionsMap.put(repoUrl.url, note);
  361. }
  362. return urlPermissionsMap.get(repoUrl.url);
  363. }
  364. protected Map<AccessRestrictionType, String> getAccessRestrictions() {
  365. if (accessRestrictionsMap == null) {
  366. accessRestrictionsMap = new HashMap<AccessRestrictionType, String>();
  367. for (AccessRestrictionType type : AccessRestrictionType.values()) {
  368. switch (type) {
  369. case NONE:
  370. accessRestrictionsMap.put(type, getString("gb.notRestricted"));
  371. break;
  372. case PUSH:
  373. accessRestrictionsMap.put(type, getString("gb.pushRestricted"));
  374. break;
  375. case CLONE:
  376. accessRestrictionsMap.put(type, getString("gb.cloneRestricted"));
  377. break;
  378. case VIEW:
  379. accessRestrictionsMap.put(type, getString("gb.viewRestricted"));
  380. break;
  381. }
  382. }
  383. }
  384. return accessRestrictionsMap;
  385. }
  386. protected Component createRepositoryIndicators(RepositoryModel repository) {
  387. Fragment fragment = new Fragment("repositoryIndicators", "indicatorsFragment", this);
  388. if (repository.isBare) {
  389. fragment.add(new Label("workingCopyIndicator").setVisible(false));
  390. } else {
  391. Fragment wc = new Fragment("workingCopyIndicator", "workingCopyFragment", this);
  392. Label lbl = new Label("workingCopy", getString("gb.workingCopy"));
  393. WicketUtils.setHtmlTooltip(lbl, getString("gb.workingCopyWarning"));
  394. wc.add(lbl);
  395. fragment.add(wc);
  396. }
  397. boolean allowForking = GitBlit.getBoolean(Keys.web.allowForking, true);
  398. if (!allowForking || user == null || !user.isAuthenticated) {
  399. // must be logged-in to fork, hide all fork controls
  400. fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
  401. } else {
  402. String fork = GitBlit.self().getFork(user.username, repository.name);
  403. boolean hasFork = fork != null;
  404. boolean canFork = user.canFork(repository);
  405. if (hasFork || !canFork) {
  406. if (user.canFork() && !repository.allowForks) {
  407. // show forks prohibited indicator
  408. Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this);
  409. Label lbl = new Label("forksProhibited", getString("gb.forksProhibited"));
  410. WicketUtils.setHtmlTooltip(lbl, getString("gb.forksProhibitedWarning"));
  411. wc.add(lbl);
  412. fragment.add(wc);
  413. } else {
  414. // can not fork, no need for forks prohibited indicator
  415. fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
  416. }
  417. } else if (canFork) {
  418. // can fork and we do not have one
  419. fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
  420. }
  421. }
  422. return fragment;
  423. }
  424. }