GitLabClient.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. GitLabClient = function(editorUi)
  6. {
  7. GitHubClient.call(this, editorUi, 'gitlabauth');
  8. };
  9. // Extends DrawioClient
  10. mxUtils.extend(GitLabClient, GitHubClient);
  11. /**
  12. * Gitlab Client ID, see https://gitlab.com/oauth/applications/135239
  13. */
  14. GitLabClient.prototype.clientId = '5cdc018a32acddf6eba37592d9374945241e644b8368af847422d74c8709bc44';
  15. /**
  16. * OAuth scope.
  17. */
  18. GitLabClient.prototype.scope = 'api%20read_repository%20write_repository';
  19. /**
  20. * Base URL for API calls.
  21. */
  22. GitLabClient.prototype.baseUrl = 'https://gitlab.com/api/v4';
  23. /**
  24. * Authorizes the client, gets the userId and calls <open>.
  25. */
  26. GitLabClient.prototype.authenticate = function(success, error)
  27. {
  28. if (window.onGitLabCallback == null)
  29. {
  30. var auth = mxUtils.bind(this, function()
  31. {
  32. var acceptAuthResponse = true;
  33. this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, authSuccess)
  34. {
  35. var state = '123';
  36. var redirectUri = encodeURIComponent(window.location.origin + '/gitlab.html');
  37. var win = window.open('https://gitlab.com/oauth/authorize?client_id=' +
  38. this.clientId + '&scope=' + this.scope + '&redirect_uri=' + redirectUri +
  39. '&response_type=token&state=' + state, 'gitlabauth');
  40. if (win != null)
  41. {
  42. window.onGitLabCallback = mxUtils.bind(this, function(code, authWindow)
  43. {
  44. if (acceptAuthResponse)
  45. {
  46. window.onGitLabCallback = null;
  47. acceptAuthResponse = false;
  48. if (code == null)
  49. {
  50. error({message: mxResources.get('accessDenied'), retry: auth});
  51. }
  52. else
  53. {
  54. if (authSuccess != null)
  55. {
  56. authSuccess();
  57. }
  58. this.token = code;
  59. this.setUser(null);
  60. if (remember)
  61. {
  62. this.setPersistentToken(this.token);
  63. }
  64. success();
  65. }
  66. }
  67. else if (authWindow != null)
  68. {
  69. authWindow.close();
  70. }
  71. });
  72. }
  73. else
  74. {
  75. error({message: mxResources.get('serviceUnavailableOrBlocked'), retry: auth});
  76. }
  77. }), mxUtils.bind(this, function()
  78. {
  79. if (acceptAuthResponse)
  80. {
  81. window.onGitLabCallback = null;
  82. acceptAuthResponse = false;
  83. error({message: mxResources.get('accessDenied'), retry: auth});
  84. }
  85. }));
  86. });
  87. auth();
  88. }
  89. else
  90. {
  91. error({code: App.ERROR_BUSY});
  92. }
  93. };
  94. /**
  95. * Authorizes the client, gets the userId and calls <open>.
  96. */
  97. GitLabClient.prototype.executeRequest = function(req, success, error, ignoreNotFound)
  98. {
  99. var doExecute = mxUtils.bind(this, function(failOnAuth)
  100. {
  101. var acceptResponse = true;
  102. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  103. {
  104. acceptResponse = false;
  105. error({code: App.ERROR_TIMEOUT, message: mxResources.get('timeout')});
  106. }), this.ui.timeout);
  107. var temp = this.token;
  108. req.setRequestHeaders = function(request, params)
  109. {
  110. request.setRequestHeader('Authorization', 'Bearer ' + temp);
  111. request.setRequestHeader('PRIVATE_TOKEN', temp);
  112. request.setRequestHeader('Content-Type', 'application/json');
  113. };
  114. req.send(mxUtils.bind(this, function()
  115. {
  116. window.clearTimeout(timeoutThread);
  117. if (acceptResponse)
  118. {
  119. if ((req.getStatus() >= 200 && req.getStatus() <= 299) ||
  120. (ignoreNotFound && req.getStatus() == 404))
  121. {
  122. success(req);
  123. }
  124. else if (req.getStatus() === 401)
  125. {
  126. if (!failOnAuth)
  127. {
  128. this.authenticate(function()
  129. {
  130. doExecute(true);
  131. }, error);
  132. }
  133. else
  134. {
  135. error({message: mxResources.get('accessDenied'), retry: mxUtils.bind(this, function()
  136. {
  137. this.authenticate(function()
  138. {
  139. fn(true);
  140. }, error);
  141. })});
  142. }
  143. }
  144. else if (req.getStatus() === 403)
  145. {
  146. var tooLarge = false;
  147. try
  148. {
  149. var temp = JSON.parse(req.getText());
  150. if (temp != null && temp.errors != null && temp.errors.length > 0)
  151. {
  152. tooLarge = temp.errors[0].code == 'too_large';
  153. }
  154. }
  155. catch (e)
  156. {
  157. // ignore
  158. }
  159. error({message: mxResources.get((tooLarge) ? 'drawingTooLarge' : 'forbidden')});
  160. }
  161. else if (req.getStatus() === 404)
  162. {
  163. error({message: this.getErrorMessage(req, mxResources.get('fileNotFound'))});
  164. }
  165. else if (req.getStatus() === 400)
  166. {
  167. // Special case: flag to the caller that there was a conflict
  168. error({status: 400});
  169. }
  170. else
  171. {
  172. error({status: req.getStatus(), message: this.getErrorMessage(req,
  173. mxResources.get('error') + ' ' + req.getStatus())});
  174. }
  175. }
  176. }), error);
  177. });
  178. var fn = mxUtils.bind(this, function(failOnAuth)
  179. {
  180. if (this.user == null)
  181. {
  182. this.updateUser(function()
  183. {
  184. fn(true);
  185. }, error, failOnAuth);
  186. }
  187. else
  188. {
  189. doExecute(failOnAuth);
  190. }
  191. });
  192. if (this.token == null)
  193. {
  194. this.authenticate(function()
  195. {
  196. fn(true);
  197. }, error);
  198. }
  199. else
  200. {
  201. fn(false);
  202. }
  203. };
  204. /**
  205. * Finds index of ref in given token list. This is required to support groups and subgroups.
  206. */
  207. GitLabClient.prototype.getRefIndex = function(tokens, isFolder, success, error, knownRefPos)
  208. {
  209. if (knownRefPos != null)
  210. {
  211. success(tokens, knownRefPos);
  212. }
  213. else
  214. {
  215. var refPos = tokens.length - 2;
  216. // Finds ref in token list by checking which URL works
  217. var checkUrl = mxUtils.bind(this, function()
  218. {
  219. if (refPos < 2)
  220. {
  221. error({message: mxResources.get('fileNotFound')});
  222. }
  223. else
  224. {
  225. var repoPos = Math.max(refPos - 1, 0);
  226. var org = tokens.slice(0, repoPos).join('/');
  227. var repo = tokens[repoPos];
  228. var ref = tokens[refPos];
  229. var path = tokens.slice(refPos + 1, tokens.length).join('/');
  230. var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/' +
  231. (!isFolder ? 'files/' + encodeURIComponent(path) + '?ref=' + ref :
  232. 'tree?path=' + path + '&ref=' + ref);
  233. var req = new mxXmlRequest(url, null, 'HEAD');
  234. this.executeRequest(req, mxUtils.bind(this, function()
  235. {
  236. if (req.getStatus() == 200)
  237. {
  238. success(tokens, refPos);
  239. }
  240. else
  241. {
  242. error({message: mxResources.get('fileNotFound')});
  243. }
  244. }), mxUtils.bind(this, function()
  245. {
  246. if (req.getStatus() == 404)
  247. {
  248. refPos--;
  249. checkUrl();
  250. }
  251. else
  252. {
  253. error({message: mxResources.get('fileNotFound')});
  254. }
  255. }));
  256. }
  257. });
  258. checkUrl();
  259. }
  260. };
  261. /**
  262. * Checks if the client is authorized and calls the next step.
  263. */
  264. GitLabClient.prototype.getFile = function(path, success, error, asLibrary, checkExists, knownRefPos)
  265. {
  266. asLibrary = (asLibrary != null) ? asLibrary : false;
  267. this.getRefIndex(path.split('/'), false, mxUtils.bind(this, function(tokens, refPos)
  268. {
  269. var repoPos = Math.max(refPos - 1, 0);
  270. var org = tokens.slice(0, repoPos).join('/');
  271. var repo = tokens[repoPos];
  272. var ref = tokens[refPos];
  273. path = tokens.slice(refPos + 1, tokens.length).join('/');
  274. var binary = /\.png$/i.test(path);
  275. // Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
  276. if (!checkExists && (/\.v(dx|sdx?)$/i.test(path) || /\.gliffy$/i.test(path) ||
  277. (!this.ui.useCanvasForExport && binary)))
  278. {
  279. // Should never be null
  280. if (this.token != null)
  281. {
  282. var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/files/' + encodeURIComponent(ref);
  283. var tokens = path.split('/');
  284. var name = (tokens.length > 0) ? tokens[tokens.length - 1] : path;
  285. this.ui.convertFile(url, name, null, this.extension, success, error);
  286. }
  287. else
  288. {
  289. error({message: mxResources.get('accessDenied')});
  290. }
  291. }
  292. else
  293. {
  294. // Adds random parameter to bypass cache
  295. var rnd = '&t=' + new Date().getTime();
  296. url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
  297. '/repository/files/' + encodeURIComponent(path) + '?ref=' + ref;
  298. var req = new mxXmlRequest(url + rnd, null, 'GET');
  299. this.executeRequest(req, mxUtils.bind(this, function(req)
  300. {
  301. try
  302. {
  303. success(this.createGitLabFile(org, repo, ref, JSON.parse(req.getText()), asLibrary, refPos));
  304. }
  305. catch (e)
  306. {
  307. error(e);
  308. }
  309. }), error);
  310. }
  311. }), error, knownRefPos);
  312. };
  313. /**
  314. * Translates this point by the given vector.
  315. *
  316. * @param {number} dx X-coordinate of the translation.
  317. * @param {number} dy Y-coordinate of the translation.
  318. */
  319. GitLabClient.prototype.createGitLabFile = function(org, repo, ref, data, asLibrary, refPos)
  320. {
  321. var gitLabUrl = 'https://gitlab.com/';
  322. var htmlUrl = gitLabUrl + org + '/' + repo + '/blob/' + ref + '/' + data.file_path;
  323. var downloadUrl = gitLabUrl + org + '/' + repo + '/raw/' + ref + '/' + data.file_path + '?inline=false';
  324. var fileName = data.file_name;
  325. var meta = {'org': org, 'repo': repo, 'ref': ref, 'name': fileName,
  326. 'path': data.file_path, 'html_url': htmlUrl, 'download_url': downloadUrl,
  327. 'last_commit_id': data.last_commit_id, 'refPos': refPos};
  328. var content = data.content;
  329. if (data.encoding === 'base64')
  330. {
  331. if (/\.jpe?g$/i.test(fileName))
  332. {
  333. content = 'data:image/jpeg;base64,' + content;
  334. }
  335. else if (/\.gif$/i.test(fileName))
  336. {
  337. content = 'data:image/gif;base64,' + content;
  338. }
  339. else
  340. {
  341. if (/\.png$/i.test(fileName))
  342. {
  343. var xml = this.ui.extractGraphModelFromPng(content);
  344. if (xml != null && xml.length > 0)
  345. {
  346. content = xml;
  347. }
  348. else
  349. {
  350. content = 'data:image/png;base64,' + content;
  351. }
  352. }
  353. else
  354. {
  355. content = Base64.decode(content);
  356. }
  357. }
  358. }
  359. return (asLibrary) ? new GitLabLibrary(this.ui, content, meta) : new GitLabFile(this.ui, content, meta);
  360. };
  361. /**
  362. * Translates this point by the given vector.
  363. *
  364. * @param {number} dx X-coordinate of the translation.
  365. * @param {number} dy Y-coordinate of the translation.
  366. */
  367. GitLabClient.prototype.insertFile = function(filename, data, success, error, asLibrary, folderId, base64Encoded)
  368. {
  369. asLibrary = (asLibrary != null) ? asLibrary : false;
  370. console.log('folderId', folderId);
  371. this.getRefIndex(folderId.split('/'), true, mxUtils.bind(this, function(tokens, refPos)
  372. {
  373. var repoPos = Math.max(refPos - 1, 0);
  374. var org = tokens.slice(0, repoPos).join('/');
  375. var repo = tokens[repoPos];
  376. var ref = tokens[refPos];
  377. path = tokens.slice(refPos + 1, tokens.length).join('/');
  378. if (path.length > 0)
  379. {
  380. path = path + '/';
  381. }
  382. path = path + filename;
  383. this.checkExists(org + '/' + repo + '/' + ref + '/' + path, true, mxUtils.bind(this, function(checked, last_commit_id)
  384. {
  385. if (checked)
  386. {
  387. // Does not insert file here as there is another writeFile implicit via fileCreated
  388. if (!asLibrary)
  389. {
  390. var gitLabUrl = 'https://gitlab.com/';
  391. var htmlUrl = gitLabUrl + org + '/' + repo + '/blob/' + ref + '/' + path;
  392. var downloadUrl = gitLabUrl + org + '/' + repo + '/raw/' + ref + '/' + path + '?inline=false';
  393. success(new GitLabFile(this.ui, data, {'org': org, 'repo': repo, 'ref': ref, 'name': filename,
  394. 'path': path, 'html_url': htmlUrl, 'download_url': downloadUrl, 'refPos': refPos,
  395. 'last_commit_id': last_commit_id, isNew: true}));
  396. }
  397. else
  398. {
  399. if (!base64Encoded)
  400. {
  401. data = Base64.encode(data);
  402. }
  403. this.showCommitDialog(filename, true, mxUtils.bind(this, function(message)
  404. {
  405. this.writeFile(org, repo, ref, path, message, data, last_commit_id, mxUtils.bind(this, function(req)
  406. {
  407. try
  408. {
  409. var msg = JSON.parse(req.getText());
  410. success(this.createGitLabFile(org, repo, ref, msg.content, asLibrary, refPos));
  411. }
  412. catch (e)
  413. {
  414. error(e);
  415. }
  416. }), error);
  417. }), error);
  418. }
  419. }
  420. else
  421. {
  422. // create if it does not exists
  423. error();
  424. }
  425. }))
  426. }), error);
  427. };
  428. /**
  429. * Translates this point by the given vector.
  430. *
  431. * @param {number} dx X-coordinate of the translation.
  432. * @param {number} dy Y-coordinate of the translation.
  433. */
  434. GitLabClient.prototype.checkExists = function(path, askReplace, fn)
  435. {
  436. this.getFile(path, mxUtils.bind(this, function(file)
  437. {
  438. if (askReplace)
  439. {
  440. var resume = this.ui.spinner.pause();
  441. this.ui.confirm(mxResources.get('replaceIt', [path]), function()
  442. {
  443. resume();
  444. fn(true, file.getCurrentEtag());
  445. }, function()
  446. {
  447. resume();
  448. fn(false);
  449. });
  450. }
  451. else
  452. {
  453. this.ui.spinner.stop();
  454. this.ui.showError(mxResources.get('error'), mxResources.get('fileExists'), mxResources.get('ok'), function()
  455. {
  456. fn(false);
  457. });
  458. }
  459. }), mxUtils.bind(this, function(err)
  460. {
  461. fn(true);
  462. }), null, true);
  463. };
  464. /**
  465. *
  466. */
  467. GitLabClient.prototype.writeFile = function(org, repo, ref, path, message, data, last_commit_id, success, error)
  468. {
  469. if (data.length >= this.maxFileSize)
  470. {
  471. error({message: mxResources.get('drawingTooLarge') + ' (' +
  472. this.ui.formatFileSize(data.length) + ' / 1 MB)'});
  473. }
  474. else
  475. {
  476. var method = 'POST';
  477. var entity = {
  478. path: encodeURIComponent(path),
  479. branch: decodeURIComponent(ref),
  480. commit_message: message,
  481. content: data,
  482. encoding: 'base64'
  483. };
  484. if (last_commit_id != null)
  485. {
  486. entity.last_commit_id = last_commit_id;
  487. method = 'PUT';
  488. }
  489. // See https://docs.gitlab.com/ee/api/repository_files.html#update-existing-file-in-repository
  490. var url = this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) + '/repository/files/' + encodeURIComponent(path);
  491. var req = new mxXmlRequest(url, JSON.stringify(entity), method);
  492. this.executeRequest(req, mxUtils.bind(this, function(req)
  493. {
  494. success(req);
  495. }), error);
  496. }
  497. };
  498. /**
  499. * Translates this point by the given vector.
  500. *
  501. * @param {number} dx X-coordinate of the translation.
  502. * @param {number} dy Y-coordinate of the translation.
  503. */
  504. GitLabClient.prototype.saveFile = function(file, success, error, overwrite, message)
  505. {
  506. var org = file.meta.org;
  507. var repo = file.meta.repo;
  508. var ref = file.meta.ref;
  509. var path = file.meta.path;
  510. var fn = mxUtils.bind(this, function(last_commit_id, data)
  511. {
  512. this.writeFile(org, repo, ref, path, message, data, last_commit_id, mxUtils.bind(this, function(req)
  513. {
  514. delete file.meta.isNew;
  515. // Response does not return last_commit_id so we have to get the file
  516. // to to update last_commit_id and compare data to avoid lost commit
  517. this.getFile(org + '/' + repo + '/' + ref + '/' + path, mxUtils.bind(this, function(tempFile)
  518. {
  519. if (tempFile.getData() == file.getData())
  520. {
  521. success(tempFile.getCurrentEtag());
  522. }
  523. else
  524. {
  525. success({content: file.getCurrentEtag()});
  526. }
  527. }), error, null, null, file.meta.refPos);
  528. }), error);
  529. });
  530. var fn2 = mxUtils.bind(this, function()
  531. {
  532. if (this.ui.useCanvasForExport && /(\.png)$/i.test(path))
  533. {
  534. this.ui.getEmbeddedPng(mxUtils.bind(this, function(data)
  535. {
  536. fn(file.meta.last_commit_id, data);
  537. }), error, (this.ui.getCurrentFile() != file) ? file.getData() : null);
  538. }
  539. else
  540. {
  541. fn(file.meta.last_commit_id, Base64.encode(file.getData()));
  542. }
  543. });
  544. // LATER: Get last_commit_id is currently not possible since HEAD does
  545. // not have Access-Control-Expose-Headers for X-Gitlab-Last-Commit-Id
  546. if (overwrite)
  547. {
  548. this.getFile(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path, mxUtils.bind(this, function(tempFile)
  549. {
  550. file.meta.last_commit_id = tempFile.meta.last_commit_id;
  551. fn2();
  552. }), error);
  553. }
  554. else
  555. {
  556. fn2();
  557. }
  558. };
  559. /**
  560. * Checks if the client is authorized and calls the next step.
  561. */
  562. GitLabClient.prototype.pickFolder = function(fn)
  563. {
  564. this.showGitLabDialog(false, fn);
  565. };
  566. /**
  567. * Checks if the client is authorized and calls the next step.
  568. */
  569. GitLabClient.prototype.pickFile = function(fn)
  570. {
  571. fn = (fn != null) ? fn : mxUtils.bind(this, function(path)
  572. {
  573. this.ui.loadFile('A' + encodeURIComponent(path));
  574. });
  575. this.showGitLabDialog(true, fn);
  576. };
  577. /**
  578. * LATER: Refactor to use common code with GitHubClient
  579. */
  580. GitLabClient.prototype.showGitLabDialog = function(showFiles, fn)
  581. {
  582. var org = null;
  583. var repo = null;
  584. var ref = null;
  585. var path = null;
  586. var content = document.createElement('div');
  587. content.style.whiteSpace = 'nowrap';
  588. content.style.overflow = 'hidden';
  589. content.style.height = '224px';
  590. var hd = document.createElement('h3');
  591. mxUtils.write(hd, mxResources.get((showFiles) ? 'selectFile' : 'selectFolder'));
  592. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  593. content.appendChild(hd);
  594. var div = document.createElement('div');
  595. div.style.whiteSpace = 'nowrap';
  596. div.style.border = '1px solid lightgray';
  597. div.style.boxSizing = 'border-box';
  598. div.style.padding = '4px';
  599. div.style.overflow = 'auto';
  600. div.style.lineHeight = '1.2em';
  601. div.style.height = '194px';
  602. content.appendChild(div);
  603. var listItem = document.createElement('div');
  604. listItem.style.textOverflow = 'ellipsis';
  605. listItem.style.boxSizing = 'border-box';
  606. listItem.style.overflow = 'hidden';
  607. listItem.style.padding = '4px';
  608. listItem.style.width = '100%';
  609. var dlg = new CustomDialog(this.ui, content, mxUtils.bind(this, function()
  610. {
  611. fn(org + '/' + repo + '/' + encodeURIComponent(ref) + '/' + path);
  612. }));
  613. this.ui.showDialog(dlg.container, 340, 270, true, true);
  614. if (showFiles)
  615. {
  616. dlg.okButton.parentNode.removeChild(dlg.okButton);
  617. }
  618. var createLink = mxUtils.bind(this, function(label, fn, padding)
  619. {
  620. var link = document.createElement('a');
  621. link.setAttribute('href', 'javascript:void(0);');
  622. mxUtils.write(link, label);
  623. mxEvent.addListener(link, 'click', fn);
  624. if (padding != null)
  625. {
  626. var temp = listItem.cloneNode();
  627. temp.style.padding = padding;
  628. temp.appendChild(link);
  629. link = temp;
  630. }
  631. return link;
  632. });
  633. var updatePathInfo = mxUtils.bind(this, function(hideRef)
  634. {
  635. var pathInfo = document.createElement('div');
  636. pathInfo.style.marginBottom = '8px';
  637. pathInfo.appendChild(createLink(org + '/' + repo, mxUtils.bind(this, function()
  638. {
  639. path = null;
  640. selectRepo();
  641. })));
  642. if (!hideRef)
  643. {
  644. mxUtils.write(pathInfo, ' / ');
  645. pathInfo.appendChild(createLink(decodeURIComponent(ref), mxUtils.bind(this, function()
  646. {
  647. path = null;
  648. selectRef();
  649. })));
  650. }
  651. if (path != null && path.length > 0)
  652. {
  653. var tokens = path.split('/');
  654. for (var i = 0; i < tokens.length; i++)
  655. {
  656. (function(index)
  657. {
  658. mxUtils.write(pathInfo, ' / ');
  659. pathInfo.appendChild(createLink(tokens[index], mxUtils.bind(this, function()
  660. {
  661. path = tokens.slice(0, index + 1).join('/');
  662. selectFile();
  663. })));
  664. })(i);
  665. }
  666. }
  667. div.appendChild(pathInfo);
  668. });
  669. var error = mxUtils.bind(this, function(err)
  670. {
  671. this.ui.handleError(err, null, mxUtils.bind(this, function()
  672. {
  673. this.ui.spinner.stop();
  674. if (this.getUser() != null)
  675. {
  676. org = null;
  677. repo = null;
  678. ref = null;
  679. path = null;
  680. selectRepo();
  681. }
  682. else
  683. {
  684. this.ui.hideDialog();
  685. }
  686. }));
  687. });
  688. var selectFile = mxUtils.bind(this, function()
  689. {
  690. var req = new mxXmlRequest(this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
  691. '/repository/tree?path=' + path + '&ref=' + ref, null, 'GET');
  692. dlg.okButton.removeAttribute('disabled');
  693. div.innerHTML = '';
  694. this.ui.spinner.spin(div, mxResources.get('loading'));
  695. this.executeRequest(req, mxUtils.bind(this, function(req)
  696. {
  697. updatePathInfo(!ref);
  698. this.ui.spinner.stop();
  699. var files = JSON.parse(req.getText());
  700. div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
  701. {
  702. if (path == '')
  703. {
  704. path = null;
  705. selectRepo();
  706. }
  707. else
  708. {
  709. var tokens = path.split('/');
  710. path = tokens.slice(0, tokens.length - 1).join('/');
  711. selectFile();
  712. }
  713. }), '4px'));
  714. if (files == null || files.length == 0)
  715. {
  716. mxUtils.write(div, mxResources.get('noFiles'));
  717. }
  718. else
  719. {
  720. var gray = true;
  721. var listFiles = mxUtils.bind(this, function(showFolders)
  722. {
  723. for (var i = 0; i < files.length; i++)
  724. {
  725. (mxUtils.bind(this, function(file)
  726. {
  727. if (showFolders == (file.type == 'tree'))
  728. {
  729. var temp = listItem.cloneNode();
  730. temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
  731. gray = !gray;
  732. var typeImg = document.createElement('img');
  733. typeImg.src = IMAGE_PATH + '/' + (file.type == 'tree'? 'folder.png' : 'file.png');
  734. typeImg.setAttribute('align', 'absmiddle');
  735. typeImg.style.marginRight = '4px';
  736. typeImg.style.marginTop = '-4px';
  737. typeImg.width = 20;
  738. temp.appendChild(typeImg);
  739. temp.appendChild(createLink(file.name + ((file.type == 'tree') ? '/' : ''), mxUtils.bind(this, function()
  740. {
  741. if (file.type == 'tree')
  742. {
  743. path = file.path;
  744. selectFile();
  745. }
  746. else if (showFiles && file.type == 'blob')
  747. {
  748. this.ui.hideDialog();
  749. fn(org + '/' + repo + '/' + ref + '/' + file.path);
  750. }
  751. })));
  752. div.appendChild(temp);
  753. }
  754. }))(files[i]);
  755. }
  756. });
  757. listFiles(true);
  758. if (showFiles)
  759. {
  760. listFiles(false);
  761. }
  762. }
  763. }), error, true);
  764. });
  765. // Adds paging for repos and branches (files limited to 1000 by API)
  766. var pageSize = 100;
  767. var nextPageDiv = null;
  768. var scrollFn = null;
  769. var selectRef = mxUtils.bind(this, function(page)
  770. {
  771. if (page == null)
  772. {
  773. div.innerHTML = '';
  774. page = 1;
  775. }
  776. var req = new mxXmlRequest(this.baseUrl + '/projects/' + encodeURIComponent(org + '/' + repo) +
  777. '/repository/branches?per_page=' + pageSize + '&page=' + page, null, 'GET');
  778. dlg.okButton.setAttribute('disabled', 'disabled');
  779. this.ui.spinner.spin(div, mxResources.get('loading'));
  780. if (nextPageDiv != null && nextPageDiv.parentNode != null)
  781. {
  782. nextPageDiv.parentNode.removeChild(nextPageDiv);
  783. }
  784. nextPageDiv = document.createElement('a');
  785. nextPageDiv.style.display = 'block';
  786. nextPageDiv.setAttribute('href', 'javascript:void(0);');
  787. mxUtils.write(nextPageDiv, mxResources.get('more') + '...');
  788. var nextPage = mxUtils.bind(this, function()
  789. {
  790. mxEvent.removeListener(div, 'scroll', scrollFn);
  791. selectRef(page + 1);
  792. });
  793. mxEvent.addListener(nextPageDiv, 'click', nextPage);
  794. this.executeRequest(req, mxUtils.bind(this, function(req)
  795. {
  796. this.ui.spinner.stop();
  797. if (page == 1)
  798. {
  799. updatePathInfo(true);
  800. div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
  801. {
  802. path = null;
  803. selectRepo();
  804. }), '4px'));
  805. }
  806. var branches = JSON.parse(req.getText());
  807. if (branches == null || branches.length == 0)
  808. {
  809. mxUtils.write(div, mxResources.get('noFiles'));
  810. }
  811. else
  812. {
  813. for (var i = 0; i < branches.length; i++)
  814. {
  815. (mxUtils.bind(this, function(branch, idx)
  816. {
  817. var temp = listItem.cloneNode();
  818. temp.style.backgroundColor = (idx % 2 == 0) ? '#eeeeee' : '';
  819. temp.appendChild(createLink(branch.name, mxUtils.bind(this, function()
  820. {
  821. ref = encodeURIComponent(branch.name);
  822. path = '';
  823. selectFile();
  824. })));
  825. div.appendChild(temp);
  826. }))(branches[i], i);
  827. }
  828. if (branches.length == pageSize)
  829. {
  830. div.appendChild(nextPageDiv);
  831. scrollFn = function()
  832. {
  833. if (div.scrollTop >= div.scrollHeight - div.offsetHeight)
  834. {
  835. nextPage();
  836. }
  837. };
  838. mxEvent.addListener(div, 'scroll', scrollFn);
  839. }
  840. }
  841. }), error);
  842. });
  843. dlg.okButton.setAttribute('disabled', 'disabled');
  844. this.ui.spinner.spin(div, mxResources.get('loading'));
  845. var selectRepo = mxUtils.bind(this, function(page)
  846. {
  847. this.ui.spinner.stop();
  848. if (page == null)
  849. {
  850. div.innerHTML = '';
  851. page = 1;
  852. }
  853. if (nextPageDiv != null && nextPageDiv.parentNode != null)
  854. {
  855. nextPageDiv.parentNode.removeChild(nextPageDiv);
  856. }
  857. nextPageDiv = document.createElement('a');
  858. nextPageDiv.style.display = 'block';
  859. nextPageDiv.setAttribute('href', 'javascript:void(0);');
  860. mxUtils.write(nextPageDiv, mxResources.get('more') + '...');
  861. var nextPage = mxUtils.bind(this, function()
  862. {
  863. mxEvent.removeListener(div, 'scroll', scrollFn);
  864. selectRepo(page + 1);
  865. });
  866. mxEvent.addListener(nextPageDiv, 'click', nextPage);
  867. var listGroups = mxUtils.bind(this, function(callback)
  868. {
  869. this.ui.spinner.spin(div, mxResources.get('loading'));
  870. var req = new mxXmlRequest(this.baseUrl + '/groups?per_page=100', null, 'GET');
  871. this.executeRequest(req, mxUtils.bind(this, function(req)
  872. {
  873. this.ui.spinner.stop();
  874. callback(JSON.parse(req.getText()));
  875. }), error);
  876. });
  877. var listProjects = mxUtils.bind(this, function(group, callback)
  878. {
  879. this.ui.spinner.spin(div, mxResources.get('loading'));
  880. var req = new mxXmlRequest(this.baseUrl + '/groups/' + group.id + '/projects?per_page=100', null, 'GET');
  881. this.executeRequest(req, mxUtils.bind(this, function(req)
  882. {
  883. this.ui.spinner.stop();
  884. callback(group, JSON.parse(req.getText()));
  885. }), error);
  886. });
  887. listGroups(mxUtils.bind(this, function(groups)
  888. {
  889. var req = new mxXmlRequest(this.baseUrl + '/users/' + this.user.id + '/projects?per_page=' +
  890. pageSize + '&page=' + page, null, 'GET');
  891. this.ui.spinner.spin(div, mxResources.get('loading'));
  892. this.executeRequest(req, mxUtils.bind(this, function(req)
  893. {
  894. this.ui.spinner.stop();
  895. var repos = JSON.parse(req.getText());
  896. if ((repos == null || repos.length == 0) && (groups == null || groups.length == 0))
  897. {
  898. mxUtils.write(div, mxResources.get('noFiles'));
  899. }
  900. else
  901. {
  902. if (page == 1)
  903. {
  904. div.appendChild(createLink(mxResources.get('enterValue') + '...', mxUtils.bind(this, function()
  905. {
  906. var dlg = new FilenameDialog(this.ui, 'org/repo/ref', mxResources.get('ok'), mxUtils.bind(this, function(value)
  907. {
  908. if (value != null)
  909. {
  910. var tokens = value.split('/');
  911. if (tokens.length > 1)
  912. {
  913. org = tokens[0];
  914. repo = tokens[1];
  915. ref = 'master';
  916. path = null;
  917. if (tokens.length > 2)
  918. {
  919. path = encodeURIComponent(tokens.slice(2, tokens.length).join('/'));
  920. }
  921. selectFile();
  922. }
  923. else
  924. {
  925. this.ui.spinner.stop();
  926. this.ui.handleError({message: mxResources.get('invalidName')});
  927. }
  928. }
  929. }), mxResources.get('enterValue'));
  930. this.ui.showDialog(dlg.container, 300, 80, true, false);
  931. dlg.init();
  932. })));
  933. mxUtils.br(div);
  934. mxUtils.br(div);
  935. }
  936. var gray = true;
  937. for (var i = 0; i < repos.length; i++)
  938. {
  939. (mxUtils.bind(this, function(repository, idx)
  940. {
  941. var temp = listItem.cloneNode();
  942. temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
  943. gray = !gray;
  944. temp.appendChild(createLink(repository.name_with_namespace, mxUtils.bind(this, function()
  945. {
  946. org = repository.owner.username;
  947. repo = repository.name;
  948. ref = repository.default_branch || 'master';
  949. path = '';
  950. selectFile();
  951. })));
  952. div.appendChild(temp);
  953. }))(repos[i], i);
  954. }
  955. for (var i = 0; i < groups.length; i++)
  956. {
  957. listProjects(groups[i], (mxUtils.bind(this, function(group, projects)
  958. {
  959. for (var j = 0; j < projects.length; j++)
  960. {
  961. var temp = listItem.cloneNode();
  962. temp.style.backgroundColor = (gray) ? '#eeeeee' : '';
  963. gray = !gray;
  964. var project = projects[j];
  965. temp.appendChild(createLink(project.name_with_namespace, mxUtils.bind(this, function()
  966. {
  967. org = group.full_path;
  968. repo = project.path;
  969. ref = project.default_branch || 'master';
  970. path = '';
  971. selectFile();
  972. })));
  973. div.appendChild(temp);
  974. }
  975. })));
  976. }
  977. }
  978. if (repos.length == pageSize)
  979. {
  980. div.appendChild(nextPageDiv);
  981. scrollFn = function()
  982. {
  983. if (div.scrollTop >= div.scrollHeight - div.offsetHeight)
  984. {
  985. nextPage();
  986. }
  987. };
  988. mxEvent.addListener(div, 'scroll', scrollFn);
  989. }
  990. }), error);
  991. }));
  992. });
  993. if (!this.token)
  994. {
  995. this.authenticate(mxUtils.bind(this, function()
  996. {
  997. this.updateUser(function() { selectRepo(); }, error, true);
  998. }), error);
  999. }
  1000. else if (!this.user)
  1001. {
  1002. this.updateUser(function()
  1003. {
  1004. selectRepo();
  1005. }, error, true);
  1006. }
  1007. else
  1008. {
  1009. selectRepo();
  1010. }
  1011. };
  1012. /**
  1013. * Checks if the client is authorized and calls the next step.
  1014. */
  1015. GitLabClient.prototype.logout = function()
  1016. {
  1017. this.clearPersistentToken();
  1018. this.setUser(null);
  1019. this.token = null;
  1020. };