// Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/ function $$(node) { var data = $(node).data("$$"); if (data) { return data; } else { data = {}; $(node).data("$$", data); return data; } }; (function($) { function Session() { function doLogin(name, password, callback) { $.couch.login({ name : name, password : password, success : function() { $.futon.session.sidebar(); callback(); }, error : function(code, error, reason) { $.futon.session.sidebar(); callback({name : "Error logging in: "+reason}); } }); }; function doSignup(name, password, callback, runLogin) { $.couch.signup({ name : name }, password, { success : function() { if (runLogin) { doLogin(name, password, callback); } else { callback(); } }, error : function(status, error, reason) { $.futon.session.sidebar(); if (error == "conflict") { callback({name : "Name '"+name+"' is taken"}); } else { callback({name : "Signup error: "+reason}); } } }); }; function validateUsernameAndPassword(data, callback) { if (!data.name || data.name.length == 0) { callback({name: "Please enter a name."}); return false; }; return validatePassword(data, callback); }; function validatePassword(data, callback) { if (!data.password || data.password.length == 0) { callback({password: "Please enter a password."}); return false; }; return true; }; function createAdmin() { $.showDialog("dialog/_create_admin.html", { submit: function(data, callback) { if (!validateUsernameAndPassword(data, callback)) return; $.couch.config({ success : function() { setTimeout(function() { doLogin(data.name, data.password, function(errors) { if(!$.isEmptyObject(errors)) { callback(errors); return; } doSignup(data.name, null, function(errors) { if (errors && errors.name && errors.name.indexOf && errors.name.indexOf("taken") == -1) { callback(errors); } else { callback(); } }, false); }); }, 200); } }, "admins", data.name, data.password); } }); return false; }; function login() { $.showDialog("dialog/_login.html", { submit: function(data, callback) { if (!validateUsernameAndPassword(data, callback)) return; doLogin(data.name, data.password, callback); } }); return false; }; function logout() { $.couch.logout({ success : function(resp) { $.futon.session.sidebar(); }, error: function(status, e, reason) { alert('An error occurred logging out: ' + reason); } }) }; function signup() { $.showDialog("dialog/_signup.html", { submit: function(data, callback) { if (!validateUsernameAndPassword(data, callback)) return; doSignup(data.name, data.password, callback, true); } }); return false; }; function changePassword () { var updateUserDoc = function(resp, data) { // regular users get their _users doc updated $.couch.db(resp.info.authentication_db).openDoc("org.couchdb.user:"+resp.userCtx.name, { error: function () { // ignore 404 location.reload(); }, success: function (user) { user.password = data.password; $.couch.db(resp.info.authentication_db).saveDoc(user, { success: function() { doLogin(user.name, user.password, function(errors) { if(!$.isEmptyObject(errors)) { callback(errors); return; } else { location.reload(); } }); } }); } }); } $.showDialog("dialog/_change_password.html", { submit: function(data, callback) { if (validatePassword(data, callback)) { if (data.password != data.verify_password) { callback({verify_password: "Passwords don't match."}); return false; } } else { return false; } $.couch.session({ error: function(status, e, reason) { alert('Could not get your session info: ' + reason); }, success: function (resp) { // admin users may have a config entry, change the password // there first. Update their user doc later, if it exists if (resp.userCtx.roles.indexOf("_admin") > -1) { // user is admin // check whether we have a config entry $.couch.config({ success : function (response) { // er do have a config entry $.couch.config({ success : function () { window.setTimeout(function() { doLogin(resp.userCtx.name, data.password, function(errors) { if(!$.isEmptyObject(errors)) { callback(errors); return; } else { location.reload(); } }); }, 1000); }, error: function(status, e, reason) { callback('Could not persist the new password: ' + reason); } }, "admins", resp.userCtx.name, data.password); } }, "admins", resp.userCtx.name); } else { // non-admin users, update their user doc updateUserDoc(resp, data); } } }); } }); return false; }; this.setupSidebar = function() { $("#userCtx .login").click(login); $("#userCtx .logout").click(logout); $("#userCtx .signup").click(signup); $("#userCtx .createadmin").click(createAdmin); $("#userCtx .changepass").click(changePassword); }; this.sidebar = function() { // get users db info? $("#userCtx span").hide(); $(".serverAdmin").attr('disabled', 'disabled'); $.couch.session({ success : function(r) { var userCtx = r.userCtx; var urlParts = location.search.substr(1).split("/"); var dbName = decodeURIComponent(urlParts.shift()); var dbNameRegExp = new RegExp("[^a-z0-9\_\$\(\)\+\/\-]", "g"); dbName = dbName.replace(dbNameRegExp, ""); $$("#userCtx").userCtx = userCtx; if (userCtx.name) { $("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)}); if ($.inArray("_admin", userCtx.roles) != -1) { $("#userCtx .loggedin").show(); $("#userCtx .loggedinadmin").show(); $(".serverAdmin").removeAttr('disabled'); // user is a server admin } else { $("#userCtx .loggedin").show(); if (dbName != "") { $.couch.db(dbName).getDbProperty("_security", { // check security roles for user admins success: function(resp) { var adminRoles = resp.admins.roles; if ($.inArray(userCtx.name, resp.admins.names)>=0) { // user is admin $(".userAdmin").removeAttr('disabled'); } else { for (var i=0; i=0) { // user has role that is an admin $(".userAdmin").removeAttr('disabled'); } } } } }); } } } else if ($.inArray("_admin", userCtx.roles) != -1) { $("#userCtx .adminparty").show(); $(".serverAdmin").removeAttr('disabled'); } else { $("#userCtx .loggedout").show(); }; } }) }; }; function Navigation() { var nav = this; this.loaded = false; this.eventHandlers = { load: [] }; this.ready = function(callback) { if (callback) { if (this.loaded) { callback.apply(this); } this.eventHandlers["load"].push(callback); } else { this.loaded = true; callbacks = this.eventHandlers["load"]; for (var i = 0; i < callbacks.length; i++) { callbacks[i].apply(this); } } } this.addDatabase = function(name) { var current = $.futon.storage.get("recent", ""); var recentDbs = current ? current.split(",") : []; if ($.inArray(name, recentDbs) == -1) { recentDbs.unshift(name); if (recentDbs.length > 10) recentDbs.length = 10; $.futon.storage.set("recent", recentDbs.join(",")); this.updateDatabases(); } } this.removeDatabase = function(name) { // remove database from recent databases list var current = $.futon.storage.get("recent", ""); var recentDbs = current ? current.split(",") : []; var recentIdx = $.inArray(name, recentDbs); if (recentIdx >= 0) { recentDbs.splice(recentIdx, 1); $.futon.storage.set("recent", recentDbs.join(",")); this.updateDatabases(); } } this.updateDatabases = function() { var selection = null; $("#dbs .selected a").each(function() { selection = [this.pathname, this.search]; }); $("#dbs").empty(); var recentDbs = $.futon.storage.get("recent").split(","); recentDbs.sort(); $.each(recentDbs, function(idx, name) { if (name) { name = encodeURIComponent(name); $("#dbs").append("
  • " + "" + "" + name + "
  • "); } }); if (selection) { this.updateSelection(selection[0], selection[1]); } $("#dbs button.remove").click(function() { nav.removeDatabase(this.value); return false; }); } this.updateSelection = function(path, queryString) { function fixupPath(path) { // hack for IE/Win return (path.charAt(0) != "/") ? ("/" + path) : path; } if (!path) { path = location.pathname; if (!queryString) { queryString = location.search; } } else if (!queryString) { queryString = ""; } var href = fixupPath(path + queryString); $("#nav li").removeClass("selected"); $("#nav li a").each(function() { if (fixupPath(this.pathname) + this.search != href) return; $(this).parent("li").addClass("selected").parents("li").addClass("selected"); }); } this.toggle = function(speed) { if (speed === undefined) { speed = 500; } var sidebar = $("#sidebar").stop(true, true); var hidden = !$(sidebar).is(".hidden"); $("#wrap").animate({ marginRight: hidden ? 0 : 210 }, speed, function() { $(document.body).toggleClass("fullwidth", hidden); }); sidebar.toggleClass("hidden").animate({ width: hidden ? 26 : 210, height: hidden ? $("h1").outerHeight() - 1 : "100%", right: hidden ? 0 : -210 }, speed).children(":not(#sidebar-toggle)").animate({ opacity: "toggle" }, speed); $("h1").animate({marginRight: hidden ? 26 : 0}, speed); $("#sidebar-toggle") .attr("title", hidden ? "Show Sidebar" : "Hide Sidebar"); $.futon.storage.set("sidebar", hidden ? "hidden" : "show"); }; } function Storage() { var storage = this; this.decls = {}; this.declare = function(name, options) { this.decls[name] = $.extend({}, { scope: "window", defaultValue: null, prefix: "" }, options || {}); } this.declareWithPrefix = function(prefix, decls) { for (var name in decls) { var options = decls[name]; options.prefix = prefix; storage.declare(name, options); } } this.del = function(name) { lookup(name, function(decl) { handlers[decl.scope].del(decl.prefix + name); }); } this.get = function(name, defaultValue) { return lookup(name, function(decl) { var value = handlers[decl.scope].get(decl.prefix + name); if (value !== undefined) { return value; } if (defaultValue !== undefined) { return defaultValue; } return decl.defaultValue; }); } this.set = function(name, value) { lookup(name, function(decl) { if (value == decl.defaultValue) { handlers[decl.scope].del(decl.prefix + name); } else { handlers[decl.scope].set(decl.prefix + name, value); } }); } function lookup(name, callback) { var decl = storage.decls[name]; if (decl === undefined) { return decl; } return callback(decl); } function windowName() { try { return JSON.parse(window.name || "{}"); } catch (e) { return {}; } } // add suffix to cookie names to be able to separate between ports var cookiePrefix = location.port + "_"; var handlers = { "cookie": { get: function(name) { var nameEq = cookiePrefix + name + "="; var parts = document.cookie.split(';'); for (var i = 0; i < parts.length; i++) { var part = parts[i].replace(/^\s+/, ""); if (part.indexOf(nameEq) == 0) { return unescape(part.substring(nameEq.length, part.length)); } } }, set: function(name, value) { var date = new Date(); date.setTime(date.getTime() + 14*24*60*60*1000); // two weeks document.cookie = cookiePrefix + name + "=" + escape(value) + "; expires=" + date.toGMTString(); }, del: function(name) { var date = new Date(); date.setTime(date.getTime() - 24*60*60*1000); // yesterday document.cookie = cookiePrefix + name + "=" + "; expires=" + date.toGMTString(); } }, "window": { get: function(name) { return windowName()[name]; }, set: function(name, value) { var obj = windowName(); obj[name] = value || null; window.name = JSON.stringify(obj); }, del: function(name) { var obj = windowName(); delete obj[name]; window.name = JSON.stringify(obj); } } }; } $.couch.urlPrefix = ".."; $.futon = $.futon || {}; $.extend($.futon, { navigation: new Navigation(), session : new Session(), storage: new Storage() }); $.fn.addPlaceholder = function() { if (this[0] && "placeholder" in document.createElement("input")) { return; // found native placeholder support } return this.live('focusin', function() { var input = $(this); if (input.val() === input.attr("placeholder")) { input.removeClass("placeholder").val(""); } }).live("focusout", function() { var input = $(this); if (input.val() === "") { input.val(input.attr("placeholder")).addClass("placeholder"); } }).trigger("focusout"); } $.fn.enableTabInsertion = function(chars) { chars = chars || "\t"; var width = chars.length; return this.keydown(function(evt) { if (evt.keyCode == 9) { var v = this.value; var start = this.selectionStart; var scrollTop = this.scrollTop; if (start !== undefined) { this.value = v.slice(0, start) + chars + v.slice(start); this.selectionStart = this.selectionEnd = start + width; } else { document.selection.createRange().text = chars; this.caretPos += width; } return false; } }); } $(document) .ajaxStart(function() { $(this.body).addClass("loading"); }) .ajaxStop(function() { $(this.body).removeClass("loading"); }); $.futon.storage.declare("sidebar", {scope: "cookie", defaultValue: "show"}); $.futon.storage.declare("recent", {scope: "cookie", defaultValue: ""}); $(function() { document.title = "Apache CouchDB - Futon: " + document.title; if ($.futon.storage.get("sidebar") == "hidden") { // doing this as early as possible prevents flickering $(document.body).addClass("fullwidth"); } $("input[placeholder]").addPlaceholder(); $.get("_sidebar.html", function(resp) { $("#wrap").append(resp) .find("#sidebar-toggle").click(function(e) { $.futon.navigation.toggle(e.shiftKey ? 2500 : 500); return false; }); if ($.futon.storage.get("sidebar") == "hidden") { $.futon.navigation.toggle(0); } $.futon.navigation.updateDatabases(); $.futon.navigation.updateSelection(); $.futon.navigation.ready(); $.futon.session.setupSidebar(); $.futon.session.sidebar(); $.couch.info({ success: function(info, status) { $("#version").text(info.version); } }); }); }); })(jQuery);