Dynamic who page using Apache Phonebook javascript link to Whimsy
diff --git a/source/javascripts/phonebook.js b/source/javascripts/phonebook.js
new file mode 100644
index 0000000..74a4b31
--- /dev/null
+++ b/source/javascripts/phonebook.js
@@ -0,0 +1,917 @@
+var pmcs = [] // array of PMC names (excludes non-PMC committees)
+var people = {} // public_ldap_people.json
+var ldapauth = {} // public_ldap_authgroups.json
+// TODO don't rely on ldap_groups containing PMC :members groups
+var ldapgroups = {} // public_ldap_groups.json
+var ldapservices = {} // public_ldap_services.json
+var ldapprojects = {} // public_ldap_projects.json
+
+var members = {} // copy of member-info.json
+var committees = {} // copy of committee-info.json (plus details for 'member' dummy PMC)
+var iclainfo = {} // copy of icla-info.json (committers only)
+var podlings = {} // public_ldap_projects.json where podling is 'current'
+
+var info = {} // copies of json info
+
+// Constants for query types.
+// Do NOT change the values once established, as they are part of the public API
+// For example they may be used in projects.a.o and reporter.a.o
+// The values are used for matching HTTP queries and linkifying lists (to generate a valid HTML link)
+
+var Q_USER = 'user' // search users
+var Q_PROJECT = 'project' // search PMC names
+var Q_UID = 'uid' // availid, exact match
+var Q_PMC = 'pmc' // PMC, exact match
+var Q_UNIX = 'unix' // LDAP group
+var Q_CTTE = 'ctte' // LDAP group
+var Q_SERVICE = 'service' // LDAP group
+var Q_PODLING = 'podling' // podling (non-LDAP group)
+var Q_AUTH = 'auth' // podling (LDAP auth group)
+
+
+// Not intended for general use; may change at any time
+var Q_DEBUG = 'debug' // print some debug info
+
+// compatibility shim for IE8 and other older browsers
+if (!Date.now) {
+ Date.now = function() {
+ return new Date().getTime();
+ }
+}
+
+// This is faster than parseInt, and it's more obvious why it is being done
+function toInt(number) {
+ return number | 0 //
+}
+
+var fetchCount = 0;
+// Fetch an array of URLs, each with their description and own callback plus a final callback
+// Used to fetch everything before rendering a page that relies on multiple JSON sources.
+
+
+function getAsyncJSONArray(urls, finalCallback) {
+ var obj = document.getElementById('progress');
+ if (fetchCount == 0) {
+ fetchCount = urls.length;
+ }
+
+ if (urls.length > 0) {
+ var a = urls.shift();
+ var URL = a[0];
+ var desc = a[1];
+ var cb = a[2];
+ var xmlHttp = null;
+ if (window.XMLHttpRequest) {
+ xmlHttp = new XMLHttpRequest();
+ } else {
+ xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
+ }
+
+ if (obj) {
+ obj.innerHTML = "loading file #" + (fetchCount - urls.length) + " / " + fetchCount + "<br>" + desc
+ }
+ var start = Date.now()
+ xmlHttp.open("GET", URL, true);
+ xmlHttp.onreadystatechange = function(state) {
+ if (xmlHttp.readyState == 4) {
+ if (cb) {
+ if (xmlHttp.status == 200) {
+ elapsed = Date.now() - start
+ cb(JSON.parse(xmlHttp.responseText));
+ // must be done after as cb creates the hash
+ info[desc]['elapsed'] = elapsed
+ } else {
+ cb({});
+ alert("Error: '" + xmlHttp.statusText + "' while loading " + URL)
+ }
+ }
+ getAsyncJSONArray(urls, finalCallback);
+ }
+ }
+ xmlHttp.send(null);
+ } else {
+ if (obj) {
+ obj.innerHTML = "building page content..."
+ }
+ finalCallback();
+ }
+}
+
+// get list of projects on which uid is a committer
+function getProjectCommittership(uid) {
+ var cl = []
+ for (var i in ldapprojects) {
+ if (ldapprojects[i].pmc && ldapprojects[i].members.indexOf(uid) > -1) {
+ cl.push(i)
+ }
+ }
+ return cl
+}
+
+//get list of projects on which uid is an owner (member karma)
+function getProjectOwnership(uid) {
+ var cl = []
+ for (var i in ldapprojects) {
+ if (ldapprojects[i].pmc && ldapprojects[i].owners.indexOf(uid) > -1) {
+ cl.push(i)
+ }
+ }
+ return cl
+}
+
+// Get the roster from a json group
+// returns: all the keys where the uid is [not] a member
+function getRoster(json, uid, notIn) {
+ var cl = []
+ for (var i in json) {
+ if (json[i].roster.indexOf(uid) > -1) {
+ if (typeof notIn === 'undefined') {
+ cl.push(i)
+ } else {
+ if (notIn.indexOf(i) == -1) {
+ cl.push(i)
+ }
+ }
+ }
+ }
+ return cl
+}
+
+// get data from committee-info for a person
+// return [list of pmcs, list of chairs]
+function getCommitteeRoles(uid) {
+ var pl = []
+ var ch = []
+ for (var i in committees) {
+ // Only list actual PMCs
+ if (committees[i].pmc && uid in committees[i].roster) {
+ pl.push(i)
+ }
+ //var chair = committees[i].chair // might not be one (eg members)
+ //if (chair && uid in committees[i].chair) {
+ ch.push(i)
+ //}
+ }
+ return [pl, ch]
+}
+
+function getCommitterName(uid) {
+ var noicla = {
+ 'andrei': '(Andrei Zmievski)',
+ 'pcs': '(Paul Sutton)',
+ 'rasmus': '(Rasmus Lerdorf)'
+ }
+ var name
+ if (uid in people) { // it's possible for a list to contain a uid that is not in people (e.g. andrei in member)
+ name = people[uid].name
+ }
+ if (!name) {
+ name = iclainfo[uid]
+ }
+ if (!name) { // try the backup specials
+ name = noicla[uid]
+ }
+ return name
+}
+
+// Linkify list of group names by adding the appropriate ?type= href
+
+function linkifyList(type, names) {
+ var text = ''
+ var index, len, i
+ names.sort()
+ for (i = 0, len = names.length; i < len; ++i) {
+ if (i > 0) {
+ text = text + ", "
+ }
+ text = text + "<a href='?" + type + "=" + names[i] + "'>" + names[i] + "</a>"
+ }
+ return text
+}
+
+// Linkify user ids
+
+function userList(ua) {
+ var text = ''
+ var index, len
+ ua.sort()
+ for (index = 0, len = ua.length; index < len; ++index) {
+ if (index > 0) {
+ text = text + ", "
+ }
+ text = text + hiliteMember(ua[index])
+ }
+ return text
+}
+
+//Linkify URLs
+
+function linkifyURLs(ua) {
+ var text = ''
+ var index, len
+ ua.sort()
+ for (index = 0, len = ua.length; index < len; ++index) {
+ if (index > 0) {
+ text = text + ", "
+ }
+ text = text + "<a target='_blank' href='" + ua[index] + "'>" + ua[index] + "</a>"
+ }
+ return text
+}
+
+function showCommitter(obj, uid) {
+ var details = document.getElementById('details_committer_' + uid)
+ if (!details) {
+ details = document.createElement('p')
+ details.setAttribute("id", 'details_committer_' + uid)
+ var cl = getProjectCommittership(uid) // committer(in :members) of these LDAP PMC projects
+ var roles = getCommitteeRoles(uid)
+ var cttees = getProjectOwnership(uid) // member(in :owners) of these LDAP PMC projects
+ var pl = roles[0] // pmc membership
+ var ch = roles[1] // chairs
+ if (isNologin(uid)) {
+ details.innerHTML += "<b>Login is currently disabled</b><br/><br/>"
+ }
+ if (isMember(uid)) {
+ details.innerHTML += "<i>Foundation member</i><br/><br/>"
+ }
+ if (ch.length > 0) {
+ details.innerHTML += "<b>Chair of:</b> " + linkifyList(Q_PMC, ch)
+ if (!isChair(uid)) {
+ details.innerHTML += " <b>Not a member of pmc-chairs!</b>"
+ }
+ details.innerHTML += "<br/><br/>"
+ }
+ var purls = urls(uid)
+ if (purls.length > 0) {
+ details.innerHTML += "<b>Personal URLs:</b> " + linkifyURLs(purls) + "<br/><br/>"
+ }
+ if (cl.length > 0) {
+ details.innerHTML += "<b>Committer on:</b> " + linkifyList(Q_UNIX, cl) + "<br/><br/>"
+ }
+ var nc = [] // On PMC but not in LDAP unix
+ var nl = [] // On PMC but not in LDAP committee
+ var np = [] // Not in PMC even though in LDAP committee
+ var nu = [] // In LDAP committee but not in LDAP unix
+ var pn;
+ if (pl.length > 0) {
+ details.innerHTML += "<b>PMC member of:</b> " + linkifyList(Q_PMC, pl) + "<br/><br/>"
+ for (p in pl) {
+ pn = pl[p]
+ // There is an LDAP PMC group but the uid is not in the committer(:members) group
+ if (isProjectPMC(pn) && cl.indexOf(pn) < 0) {
+ nc.push(pn)
+ }
+ // There is an LDAP PMC group but the uid is not in the committee(:owners) group
+ if (isProjectPMC(pn) && cttees.indexOf(pn) < 0) {
+ nl.push(pn)
+ }
+ }
+ }
+
+ if (cttees.length > 0) {
+ for (p in cttees) {
+ pn = cttees[p]
+ // name is a PMC but uid is not on the PMC
+ if (isPMC(pn) && pl.indexOf(pn) < 0) {
+ np.push(pn)
+ }
+ // name has LDAP project entry but uid is not in the committer (member) list
+ if (isProjectPMC(pn) && cl.indexOf(pn) < 0) {
+ nu.push(pn)
+ }
+ }
+ details.innerHTML += "<b>LDAP committee group membership:</b> " + linkifyList(Q_CTTE, cttees) + "<br/><br/>"
+ }
+
+ var services = getRoster(ldapservices, uid)
+ if (services.length > 0) {
+ details.innerHTML += "<b>Service group membership:</b> " + linkifyList(Q_SERVICE, services) + "<br/><br/>"
+ }
+ var auths = getRoster(ldapauth, uid)
+ if (auths.length > 0) {
+ details.innerHTML += "<b>Auth group membership:</b> " + linkifyList(Q_AUTH, auths) + "<br/><br/>"
+ }
+ var pods = getRoster(podlings, uid)
+ if (pods.length > 0) {
+ details.innerHTML += "<b>Podling membership:</b> " + linkifyList(Q_PODLING, pods) + "<br/><br/>"
+ }
+
+ // Note any discrepancies
+ if (np.length > 0) {
+ details.innerHTML += "<span class='error'>In LDAP committee group, but <b>not a PMC member</b>:</span> " + linkifyList(Q_CTTE, np) + "<br/><br/>"
+ }
+ if (nc.length > 0) {
+ details.innerHTML += "<span class='error'>On PMC, but not a member of the committer group:</span> " + linkifyList(Q_PMC, nc) + "<br/><br/>"
+ }
+ if (nl.length > 0) {
+ details.innerHTML += "<span class='error'>On PMC, but not member of the LDAP committee group:</span> " + linkifyList(Q_CTTE, nl) + "<br/><br/>"
+ }
+ if (nu.length > 0) {
+ details.innerHTML += "<span class='error'>In LDAP committee group but not a member of the committer group:</span> " + linkifyList(Q_UNIX, nu) + "<br/><br/>"
+ }
+ obj.appendChild(details)
+ } else {
+ obj.removeChild(details)
+ }
+}
+
+function hoverCommitter(parent, uid) {
+ var div = document.getElementById('hoverbar')
+
+ // If the datepicker object doesn't exist, spawn it
+ if (!div) {
+ div = document.createElement('div')
+ document.body.appendChild(div)
+ div.setAttribute("id", "hoverbar")
+ div.style.position = "fixed"
+ div.style.width = "400px"
+ div.style.background = "linear-gradient(to bottom, rgba(254,255,232,1) 0%,rgba(214,219,191,1) 100%)"
+ div.style.borderRadius = "4px"
+ div.style.border = "1px solid #333"
+ div.style.zIndex = "9999"
+ }
+
+ // Reset the contents of the datepicker object
+ div.innerHTML = ""
+
+ var bb = parent.getBoundingClientRect()
+ div.style.top = (bb.bottom + 24) + "px"
+ div.style.left = (bb.left + 32) + "px"
+
+ if (uid) {
+ div.style.display = "block"
+ div.innerHTML = "<h4>" + getCommitterName(uid) + "</h4>"
+ var cl = getProjectCommittership(uid)
+ var roles = getCommitteeRoles(uid)
+ var pl = roles[0]
+ var ch = roles[1]
+ if (isMember(uid) == true) {
+ div.innerHTML += "<i>Foundation member</i><br/><br/>"
+ }
+ if (isNologin(uid)) {
+ div.innerHTML += "<b>Login is currently disabled</b><br/><br/>"
+ }
+ if (ch.length > 0) {
+ ch.sort()
+ div.innerHTML += "<b>Chair of:</b> " + ch.join(", ")
+ if (!isChair(uid)) {
+ div.innerHTML += " <b>Not a member of pmc-chairs!</b>"
+ }
+ div.innerHTML += "<br/><br/>"
+ }
+ if (cl.length > 0) {
+ cl.sort()
+ div.innerHTML += "<b>Committer on:</b> " + cl.join(", ") + "<br/><br/>"
+ }
+ var nc = []
+ if (pl.length > 0) {
+ pl.sort()
+ div.innerHTML += "<b>PMC member of:</b> " + pl.join(", ") + "<br/><br/>"
+ for (p in pl) {
+ var pn = pl[p]
+ if (pn != 'member' && cl.indexOf(pn) < 0) {
+ nc.push(pn)
+ }
+ }
+ }
+ if (nc.length > 0) {
+ div.innerHTML += "<i>On PMC, but not a Committer on:</i> " + nc.join(", ") + "<br/><br/>"
+ }
+
+
+ } else {
+ div.style.display = "none"
+ }
+}
+
+function isNologin(uid) {
+ return !(uid in people) || people[uid].noLogin
+}
+
+function isMember(uid) {
+ return members['members'].indexOf(uid) > -1
+}
+
+function isChair(uid) {
+ return ldapservices['pmc-chairs'].roster.indexOf(uid) > -1
+}
+
+function urls(uid) {
+ return people[uid].urls || []
+}
+
+// Is a PMC according to committee-info
+function isPMC(name) {
+ return pmcs.indexOf(name) >= 0;
+}
+
+//Is a PMC according to ldap_projects
+function isProjectPMC(name) {
+ return name in ldapprojects && ldapprojects[name].pmc
+}
+
+function linkifyUid(uid) {
+ if (isNologin(uid)) {
+ return uid
+ }
+ return uid
+}
+
+function hiliteMember(uid) {
+ if (isMember(uid)) {
+ return linkifyUid(uid)
+ } else {
+ return linkifyUid(uid)
+ }
+}
+
+function getChair(uid) {
+ var chair = committees[uid].chair
+ if (chair) {
+ for (var x in chair) {
+ return chair[x].name
+ }
+ }
+ return null
+}
+
+function showProject(obj, prj) {
+ var details = document.getElementById('details_project_' + prj)
+ if (!details) {
+ details = document.createElement('p')
+ details.setAttribute("id", 'details_project_' + prj)
+ var desc = committees[prj].description
+ if (!desc) {
+ desc = 'TBA (please ensure that <a href="http://www.apache.org/index.html#projects-list">the projects list</a> is updated)'
+ }
+ var chair = getChair(prj)
+ if (chair) {
+ details.innerHTML += "<b>Chair:</b> " + chair + "<br/><br/>"
+ }
+ var url = committees[prj].site
+ var cl
+ var clExists = false // Does the unix group exist?
+ try {
+ cl = ldapgroups[prj].roster.slice()
+ clExists = true
+ } catch (err) { // Allow for missing Unix group
+ cl = []
+ }
+ var pl = []
+ var pmc = committees[prj]
+
+ var pmcnoctte = [] // on pmc but not in LDAP committee
+ var cttenopmc = [] // In LDAP ctte but not on PMC
+ var ldappmc = []
+ var ctteeExists = false
+ if (isProjectPMC(prj)) { // may not exist, e.g. for 'member' PMC and if group has yet to be created
+ ldappmc = ldapprojects[prj].owners
+ ctteeExists = true
+ }
+ var pmcnounix = [] // on PMC but not in LDAP unix group
+ var cttenounix = [] // In LDAP ctte but not in LDAP unix
+ if (pmc) {
+ for (var c in pmc.roster) {
+ pl.push(c)
+ }
+ for (var i in ldappmc) {
+ if (!(ldappmc[i] in pmc.roster)) {
+ cttenopmc.push(ldappmc[i])
+ }
+ }
+ }
+ cl.sort()
+ pl.sort()
+
+ // Must use cl before it is re-used to hold the entries
+ if (clExists && prj != 'member') { // does not exist for 'member' PMC
+ for (var i in ldappmc) {
+ var id = ldappmc[i]
+ if (cl.indexOf(id) < 0) { // in LDAP cttee but not in LDAP unix
+ cttenounix.push(id)
+ }
+ }
+ }
+
+ for (var i in pl) {
+ var id = pl[i]
+ pl[i] = "<tr><td><b>" + getCommitterName(pl[i]) + "</b></td><td>(" + hiliteMember(pl[i]) + ")</td></tr>"
+ if (clExists && cl.indexOf(id) < 0) { // On PMC but not in LDAP unix group
+ pmcnounix.push(id)
+ }
+ if (prj != 'member' && ldappmc && ldappmc.indexOf(id) < 0) { // in PMC but not in LDAP committee (does not apply to member)
+ pmcnoctte.push(id)
+ }
+ }
+
+ for (var i in cl) {
+ cl[i] = "<tr><td><b>" + getCommitterName(cl[i]) + "</b></td><td>(" + hiliteMember(cl[i]) + ")</td></tr>"
+ }
+
+ if (pl.length > 0) {
+ if (prj == 'member') {
+ details.innerHTML += "<b>ASF members</b><br><br><table>" + pl.join("\n") + "</table><br/>"
+ } else {
+ details.innerHTML += "<h4>PMC members (also in the committer group): " + pl.length + "</h4><table>" + pl.join("\n") + "</table><br/>"
+ }
+ }
+
+ if (cl && cl.length > 0) {
+ details.innerHTML += "<h4>Committers: " + cl.length + "</h4><table>" + cl.join("\n") + "</table>"
+ if (podlings[prj]) {
+ details.innerHTML += "<span class='error'>WARNING: <a href='?podling=" + prj + "'>" + prj + " podling group</a> also exists - this can cause authentication issues</span><br/><br/>"
+ }
+ } else {
+ if (!clExists) {
+ details.innerHTML += "<span class='error'>LDAP unix group not present!</span><br/><br/>"
+ }
+ }
+
+ if (pmcnoctte.length) {
+ if (ctteeExists) {
+ details.innerHTML += "<span class='error'>PMC members not in LDAP committee group:</span> " + userList(pmcnoctte) + "<br/><br/>"
+ } else {
+ details.innerHTML += "<span class='error'>LDAP committee group not present!</span><br/><br/>"
+ }
+ }
+ if (pmcnounix.length) {
+ if (prj == 'member') {
+ details.innerHTML += "<span class='error'>ASF members not in committers(unix) group:</span> " + userList(pmcnounix) + "<br/><br/>"
+ } else {
+ details.innerHTML += "<span class='error'>PMC members not in committers(unix) group:</span> " + userList(pmcnounix) + "<br/><br/>"
+ }
+ }
+ if (cttenounix.length) {
+ details.innerHTML += "<span class='error'>LDAP committee group members not in committers(unix) group:</span> " + userList(cttenounix) + "<br/><br/>"
+ }
+ if (cttenopmc.length) {
+ details.innerHTML += "<span class='error'>LDAP committee group members not on PMC:</span> " + userList(cttenopmc) + "<br/><br/>"
+ }
+
+
+ obj.appendChild(details)
+ } else {
+ obj.removeChild(details)
+ }
+}
+
+// Generic group display function
+// attr is either missing or 'owners' or 'members'
+function showJsonRoster(obj, type, json, name, attr, checkUnix) {
+ var id = 'details_' + type + '_' + name
+ var details = document.getElementById(id)
+ if (!details) {
+ details = document.createElement('p')
+ details.setAttribute("id", id)
+ var podtype = json[name]['podling']
+ if (podtype) {
+ details.innerHTML += "<b>podling:</b> " + podtype + "<br><br>"
+ }
+ var cl;
+ if (attr == 'owners') {
+ cl = json[name].owners.slice()
+ } else if (attr == 'members') {
+ cl = json[name].members.slice()
+ } else {
+ cl = json[name].roster.slice()
+ }
+ cl.sort()
+ for (var i in cl) {
+ var uid = cl[i]
+ cl[i] = "<tr><td onmouseover='hoverCommitter(this, \"" + uid + "\");' onmouseout='hoverCommitter(this, null);'><kbd>" + hiliteMember(uid) + "</kbd></td><td>" + getCommitterName(uid) + "</td>"
+ if (checkUnix) { // check against Unix group
+ if (ldapgroups[name]) { // make sure group exists!
+ if (ldapgroups[name].roster.indexOf(uid) > -1) {
+ cl[i] += "<td> </td>"
+ } else {
+ cl[i] += "<td> N.B. not found in corresponding Unix group</td>"
+ }
+ }
+ }
+ cl[i] += "</tr>"
+ }
+
+ if (cl && cl.length > 0) {
+ details.innerHTML += "<b>Roster:</b><br><br><table>" + cl.join("\n") + "</table><br/>"
+ }
+ obj.appendChild(details)
+ } else {
+ obj.removeChild(details)
+ }
+}
+
+// Show a single Service group
+function showServiceRoster(obj, name) {
+ showJsonRoster(obj, 'service', ldapservices, name)
+}
+
+// Show a single Auth group
+function showAuthRoster(obj, name) {
+ showJsonRoster(obj, 'auth', ldapauth, name)
+}
+
+function showPodlingRoster(obj, name) {
+ showJsonRoster(obj, 'podling', podlings, name)
+}
+
+// Show an LDAP Unix group
+
+function showGroup(obj, name) {
+ showJsonRoster(obj, 'group', ldapgroups, name)
+}
+
+// Show an LDAP Commiteee group
+
+function showCommittee(obj, name) {
+ showJsonRoster(obj, 'ctte', ldapprojects, name, 'owners', true)
+}
+
+function searchProjects(keyword, open) {
+ var obj = document.getElementById('phonebook')
+ if (keyword != '') {
+ obj.innerHTML = ""
+ } else {
+ obj.innerHTML = ''
+ }
+ for (var i in pmcs) {
+ var pmc = pmcs[i]
+ if (pmc.search(keyword.toLowerCase()) != -1) {
+ var ppmc = committees[pmc].display_name
+ obj.innerHTML += "<div id='project_" + pmc + "' class='group'><h3>Apache " + ppmc + "</h3></div>"
+ if (open) {
+ showProject(document.getElementById('project_' + pmc), pmc)
+ }
+ }
+ }
+}
+
+function searchService(keyword, open) {
+ var obj = document.getElementById('phonebook')
+ if (keyword != '') {
+ obj.innerHTML = "<h3>Search results:</h3><hr/>"
+ } else {
+ obj.innerHTML = ''
+ }
+ for (var srv in ldapservices) {
+ if (srv.search(keyword.toLowerCase()) != -1) {
+ obj.innerHTML += "<div id='service_" + srv + "' class='group'><h3 onclick=\"showServiceRoster(this.parentNode, '" + srv + "');\">" + srv + "</h3></div>"
+ if (open) {
+ showServiceRoster(document.getElementById('service_' + srv), srv)
+ }
+ }
+ }
+}
+
+function searchAuth(keyword, open) {
+ var obj = document.getElementById('phonebook')
+ if (keyword != '') {
+ obj.innerHTML = "<h3>Search results:</h3><hr/>"
+ } else {
+ obj.innerHTML = ''
+ }
+ for (var auth in ldapauth) {
+ if (auth.search(keyword.toLowerCase()) != -1) {
+ obj.innerHTML += "<div id='auth_" + auth + "' class='group'><h3 onclick=\"showAuthRoster(this.parentNode, '" + auth + "');\">" + auth + "</h3></div>"
+ if (open) {
+ showauthRoster(document.getElementById('auth_' + auth), auth)
+ }
+ }
+ }
+}
+
+// Show a single PMC
+
+function showPMC(pmc) {
+ var obj = document.getElementById('phonebook')
+ if (pmc in committees) {
+ var ppmc = committees[pmc].display_name
+ obj.innerHTML = "<div id='project_" + pmc + "' class='group'><h3 onclick=\"showProject(this.parentNode, '" + pmc + "');\">Apache " + ppmc + "</h3></div>"
+ showProject(document.getElementById('project_' + pmc), pmc)
+ } else {
+ obj.innerHTML = "<h3>Could not find PMC: '" + pmc + "'</h3>"
+ }
+}
+
+// Show a single Unix Group
+
+function showUNIX(unix) {
+ var obj = document.getElementById('phonebook')
+ var id = 'group_' + unix
+ if (unix in ldapgroups) {
+ obj.innerHTML = "<div id='" + id + "' class='group'><h3 onclick=\"showGroup(this.parentNode, '" + unix + "');\">" + unix + " (LDAP unix group)</h3></div>"
+ showGroup(document.getElementById(id), unix)
+ } else {
+ obj.innerHTML = "<h3>Could not find unix group: '" + unix + "'</h3>"
+ }
+}
+
+// Show a single Committee group
+
+function showCTTE(ctte) {
+ var obj = document.getElementById('phonebook')
+ var id = 'ctte_' + ctte
+ if (ctte in ldapprojects && ldapprojects[ctte].pmc) {
+ obj.innerHTML = "<div id='" + id + "' class='group'><h3 onclick=\"showCommittee(this.parentNode, '" + ctte + "');\">" + ctte + " (LDAP committee group)</h3></div>"
+ showCommittee(document.getElementById(id), ctte)
+ } else {
+ obj.innerHTML = "<h3>Could not find committee group: '" + ctte + "'</h3>"
+ }
+}
+
+function showSVC(name) {
+ var obj = document.getElementById('phonebook')
+ var id = 'service_' + name
+ if (name in ldapservices) {
+ obj.innerHTML = "<div id='" + id + "' class='group'><h3 onclick=\"showServiceRoster(this.parentNode, '" + name + "');\">" + name + " (LDAP service group)</h3></div>"
+ showServiceRoster(document.getElementById(id), name)
+ } else {
+ obj.innerHTML = "<h3>Could not find the service group: '" + name + "'</h3>"
+ }
+}
+
+function showAUTH(name) {
+ var obj = document.getElementById('phonebook')
+ var id = 'auth_' + name
+ if (name in ldapauth) {
+ obj.innerHTML = "<div id='" + id + "' class='group'><h3 onclick=\"showAuthRoster(this.parentNode, '" + name + "');\">" + name + " (LDAP auth group)</h3></div>"
+ showAuthRoster(document.getElementById(id), name)
+ } else {
+ obj.innerHTML = "<h3>Could not find the auth group: '" + name + "'</h3>"
+ }
+}
+
+function showPOD(name) {
+ var obj = document.getElementById('phonebook')
+ var id = 'podling_' + name
+ if (name in podlings) {
+ obj.innerHTML = "<div id='" + id + "' class='group'><h3 onclick=\"showPodlingRoster(this.parentNode, '" + name + "');\">" + name + " (podling)</h3></div>"
+ showPodlingRoster(document.getElementById(id), name)
+ } else {
+ obj.innerHTML = "<h3>Could not find the podling: '" + name + "'</h3>"
+ }
+}
+
+function searchPodlings(keyword, open) {
+ var obj = document.getElementById('phonebook')
+ obj.innerHTML = "<h3>Search results:</h3><hr/>"
+ for (var name in podlings) {
+ if (name.search(keyword.toLowerCase()) != -1) {
+ var id = 'podling_' + name
+ obj.innerHTML += "<div id='" + id + "' class='group'><h3 onclick=\"showPodlingRoster(this.parentNode, '" + name + "');\">" + name + " (podling)</h3></div>"
+ }
+ }
+}
+
+function showDBG(name) {
+ var obj = document.getElementById('phonebook')
+ if (name == 'info') {
+ obj.innerHTML = "<h3>info</h3>"
+ obj.innerHTML += "<pre>" + JSON.stringify(info, null, 1) + "</pre>"
+
+ } else {
+ obj.innerHTML = "<h3>Unknown debug name: '" + name + "'</h3>"
+ }
+}
+
+// Show a single User
+
+function showUid(uid) {
+ var obj = document.getElementById('phonebook')
+ if (uid in people) {
+ var name = getCommitterName(uid)
+ obj.innerHTML = "<div class='group' id='committer_" + uid + "'><h4 onclick=\"showCommitter(this.parentNode, '" + uid + "');\">" + name + " (<kbd>" + uid + "</kbd>)</h4></div>"
+ showCommitter(document.getElementById('committer_' + uid), uid)
+ } else {
+ obj.innerHTML = "<h3>Could not find user id: '" + uid + "'</h3>"
+ }
+}
+
+function showError(error) {
+ var obj = document.getElementById('phonebook')
+ if (typeof(error) === 'string') {
+ obj.innerHTML = "<h3>Error detected</h3>"
+ obj.innerHTML += error
+ } else { // assume it's an error object
+ obj.innerHTML = "<h3>Javascript Error detected</h3>"
+ obj.innerHTML += "<hr/>"
+ obj.innerHTML += "<pre>" + error.message + "</pre>"
+ obj.innerHTML += "<pre>" + error.stack + "</pre>"
+ obj.innerHTML += "<hr/>"
+ }
+}
+
+function searchCommitters(keyword, open) {
+ if (keyword.length < 2) {
+ return
+ }
+ var n = 0
+ var obj = document.getElementById('phonebook')
+ obj.innerHTML = ""
+ for (var uid in people) {
+ if (!people[uid].noLogin) { // don't display disabled logins
+ var name = getCommitterName(uid)
+ if (uid.search(keyword.toLowerCase()) != -1 || name.toLowerCase().search(keyword.toLowerCase()) != -1) {
+ n++
+ if (n > 50) {
+ return;
+ }
+ obj.innerHTML += "<div class='group' id='committer_" + uid + "'><h4 onclick=\"showCommitter(this.parentNode, '" + uid + "');\">" + name +
+ " (<kbd>" + uid + "</kbd>) <a title='Link to committer details' href='phonebook.html?uid=" + uid + "'>•</a></h4></div>"
+ if (open) {
+ showCommitter(document.getElementById('committer_' + uid), uid)
+ }
+ }
+ }
+ }
+}
+
+function saveInfo(json, name) {
+ info[name] = {}
+ try {
+ info[name]['lastTimestamp'] = json.lastTimestamp
+ } catch (err) {} // ignored
+ try {
+ info[name]['lastCreateTimestamp'] = json.lastCreateTimestamp
+ } catch (err) {} // ignored
+ try {
+ info[name]['last_updated'] = json.last_updated
+ } catch (err) {} // ignored
+}
+
+function preRender() {
+ getAsyncJSONArray([
+ ['https://whimsy.apache.org/public/public_ldap_projects.json', "projects", function(json) {
+ ldapprojects = json.projects;
+ saveInfo(json, 'projects');
+ }],
+ ['https://whimsy.apache.org/public/member-info.json', "members", function(json) {
+ members = json;
+ saveInfo(json, 'members');
+ }],
+ ['https://whimsy.apache.org/public/public_ldap_people.json', "people", function(json) {
+ people = json.people;
+ saveInfo(json, 'people');
+ }],
+ ['https://whimsy.apache.org/public/committee-info.json', "committees", function(json) {
+ committees = json.committees;
+ saveInfo(json, 'committees');
+ }],
+ ['https://whimsy.apache.org/public/icla-info.json', "iclainfo", function(json) {
+ iclainfo = json.committers;
+ saveInfo(json, 'iclainfo');
+ }],
+ ['https://whimsy.apache.org/public/public_ldap_groups.json', "ldapgroups", function(json) {
+ ldapgroups = json.groups;
+ saveInfo(json, 'ldapgroups');
+ }],
+ ['https://whimsy.apache.org/public/public_ldap_authgroups.json', "ldapauth", function(json) {
+ ldapauth = json.auth;
+ saveInfo(json, 'ldapauth');
+ }],
+ ['https://whimsy.apache.org/public/public_ldap_services.json', "services", function(json) {
+ ldapservices = json.services;
+ saveInfo(json, 'services');
+ }],
+ ],
+ allDone);
+}
+
+// Called when all the async GETs have been completed
+
+function allDone() {
+ try {
+ pmcs = []
+ for (var k in committees) { // actual committees, not LDAP committee groups
+ if (committees[k].pmc) { // skip non-PMCs
+ pmcs.push(k)
+ }
+ }
+ for (var g in ldapprojects) {
+ // get podlings from projects
+ if (ldapprojects[g]['podling'] == 'current') {
+ podlings[g] = {}
+ podlings[g].roster = ldapprojects[g].members
+ }
+ }
+ pmcs.push('member')
+ pmcs.sort()
+ var mMap = {}
+ for (var m in members.members) {
+ mMap[members.members[m]] = {}
+ }
+ // copy across the members info
+ committees['member'] = {
+ 'roster': mMap,
+ 'display_name': 'Foundation Members',
+ 'description': "Current ASF members (Committers == those with member karma)",
+ 'site': 'http://www.apache.org/foundation/'
+ }
+
+ // Match ?type=name
+ searchProjects('cloudstack', true)
+
+ } catch (error) {
+ showError(error)
+ }
+}
\ No newline at end of file
diff --git a/source/who.html.markdown b/source/who.html.markdown
index bc10c5e..e44b827 100644
--- a/source/who.html.markdown
+++ b/source/who.html.markdown
@@ -15,208 +15,20 @@
</div>
</div>
+ <div id="phonebook">
-This page includes the Apache CloudStack Project Management Committee (PMC) members and committers.
-
-## PMC
-
-Active Project Management Committee contains (in alphabetical order of their usernames):
-
-{:.table-bordered}
-
-
-| Username | Name |
-|----------|------|
-|akarasulu|Alex Karasulu|
-|alena1108|Alena Prokharchyk|
-|animesh|Animesh|
-|andrijapanic|Andrija Panic|
-|boris|Boris Schrijver|
-|bstoyanov|Boris Stoyanov|
-|chipchilders|Chip Childers|
-|chiradeep|Chiradeep Vittal|
-|dahn|Daan|
-|ekho|Wilder Rodrigues|
-|erikw|Erik Weber|
-|giles|Giles Sirett|
-|gochiba|Go Chiba|
-|gabriel|Gabriel Beims Bräscher|
-|hogstrom|Matt Richard Hogstrom|
-|hugo|Hugo Trippaers|
-|ilya|Ilya Musayev|
-|jburwell|John Burwell|
-|jlk|John Kinsella|
-|jzb|Joe Brockmeier|
-|karenv|Karen Vuong|
-|ke4qqq|David Nalley|
-|kluge|Kevin Kluge|
-|milamber|Bruno Demion|
-|mlsorensen|Marcus Sorensen|
-|mnour|Mohammad Nour El-Din|
-|mrhinkle|Mark R. Hinkle|
-|mtutkowski|Mike Tutkowski|
-|nslater|Naomi Slater|
-|nux|Nux|
-|nathanejohnson|Nathan Johnson|
-|paul_a|Paul Angus|
-|pdion891|Pierre-Luc Dion|
-|rafael|Rafael Weingärtner|
-|rajani|Rajani Karuturi|
-|remi|Remi Bergsma|
-|resmo|Rene Moser|
-|rohit|Rohit Yadav|
-|sebgoa|Sebastien Goasguen|
-|sweller|Simon Weller|
-|swill|Will Stevens|
-|syed|Syed Ahmed|
-|svogel|Sven Vogel|
-|tsp|Prasanna|
-|weizhou|Wei Zhou|
-|widodh|Wido den Hollander|
-|willchan|William Chan|
-
-
-
-## Emeritus PMC Members
-
-PMC members who are no longer active include:
-
-+ Alex Huang (ahuang)
-+ Disheng Su (edison)
-+ Ian Duffy (duffy)
-+ Olivier Lamy (olamy)
-
-
-
-## Committers
-
-Active list of committers (in alphabetical order of their usernames):
-
-{:.table-bordered}
-
-
-| Username | Name |
-|----------|------|
-|ahmad|Ahmad Emneina|
-|akarasulu|Alex Karasulu|
-|alena1108|Alena Prokharchyk|
-|amoghvk|Amogh Vasekar|
-|andrijapanic|Andrija Panic|
-|animesh|Animesh|
-|anthonyxu|Anthony Xu|
-|aprateek|Abhinandan Prateek|
-|bfederle|Brian Federle|
-|boris|Boris Schrijver|
-|brett|Brett Porter|
-|bstoyanov|Boris Stoyanov|
-|chipchilders|Chip Childers|
-|chiradeep|Chiradeep Vittal|
-|claytonweise|Clayton Weise|
-|csuich2|Chris Suich|
-|dahn|Daan|
-|darren|Darren Shepherd|
-|dcahill|Dave Cahill|
-|demetriust|Demetrius Tsitrelis|
-|devdeep|Devdeep Singh|
-|dkonrad|Dennis Konrad|
-|dsonstebo|Dag Sonstebo|
-|ekho|Wilder Rodrigues|
-|erikw|Erik Weber|
-|fmaximus|Frank Maximus|
-|frankzhang|Xin Zhang|
-|gabriel|Gabriel Beims Bräscher|
-|gaurav|Gaurav Nandkumar Aradhye|
-|gavinlee|Gavin Lee|
-|giles|Giles Sirett|
-|girish|Girish Prabhakar Shilamkar|
-|gochiba|Go Chiba|
-|haeena|Toshiaki Hatano|
-|harikrishna|Harikrishna Patnala|
-|higster|Geoff Higginbottom|
-|hogstrom|Matt Richard Hogstrom|
-|hugo|Hugo Trippaers|
-|ilya|Ilya Musayev|
-|isaacchiang|Isaac Chiang|
-|jayapal|Jayapal|
-|jbausewein|Jason Bausewein|
-|jburwell|John Burwell|
-|jessicawang|Jessica Wang|
-|jim|Jim Jagielski|
-|jlk|John Kinsella|
-|jtomechak|Jessica Tomechak|
-|jzb|Joe Brockmeier|
-|karenv|Karen Vuong|
-|kawai|Hiroaki Kawai|
-|kdamage|Kelcey Damage|
-|ke4qqq|David Nalley|
-|kelveny|Kelven Yang|
-|kirk|Kirk Kosinski|
-|kishan|Kishan|
-|kluge|Kevin Kluge|
-|kocka|Laszlo Hornyak|
-|koushik|Koushik Das|
-|likithas|Likitha Shetty|
-|marcaurele|Marc-Aurèle Brothier|
-|mchen|Min Chen|
-|mice|Mice Xia|
-|milamber|Bruno Demion|
-|mlsorensen|Marcus Sorensen|
-|mnour|Mohammad Nour El-Din|
-|mrhinkle|Mark R. Hinkle|
-|msinhore|Marco Sinhoreli|
-|mtutkowski|Mike Tutkowski|
-|muralireddy|Murali Mohan Reddy|
-|nathanejohnson|Nathan Johnson|
-|nitin|Nitin|
-|noa|Noa Resare|
-|nslater|Naomi Slater|
-|nux|Nux|
-|nvazquez|Nicolás Vázquez|
-|olgasmola|Olga Smola|
-|paul_a|Paul Angus|
-|pdion891|Pierre-Luc Dion|
-|pnguyen|Phong Nguyen|
-|prachidamle|Prachi Damle|
-|pranavs|Pranav Saxena|
-|pyr|Pierre-Yves Ritschard|
-|radhika|Radhika Nair|
-|rafael|Rafael Weingärtner|
-|rajani|Rajani Karuturi|
-|rajeshbattala|Rajesh Battala|
-|remi|Remi Bergsma|
-|resmo|Rene Moser|
-|rohit|Rohit Yadav|
-|sailajamada|Sailaja Mada|
-|saksham|Saksham Srivastava|
-|sangeethah|Sangeetha Hariharan|
-|sanjaytripathi|Sanjay Tripathi|
-|sanjeev|Sanjeev Neelarapu|
-|santhoshedukulla|Santhosh|
-|sateesh|Sateesh Chodapuneedi|
-|schhen|Sonny Heng Chhen|
-|sebgoa|Sebastien Goasguen|
-|serg38|Sergey Levitskiy|
-|slriv|Sam Robertson|
-|snuf|Funs Kessen|
-|sowmya|Sowmya Krishnan|
-|sudhap|Sudhap|
-|svogel|Sven Vogel|
-|swamy|Venkata Swamy|
-|sweller|Simon Weller|
-|swill|Will Stevens|
-|syed|Syed Ahmed|
-|talluri|Srikanteswararao Talluri|
-|tsp|Prasanna|
-|tuna|Anh Tu Nguyen|
-|vijayendrabvs|Vijayendra Bhamidipati|
-|weizhou|Wei Zhou|
-|widodh|Wido den Hollander|
-|willchan|William Chan|
-|yasker|Sheng Yang|
-|ynojima|Yoshikazu Nojima|
-
-
-
-## Emeritus Committers
-
-Committers who are no longer active include:
+ <p>
+ Loading data, please wait...<br/>
+ <img src="images/loader.gif" alt=""/>
+ </p>
+ <p id="progress"></p>
+ <noscript>
+ <h2>Notice!</h2>
+ <p>
+ This site relies heavily on JavaScript.
+ Please enable it or get a browser that supports it.
+ </p>
+ </noscript>
+
+ </div>
+ <script type='text/javascript' src="javascripts/phonebook.js" onload="preRender()"></script>