summaryrefslogtreecommitdiff
path: root/src/static
diff options
context:
space:
mode:
authorJohn McLear <john@mclear.co.uk>2013-03-27 12:05:00 -0700
committerJohn McLear <john@mclear.co.uk>2013-03-27 12:05:00 -0700
commit09b32ea6946d538f04b4b48fb2371a3057ebcdb9 (patch)
tree98ab64f63881656f97ba79c31c703f613a6accc3 /src/static
parent2abb993e8be5048930acbe8ee5ace2ef84646b54 (diff)
parentcbee50d42d8a528c524ad5a76a54bf59e8978689 (diff)
downloadetherpad-lite-09b32ea6946d538f04b4b48fb2371a3057ebcdb9.zip
Merge pull request #1672 from ether/feature/admin-plugins-revamp
/admin/plugins revamp
Diffstat (limited to 'src/static')
-rw-r--r--src/static/css/admin.css71
-rw-r--r--src/static/js/admin/plugins.js350
-rw-r--r--src/static/js/pluginfw/installer.js151
3 files changed, 331 insertions, 241 deletions
diff --git a/src/static/css/admin.css b/src/static/css/admin.css
index b6823842..2260e27d 100644
--- a/src/static/css/admin.css
+++ b/src/static/css/admin.css
@@ -43,7 +43,7 @@ div.innerwrapper {
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
margin: auto;
max-width: 1150px;
- min-height: 100%;
+ min-height: 101%;/*always display a scrollbar*/
}
h1 {
@@ -102,12 +102,26 @@ input[type="text"] {
max-width: 500px;
}
+.sort {
+ cursor: pointer;
+}
+.sort:after {
+ content: '▲▼'
+}
+.sort.up:after {
+ content:'▲'
+}
+.sort.down:after {
+ content:'▼'
+}
+
table {
border: 1px solid #ddd;
border-radius: 3px;
border-spacing: 0;
width: 100%;
margin: 20px 0;
+ position:relative; /* Allows us to position the loading indicator relative to the table */
}
table thead tr {
@@ -122,13 +136,40 @@ td, th {
display: none;
}
-#progress {
+#installed-plugins td>div {
+ position: relative;/* Allows us to position the loading indicator relative to this row */
+ display: inline-block; /*make this fill the whole cell*/
+ width:100%;
+}
+
+.messages td>* {
+ display: none;
+ text-align: center;
+}
+
+.messages .fetching {
+ display: block;
+}
+
+.progress {
position: absolute;
- bottom: 50px;
+ top: 0; left: 0; bottom:0; right:0;
+ padding: auto;
+
+ background: rgb(255,255,255);
+ display: none;
}
-#progress img {
- vertical-align: top;
+#search-progress.progress {
+ padding-top: 20%;
+ background: rgba(255,255,255,0.7);
+}
+
+.progress * {
+ display: block;
+ margin: 0 auto;
+ text-align: center;
+ color: #666;
}
.settings {
@@ -147,7 +188,25 @@ a:link, a:visited, a:hover, a:focus {
}
a:focus, a:hover {
- border-bottom: #333333 1px solid;
+ text-decoration: underline;
+}
+
+.installed-results a:link,
+.search-results a:link,
+.installed-results a:visited,
+.search-results a:visited,
+.installed-results a:hover,
+.search-results a:hover,
+.installed-results a:focus,
+.search-results a:focus {
+ text-decoration: underline;
+}
+
+.installed-results a:focus,
+.search-results a:focus,
+.installed-results a:hover,
+.search-results a:hover {
+ text-decoration: none;
}
pre {
diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js
index a973875c..41affa74 100644
--- a/src/static/js/admin/plugins.js
+++ b/src/static/js/admin/plugins.js
@@ -12,176 +12,248 @@ $(document).ready(function () {
//connect
socket = io.connect(url, {resource : resource}).of("/pluginfw/installer");
- $('.search-results').data('query', {
- pattern: '',
- offset: 0,
- limit: 12,
- });
+ function search(searchTerm, limit) {
+ if(search.searchTerm != searchTerm) {
+ search.offset = 0
+ search.results = []
+ search.end = false
+ }
+ limit = limit? limit : search.limit
+ search.searchTerm = searchTerm;
+ socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir});
+ search.offset += limit;
+ $('#search-progress').show()
+ }
+ search.offset = 0;
+ search.limit = 12;
+ search.results = [];
+ search.sortBy = 'name';
+ search.sortDir = /*DESC?*/true;
+ search.end = true;// have we received all results already?
+ search.messages = {
+ show: function(msg) {
+ $('.search-results .messages').show()
+ $('.search-results .messages .'+msg+'').show()
+ },
+ hide: function(msg) {
+ $('.search-results .messages').hide()
+ $('.search-results .messages .'+msg+'').hide()
+ }
+ }
- var doUpdate = false;
+ var installed = {
+ progress: {
+ show: function(plugin, msg) {
+ $('.installed-results .'+plugin+' .progress').show()
+ $('.installed-results .'+plugin+' .progress .message').text(msg)
+ if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100)
+ },
+ hide: function(plugin) {
+ $('.installed-results .'+plugin+' .progress').hide()
+ $('.installed-results .'+plugin+' .progress .message').text('')
+ }
+ },
+ messages: {
+ show: function(msg) {
+ $('.installed-results .messages').show()
+ $('.installed-results .messages .'+msg+'').show()
+ },
+ hide: function(msg) {
+ $('.installed-results .messages').hide()
+ $('.installed-results .messages .'+msg+'').hide()
+ }
+ },
+ list: []
+ }
+
+ function displayPluginList(plugins, container, template) {
+ plugins.forEach(function(plugin) {
+ var row = template.clone();
+
+ for (attr in plugin) {
+ if(attr == "name"){ // Hack to rewrite URLS into name
+ row.find(".name").html("<a target='_blank' title='Plugin details' href='https://npmjs.org/package/"+plugin['name']+"'>"+plugin['name'].substr(3)+"</a>"); // remove 'ep_'
+ }else{
+ row.find("." + attr).html(plugin[attr]);
+ }
+ }
+ row.find(".version").html( plugin.version );
+ row.addClass(plugin.name)
+ row.data('plugin', plugin.name)
+ container.append(row);
+ })
+ updateHandlers();
+ }
+
+ function sortPluginList(plugins, property, /*ASC?*/dir) {
+ return plugins.sort(function(a, b) {
+ if (a[property] < b[property])
+ return dir? -1 : 1;
+ if (a[property] > b[property])
+ return dir? 1 : -1;
+ // a must be equal to b
+ return 0;
+ })
+ }
- var search = function () {
- socket.emit("search", $('.search-results').data('query'));
- tasks++;
+ // Infinite scroll
+ $(window).scroll(checkInfiniteScroll)
+ function checkInfiniteScroll() {
+ if(search.end) return;// don't keep requesting if there are no more results
+ try{
+ var top = $('.search-results .results > tr:last').offset().top
+ if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm)
+ }catch(e){}
}
function updateHandlers() {
- $("form").submit(function(){
- var query = $('.search-results').data('query');
- query.pattern = $("#search-query").val();
- query.offset = 0;
- search();
- return false;
- });
-
+ // Search
$("#search-query").unbind('keyup').keyup(function () {
- var query = $('.search-results').data('query');
- query.pattern = $("#search-query").val();
- query.offset = 0;
- search();
+ search($("#search-query").val());
});
+ // update & install
$(".do-install, .do-update").unbind('click').click(function (e) {
- var row = $(e.target).closest("tr");
- doUpdate = true;
- socket.emit("install", row.find(".name").text());
- tasks++;
+ var $row = $(e.target).closest("tr")
+ , plugin = $row.data('plugin');
+ if($(this).hasClass('do-install')) {
+ $row.remove().appendTo('#installed-plugins')
+ installed.progress.show(plugin, 'Installing')
+ }else{
+ installed.progress.show(plugin, 'Updating')
+ }
+ socket.emit("install", plugin);
+ installed.messages.hide("nothing-installed")
});
+ // uninstall
$(".do-uninstall").unbind('click').click(function (e) {
- var row = $(e.target).closest("tr");
- doUpdate = true;
- socket.emit("uninstall", row.find(".name").text());
- tasks++;
+ var $row = $(e.target).closest("tr")
+ , pluginName = $row.data('plugin');
+ socket.emit("uninstall", pluginName);
+ installed.progress.show(pluginName, 'Uninstalling')
+ installed.list = installed.list.filter(function(plugin) {
+ return plugin.name != pluginName
+ })
});
- $(".do-prev-page").unbind('click').click(function (e) {
- var query = $('.search-results').data('query');
- query.offset -= query.limit;
- if (query.offset < 0) {
- query.offset = 0;
- }
- search();
- });
- $(".do-next-page").unbind('click').click(function (e) {
- var query = $('.search-results').data('query');
- var total = $('.search-results').data('total');
- if (query.offset + query.limit < total) {
- query.offset += query.limit;
- }
- search();
- });
+ // Sort
+ $('.sort.up').unbind('click').click(function() {
+ search.sortBy = $(this).text().toLowerCase();
+ search.sortDir = false;
+ search.offset = 0;
+ search(search.searchTerm, search.results.length);
+ search.results = [];
+ })
+ $('.sort.down, .sort.none').unbind('click').click(function() {
+ search.sortBy = $(this).text().toLowerCase();
+ search.sortDir = true;
+ search.offset = 0;
+ search(search.searchTerm, search.results.length);
+ search.results = [];
+ })
}
- updateHandlers();
-
- var tasks = 0;
- socket.on('progress', function (data) {
- $("#progress").show();
- $('#progress').data('progress', data.progress);
-
- var message = "Unknown status";
- if (data.message) {
- message = data.message.toString();
- }
- if (data.error) {
- data.progress = 1;
- }
+ socket.on('results:search', function (data) {
+ if(!data.results.length) search.end = true;
+ search.messages.hide('nothing-found')
+ search.messages.hide('fetching')
+ $("#search-query").removeAttr('disabled')
- $("#progress .message").html(message);
-
- if (data.progress >= 1) {
- tasks--;
- if (tasks <= 0) {
- // Hide the activity indicator once all tasks are done
- $("#progress").hide();
- tasks = 0;
- }
-
- if (data.error) {
- alert('An error occurred: '+data.error+' -- the server log might know more...');
- }else {
- if (doUpdate) {
- doUpdate = false;
- socket.emit("load");
- tasks++;
- }
- }
- }
- });
+ console.log('got search results', data)
- socket.on('search-result', function (data) {
- var widget=$(".search-results");
+ // add to results
+ search.results = search.results.concat(data.results);
- widget.data('query', data.query);
- widget.data('total', data.total);
+ // Update sorting head
+ $('.sort')
+ .removeClass('up down')
+ .addClass('none');
+ $('.search-results thead th[data-label='+data.query.sortBy+']')
+ .removeClass('none')
+ .addClass(data.query.sortDir? 'up' : 'down');
- widget.find('.offset').html(data.query.offset);
- if (data.query.offset + data.query.limit > data.total){
- widget.find('.limit').html(data.total);
- }else{
- widget.find('.limit').html(data.query.offset + data.query.limit);
+ // re-render search results
+ var searchWidget = $(".search-results");
+ searchWidget.find(".results *").remove();
+ if(search.results.length > 0) {
+ displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr"))
+ }else {
+ search.messages.show('nothing-found')
}
- widget.find('.total').html(data.total);
+ $('#search-progress').hide()
+ checkInfiniteScroll()
+ });
- widget.find(".results *").remove();
- for (plugin_name in data.results) {
- var plugin = data.results[plugin_name];
- var row = widget.find(".template tr").clone();
-
- for (attr in plugin) {
- if(attr == "name"){ // Hack to rewrite URLS into name
- row.find(".name").html("<a target='_blank' href='https://npmjs.org/package/"+plugin['name']+"'>"+plugin[attr]+"</a>");
- }else{
- row.find("." + attr).html(plugin[attr]);
- }
- }
- row.find(".version").html( data.results[plugin_name]['dist-tags'].latest );
-
- widget.find(".results").append(row);
- }
+ socket.on('results:installed', function (data) {
+ installed.messages.hide("fetching")
+ installed.messages.hide("nothing-installed")
- updateHandlers();
- });
+ installed.list = data.installed
+ sortPluginList(installed.list, 'name', /*ASC?*/true);
- socket.on('installed-results', function (data) {
- $("#installed-plugins *").remove();
+ // filter out epl
+ installed.list = installed.list.filter(function(plugin) {
+ return plugin.name != 'ep_etherpad-lite'
+ })
- for (plugin_name in data.results) {
- if (plugin_name == "ep_etherpad-lite") continue; // Hack...
- var plugin = data.results[plugin_name];
- var row = $("#installed-plugin-template").clone();
+ // remove all installed plugins (leave plugins that are still being installed)
+ installed.list.forEach(function(plugin) {
+ $('#installed-plugins .'+plugin.name).remove()
+ })
- for (attr in plugin.package) {
- if(attr == "name"){ // Hack to rewrite URLS into name
- row.find(".name").html("<a target='_blank' href='https://npmjs.org/package/"+plugin.package['name']+"'>"+plugin.package[attr]+"</a>");
- }else{
- row.find("." + attr).html(plugin.package[attr]);
- }
- }
- $("#installed-plugins").append(row);
+ if(installed.list.length > 0) {
+ displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template"));
+ socket.emit('checkUpdates');
+ }else {
+ installed.messages.show("nothing-installed")
}
- updateHandlers();
-
- socket.emit('checkUpdates');
- tasks++;
});
- socket.on('updatable', function(data) {
- $('#installed-plugins>tr').each(function(i,tr) {
- var pluginName = $(tr).find('.name').text()
-
- if (data.updatable.indexOf(pluginName) >= 0) {
- var actions = $(tr).find('.actions')
- actions.append('<input class="do-update" type="button" value="Update" />')
- actions.css('width', 200)
- }
+ socket.on('results:updatable', function(data) {
+ data.updatable.forEach(function(pluginName) {
+ var $row = $('#installed-plugins > tr.'+pluginName)
+ , actions = $row.find('.actions')
+ actions.append('<input class="do-update" type="button" value="Update" />')
})
updateHandlers();
})
- socket.emit("load");
- tasks++;
-
- search();
+ socket.on('finished:install', function(data) {
+ if(data.error) {
+ alert('An error occured while installing '+data.plugin+' \n'+data.error)
+ $('#installed-plugins .'+data.plugin).remove()
+ }
+
+ socket.emit("getInstalled");
+
+ // update search results
+ search.offset = 0;
+ search(search.searchTerm, search.results.length);
+ search.results = [];
+ })
+
+ socket.on('finished:uninstall', function(data) {
+ if(data.error) alert('An error occured while uninstalling the '+data.plugin+' \n'+data.error)
+
+ // remove plugin from installed list
+ $('#installed-plugins .'+data.plugin).remove()
+
+ socket.emit("getInstalled");
+
+ // update search results
+ search.offset = 0;
+ search(search.searchTerm, search.results.length);
+ search.results = [];
+ })
+
+ // init
+ updateHandlers();
+ socket.emit("getInstalled");
+ search('');
+
+ // check for updates every 5mins
+ setInterval(function() {
+ socket.emit('checkUpdates');
+ }, 1000*60*5)
});
diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js
index 15d87940..377be35e 100644
--- a/src/static/js/pluginfw/installer.js
+++ b/src/static/js/pluginfw/installer.js
@@ -1,118 +1,77 @@
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm");
-var RegClient = require("npm-registry-client")
-var registry = new RegClient(
-{ registry: "http://registry.npmjs.org"
-, cache: npm.cache }
-);
-
-var withNpm = function (npmfn, final, cb) {
+var npmIsLoaded = false;
+var withNpm = function (npmfn) {
+ if(npmIsLoaded) return npmfn();
npm.load({}, function (er) {
- if (er) return cb({progress:1, error:er});
+ if (er) return npmfn(er);
+ npmIsLoaded = true;
npm.on("log", function (message) {
- cb({progress: 0.5, message:message.msg + ": " + message.pref});
- });
- npmfn(function (er, data) {
- if (er) {
- console.error(er);
- return cb({progress:1, error: er.message});
- }
- if (!data) data = {};
- data.progress = 1;
- data.message = "Done.";
- cb(data);
- final();
+ console.log('npm: ',message)
});
+ npmfn();
});
}
-// All these functions call their callback multiple times with
-// {progress:[0,1], message:STRING, error:object}. They will call it
-// with progress = 1 at least once, and at all times will either
-// message or error be present, not both. It can be called multiple
-// times for all values of propgress except for 1.
-
exports.uninstall = function(plugin_name, cb) {
- withNpm(
- function (cb) {
- npm.commands.uninstall([plugin_name], function (er) {
+ withNpm(function (er) {
+ if (er) return cb && cb(er);
+ npm.commands.uninstall([plugin_name], function (er) {
+ if (er) return cb && cb(er);
+ hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
- hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
- if (er) return cb(er);
- plugins.update(cb);
- });
+ plugins.update(cb);
+ hooks.aCallAll("restartServer", {}, function () {});
});
- },
- function () {
- hooks.aCallAll("restartServer", {}, function () {});
- },
- cb
- );
+ });
+ });
};
exports.install = function(plugin_name, cb) {
- withNpm(
- function (cb) {
- npm.commands.install([plugin_name], function (er) {
+ withNpm(function (er) {
+ if (er) return cb && cb(er);
+ npm.commands.install([plugin_name], function (er) {
+ if (er) return cb && cb(er);
+ hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
- hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
- if (er) return cb(er);
- plugins.update(cb);
- });
+ plugins.update(cb);
+ hooks.aCallAll("restartServer", {}, function () {});
});
- },
- function () {
- hooks.aCallAll("restartServer", {}, function () {});
- },
- cb
- );
+ });
+ });
+};
+
+exports.availablePlugins = null;
+var cacheTimestamp = 0;
+
+exports.getAvailablePlugins = function(maxCacheAge, cb) {
+ withNpm(function (er) {
+ if (er) return cb && cb(er);
+ if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) {
+ return cb && cb(null, exports.availablePlugins)
+ }
+ npm.commands.search(['ep_'], /*silent?*/true, function(er, results) {
+ if(er) return cb && cb(er);
+ exports.availablePlugins = results;
+ cacheTimestamp = Math.round(+new Date/1000);
+ cb && cb(null, results)
+ })
+ });
};
-exports.searchCache = null;
-exports.search = function(query, cache, cb) {
- withNpm(
- function (cb) {
- var getData = function (cb) {
- if (cache && exports.searchCache) {
- cb(null, exports.searchCache);
- } else {
- registry.get(
- "/-/all", 600, false, true,
- function (er, data) {
- if (er) return cb(er);
- exports.searchCache = data;
- cb(er, data);
- }
- );
- }
- }
- getData(
- function (er, data) {
- if (er) return cb(er);
- var res = {};
- var i = 0;
- var pattern = query.pattern.toLowerCase();
- for (key in data) { // for every plugin in the data from npm
- if ( key.indexOf(plugins.prefix) == 0
- && key.indexOf(pattern) != -1
- || key.indexOf(plugins.prefix) == 0
- && data[key].description.indexOf(pattern) != -1
- ) { // If the name contains ep_ and the search string is in the name or description
- i++;
- if (i > query.offset
- && i <= query.offset + query.limit) {
- res[key] = data[key];
- }
- }
- }
- cb(null, {results:res, query: query, total:i});
- }
- );
- },
- function () { },
- cb
- );
+exports.search = function(searchTerm, maxCacheAge, cb) {
+ exports.getAvailablePlugins(maxCacheAge, function(er, results) {
+ if(er) return cb && cb(er);
+ var res = {};
+ searchTerm = searchTerm.toLowerCase();
+ for (var pluginName in results) { // for every available plugin
+ if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
+ if(pluginName.indexOf(searchTerm) < 0 && results[pluginName].description.indexOf(searchTerm) < 0) continue;
+ res[pluginName] = results[pluginName];
+ }
+ cb && cb(null, res)
+ })
};