PageRenderTime 354ms CodeModel.GetById 54ms RepoModel.GetById 91ms app.codeStats 0ms

/share/www/script/futon.js

http://github.com/apache/couchdb
JavaScript | 597 lines | 522 code | 54 blank | 21 comment | 92 complexity | 7932be5b02da73cdf5787c812ddbd8fc MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. // $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
  13. function $$(node) {
  14. var data = $(node).data("$$");
  15. if (data) {
  16. return data;
  17. } else {
  18. data = {};
  19. $(node).data("$$", data);
  20. return data;
  21. }
  22. };
  23. (function($) {
  24. function Session() {
  25. function doLogin(name, password, callback) {
  26. $.couch.login({
  27. name : name,
  28. password : password,
  29. success : function() {
  30. $.futon.session.sidebar();
  31. callback();
  32. },
  33. error : function(code, error, reason) {
  34. $.futon.session.sidebar();
  35. callback({name : "Error logging in: "+reason});
  36. }
  37. });
  38. };
  39. function doSignup(name, password, callback, runLogin) {
  40. $.couch.signup({
  41. name : name
  42. }, password, {
  43. success : function() {
  44. if (runLogin) {
  45. doLogin(name, password, callback);
  46. } else {
  47. callback();
  48. }
  49. },
  50. error : function(status, error, reason) {
  51. $.futon.session.sidebar();
  52. if (error == "conflict") {
  53. callback({name : "Name '"+name+"' is taken"});
  54. } else {
  55. callback({name : "Signup error: "+reason});
  56. }
  57. }
  58. });
  59. };
  60. function validateUsernameAndPassword(data, callback) {
  61. if (!data.name || data.name.length == 0) {
  62. callback({name: "Please enter a name."});
  63. return false;
  64. };
  65. return validatePassword(data, callback);
  66. };
  67. function validatePassword(data, callback) {
  68. if (!data.password || data.password.length == 0) {
  69. callback({password: "Please enter a password."});
  70. return false;
  71. };
  72. return true;
  73. };
  74. function createAdmin() {
  75. $.showDialog("dialog/_create_admin.html", {
  76. submit: function(data, callback) {
  77. if (!validateUsernameAndPassword(data, callback)) return;
  78. $.couch.config({
  79. success : function() {
  80. setTimeout(function() {
  81. doLogin(data.name, data.password, function(errors) {
  82. if(!$.isEmptyObject(errors)) {
  83. callback(errors);
  84. return;
  85. }
  86. doSignup(data.name, null, function(errors) {
  87. if (errors && errors.name && errors.name.indexOf && errors.name.indexOf("taken") == -1) {
  88. callback(errors);
  89. } else {
  90. callback();
  91. }
  92. }, false);
  93. });
  94. }, 200);
  95. }
  96. }, "admins", data.name, data.password);
  97. }
  98. });
  99. return false;
  100. };
  101. function login() {
  102. $.showDialog("dialog/_login.html", {
  103. submit: function(data, callback) {
  104. if (!validateUsernameAndPassword(data, callback)) return;
  105. doLogin(data.name, data.password, callback);
  106. }
  107. });
  108. return false;
  109. };
  110. function logout() {
  111. $.couch.logout({
  112. success : function(resp) {
  113. $.futon.session.sidebar();
  114. },
  115. error: function(status, e, reason) {
  116. alert('An error occurred logging out: ' + reason);
  117. }
  118. })
  119. };
  120. function signup() {
  121. $.showDialog("dialog/_signup.html", {
  122. submit: function(data, callback) {
  123. if (!validateUsernameAndPassword(data, callback)) return;
  124. doSignup(data.name, data.password, callback, true);
  125. }
  126. });
  127. return false;
  128. };
  129. function changePassword () {
  130. var updateUserDoc = function(resp, data) {
  131. // regular users get their _users doc updated
  132. $.couch.db(resp.info.authentication_db).openDoc("org.couchdb.user:"+resp.userCtx.name, {
  133. error: function () {
  134. // ignore 404
  135. location.reload();
  136. },
  137. success: function (user) {
  138. user.password = data.password;
  139. $.couch.db(resp.info.authentication_db).saveDoc(user, {
  140. success: function() {
  141. doLogin(user.name, user.password, function(errors) {
  142. if(!$.isEmptyObject(errors)) {
  143. callback(errors);
  144. return;
  145. } else {
  146. location.reload();
  147. }
  148. });
  149. }
  150. });
  151. }
  152. });
  153. }
  154. $.showDialog("dialog/_change_password.html", {
  155. submit: function(data, callback) {
  156. if (validatePassword(data, callback)) {
  157. if (data.password != data.verify_password) {
  158. callback({verify_password: "Passwords don't match."});
  159. return false;
  160. }
  161. } else {
  162. return false;
  163. }
  164. $.couch.session({
  165. error: function(status, e, reason) {
  166. alert('Could not get your session info: ' + reason);
  167. },
  168. success: function (resp) {
  169. // admin users may have a config entry, change the password
  170. // there first. Update their user doc later, if it exists
  171. if (resp.userCtx.roles.indexOf("_admin") > -1) { // user is admin
  172. // check whether we have a config entry
  173. $.couch.config({
  174. success : function (response) { // er do have a config entry
  175. $.couch.config({
  176. success : function () {
  177. window.setTimeout(function() {
  178. doLogin(resp.userCtx.name, data.password, function(errors) {
  179. if(!$.isEmptyObject(errors)) {
  180. callback(errors);
  181. return;
  182. } else {
  183. location.reload();
  184. }
  185. });
  186. }, 1000);
  187. },
  188. error: function(status, e, reason) {
  189. callback('Could not persist the new password: ' + reason);
  190. }
  191. }, "admins", resp.userCtx.name, data.password);
  192. }
  193. }, "admins", resp.userCtx.name);
  194. } else { // non-admin users, update their user doc
  195. updateUserDoc(resp, data);
  196. }
  197. }
  198. });
  199. }
  200. });
  201. return false;
  202. };
  203. this.setupSidebar = function() {
  204. $("#userCtx .login").click(login);
  205. $("#userCtx .logout").click(logout);
  206. $("#userCtx .signup").click(signup);
  207. $("#userCtx .createadmin").click(createAdmin);
  208. $("#userCtx .changepass").click(changePassword);
  209. };
  210. this.sidebar = function() {
  211. // get users db info?
  212. $("#userCtx span").hide();
  213. $(".serverAdmin").attr('disabled', 'disabled');
  214. $.couch.session({
  215. success : function(r) {
  216. var userCtx = r.userCtx;
  217. var urlParts = location.search.substr(1).split("/");
  218. var dbName = decodeURIComponent(urlParts.shift());
  219. var dbNameRegExp = new RegExp("[^a-z0-9\_\$\(\)\+\/\-]", "g");
  220. dbName = dbName.replace(dbNameRegExp, "");
  221. $$("#userCtx").userCtx = userCtx;
  222. if (userCtx.name) {
  223. $("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)});
  224. if (userCtx.roles.indexOf("_admin") != -1) {
  225. $("#userCtx .loggedin").show();
  226. $("#userCtx .loggedinadmin").show();
  227. $(".serverAdmin").removeAttr('disabled'); // user is a server admin
  228. } else {
  229. $("#userCtx .loggedin").show();
  230. if (dbName != "") {
  231. $.couch.db(dbName).getDbProperty("_security", { // check security roles for user admins
  232. success: function(resp) {
  233. var adminRoles = resp.admins.roles;
  234. if ($.inArray(userCtx.name, resp.admins.names)>=0) { // user is admin
  235. $(".userAdmin").removeAttr('disabled');
  236. }
  237. else {
  238. for (var i=0; i<userCtx.roles.length; i++) {
  239. if ($.inArray(userCtx.roles[i], resp.admins.roles)>=0) { // user has role that is an admin
  240. $(".userAdmin").removeAttr('disabled');
  241. }
  242. }
  243. }
  244. }
  245. });
  246. }
  247. }
  248. } else if (userCtx.roles.indexOf("_admin") != -1) {
  249. $("#userCtx .adminparty").show();
  250. $(".serverAdmin").removeAttr('disabled');
  251. } else {
  252. $("#userCtx .loggedout").show();
  253. };
  254. }
  255. })
  256. };
  257. };
  258. function Navigation() {
  259. var nav = this;
  260. this.loaded = false;
  261. this.eventHandlers = {
  262. load: []
  263. };
  264. this.ready = function(callback) {
  265. if (callback) {
  266. if (this.loaded) {
  267. callback.apply(this);
  268. }
  269. this.eventHandlers["load"].push(callback);
  270. } else {
  271. this.loaded = true;
  272. callbacks = this.eventHandlers["load"];
  273. for (var i = 0; i < callbacks.length; i++) {
  274. callbacks[i].apply(this);
  275. }
  276. }
  277. }
  278. this.addDatabase = function(name) {
  279. var current = $.futon.storage.get("recent", "");
  280. var recentDbs = current ? current.split(",") : [];
  281. if ($.inArray(name, recentDbs) == -1) {
  282. recentDbs.unshift(name);
  283. if (recentDbs.length > 10) recentDbs.length = 10;
  284. $.futon.storage.set("recent", recentDbs.join(","));
  285. this.updateDatabases();
  286. }
  287. }
  288. this.removeDatabase = function(name) {
  289. // remove database from recent databases list
  290. var current = $.futon.storage.get("recent", "");
  291. var recentDbs = current ? current.split(",") : [];
  292. var recentIdx = $.inArray(name, recentDbs);
  293. if (recentIdx >= 0) {
  294. recentDbs.splice(recentIdx, 1);
  295. $.futon.storage.set("recent", recentDbs.join(","));
  296. this.updateDatabases();
  297. }
  298. }
  299. this.updateDatabases = function() {
  300. var selection = null;
  301. $("#dbs .selected a").each(function() {
  302. selection = [this.pathname, this.search];
  303. });
  304. $("#dbs").empty();
  305. var recentDbs = $.futon.storage.get("recent").split(",");
  306. recentDbs.sort();
  307. $.each(recentDbs, function(idx, name) {
  308. if (name) {
  309. name = encodeURIComponent(name);
  310. $("#dbs").append("<li>" +
  311. "<button class='remove' title='Remove from list' value='" + name + "'></button>" +
  312. "<a href='database.html?" + name + "' title='" + name + "'>" + name +
  313. "</a></li>");
  314. }
  315. });
  316. if (selection) {
  317. this.updateSelection(selection[0], selection[1]);
  318. }
  319. $("#dbs button.remove").click(function() {
  320. nav.removeDatabase(this.value);
  321. return false;
  322. });
  323. }
  324. this.updateSelection = function(path, queryString) {
  325. function fixupPath(path) { // hack for IE/Win
  326. return (path.charAt(0) != "/") ? ("/" + path) : path;
  327. }
  328. if (!path) {
  329. path = location.pathname;
  330. if (!queryString) {
  331. queryString = location.search;
  332. }
  333. } else if (!queryString) {
  334. queryString = "";
  335. }
  336. var href = fixupPath(path + queryString);
  337. $("#nav li").removeClass("selected");
  338. $("#nav li a").each(function() {
  339. if (fixupPath(this.pathname) + this.search != href) return;
  340. $(this).parent("li").addClass("selected").parents("li").addClass("selected");
  341. });
  342. }
  343. this.toggle = function(speed) {
  344. if (speed === undefined) {
  345. speed = 500;
  346. }
  347. var sidebar = $("#sidebar").stop(true, true);
  348. var hidden = !$(sidebar).is(".hidden");
  349. $("#wrap").animate({
  350. marginRight: hidden ? 0 : 210
  351. }, speed, function() {
  352. $(document.body).toggleClass("fullwidth", hidden);
  353. });
  354. sidebar.toggleClass("hidden").animate({
  355. width: hidden ? 26 : 210,
  356. height: hidden ? $("h1").outerHeight() - 1 : "100%",
  357. right: hidden ? 0 : -210
  358. }, speed).children(":not(#sidebar-toggle)").animate({
  359. opacity: "toggle"
  360. }, speed);
  361. $("h1").animate({marginRight: hidden ? 26 : 0}, speed);
  362. $("#sidebar-toggle")
  363. .attr("title", hidden ? "Show Sidebar" : "Hide Sidebar");
  364. $.futon.storage.set("sidebar", hidden ? "hidden" : "show");
  365. };
  366. }
  367. function Storage() {
  368. var storage = this;
  369. this.decls = {};
  370. this.declare = function(name, options) {
  371. this.decls[name] = $.extend({}, {
  372. scope: "window",
  373. defaultValue: null,
  374. prefix: ""
  375. }, options || {});
  376. }
  377. this.declareWithPrefix = function(prefix, decls) {
  378. for (var name in decls) {
  379. var options = decls[name];
  380. options.prefix = prefix;
  381. storage.declare(name, options);
  382. }
  383. }
  384. this.del = function(name) {
  385. lookup(name, function(decl) {
  386. handlers[decl.scope].del(decl.prefix + name);
  387. });
  388. }
  389. this.get = function(name, defaultValue) {
  390. return lookup(name, function(decl) {
  391. var value = handlers[decl.scope].get(decl.prefix + name);
  392. if (value !== undefined) {
  393. return value;
  394. }
  395. if (defaultValue !== undefined) {
  396. return defaultValue;
  397. }
  398. return decl.defaultValue;
  399. });
  400. }
  401. this.set = function(name, value) {
  402. lookup(name, function(decl) {
  403. if (value == decl.defaultValue) {
  404. handlers[decl.scope].del(decl.prefix + name);
  405. } else {
  406. handlers[decl.scope].set(decl.prefix + name, value);
  407. }
  408. });
  409. }
  410. function lookup(name, callback) {
  411. var decl = storage.decls[name];
  412. if (decl === undefined) {
  413. return decl;
  414. }
  415. return callback(decl);
  416. }
  417. function windowName() {
  418. try {
  419. return JSON.parse(window.name || "{}");
  420. } catch (e) {
  421. return {};
  422. }
  423. }
  424. // add suffix to cookie names to be able to separate between ports
  425. var cookiePrefix = location.port + "_";
  426. var handlers = {
  427. "cookie": {
  428. get: function(name) {
  429. var nameEq = cookiePrefix + name + "=";
  430. var parts = document.cookie.split(';');
  431. for (var i = 0; i < parts.length; i++) {
  432. var part = parts[i].replace(/^\s+/, "");
  433. if (part.indexOf(nameEq) == 0) {
  434. return unescape(part.substring(nameEq.length, part.length));
  435. }
  436. }
  437. },
  438. set: function(name, value) {
  439. var date = new Date();
  440. date.setTime(date.getTime() + 14*24*60*60*1000); // two weeks
  441. document.cookie = cookiePrefix + name + "=" + escape(value) +
  442. "; expires=" + date.toGMTString();
  443. },
  444. del: function(name) {
  445. var date = new Date();
  446. date.setTime(date.getTime() - 24*60*60*1000); // yesterday
  447. document.cookie = cookiePrefix + name + "=" +
  448. "; expires=" + date.toGMTString();
  449. }
  450. },
  451. "window": {
  452. get: function(name) {
  453. return windowName()[name];
  454. },
  455. set: function(name, value) {
  456. var obj = windowName();
  457. obj[name] = value || null;
  458. window.name = JSON.stringify(obj);
  459. },
  460. del: function(name) {
  461. var obj = windowName();
  462. delete obj[name];
  463. window.name = JSON.stringify(obj);
  464. }
  465. }
  466. };
  467. }
  468. $.couch.urlPrefix = "..";
  469. $.futon = $.futon || {};
  470. $.extend($.futon, {
  471. navigation: new Navigation(),
  472. session : new Session(),
  473. storage: new Storage()
  474. });
  475. $.fn.addPlaceholder = function() {
  476. if (this[0] && "placeholder" in document.createElement("input")) {
  477. return; // found native placeholder support
  478. }
  479. return this.live('focusin', function() {
  480. var input = $(this);
  481. if (input.val() === input.attr("placeholder")) {
  482. input.removeClass("placeholder").val("");
  483. }
  484. }).live("focusout", function() {
  485. var input = $(this);
  486. if (input.val() === "") {
  487. input.val(input.attr("placeholder")).addClass("placeholder");
  488. }
  489. }).trigger("focusout");
  490. }
  491. $.fn.enableTabInsertion = function(chars) {
  492. chars = chars || "\t";
  493. var width = chars.length;
  494. return this.keydown(function(evt) {
  495. if (evt.keyCode == 9) {
  496. var v = this.value;
  497. var start = this.selectionStart;
  498. var scrollTop = this.scrollTop;
  499. if (start !== undefined) {
  500. this.value = v.slice(0, start) + chars + v.slice(start);
  501. this.selectionStart = this.selectionEnd = start + width;
  502. } else {
  503. document.selection.createRange().text = chars;
  504. this.caretPos += width;
  505. }
  506. return false;
  507. }
  508. });
  509. }
  510. $(document)
  511. .ajaxStart(function() { $(this.body).addClass("loading"); })
  512. .ajaxStop(function() { $(this.body).removeClass("loading"); });
  513. $.futon.storage.declare("sidebar", {scope: "cookie", defaultValue: "show"});
  514. $.futon.storage.declare("recent", {scope: "cookie", defaultValue: ""});
  515. $(function() {
  516. document.title = "Apache CouchDB - Futon: " + document.title;
  517. if ($.futon.storage.get("sidebar") == "hidden") {
  518. // doing this as early as possible prevents flickering
  519. $(document.body).addClass("fullwidth");
  520. }
  521. $("input[placeholder]").addPlaceholder();
  522. $.get("_sidebar.html", function(resp) {
  523. $("#wrap").append(resp)
  524. .find("#sidebar-toggle").click(function(e) {
  525. $.futon.navigation.toggle(e.shiftKey ? 2500 : 500);
  526. return false;
  527. });
  528. if ($.futon.storage.get("sidebar") == "hidden") {
  529. $.futon.navigation.toggle(0);
  530. }
  531. $.futon.navigation.updateDatabases();
  532. $.futon.navigation.updateSelection();
  533. $.futon.navigation.ready();
  534. $.futon.session.setupSidebar();
  535. $.futon.session.sidebar();
  536. $.couch.info({
  537. success: function(info, status) {
  538. $("#version").text(info.version);
  539. }
  540. });
  541. });
  542. });
  543. })(jQuery);