blob: 07238390c651b01aaec69dccd1f06e3141cb02a6 [file] [log] [blame]
// 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<userCtx.roles.length; i++) {
if ($.inArray(userCtx.roles[i], resp.admins.roles)>=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("<li>" +
"<button class='remove' title='Remove from list' value='" + name + "'></button>" +
"<a href='database.html?" + name + "' title='" + name + "'>" + decodeURIComponent(name) +
"</a></li>");
}
});
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);