Plugin list can now be reloaded 'live'

This commit is contained in:
Egil Moeller 2012-03-19 17:16:49 +01:00
parent 6fe7f2c2b2
commit c591efb352
6 changed files with 152 additions and 83 deletions

View File

@ -1,6 +1,7 @@
var path = require('path'); var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs'); var eejs = require('ep_etherpad-lite/node/eejs');
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) { args.app.get('/admin/plugins', function(req, res) {
@ -20,35 +21,30 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.socketio = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer"); var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) { io.on('connection', function (socket) {
socket.on("load", function (query) {
socket.emit("installed-results", {results: plugins.plugins});
});
socket.on("search", function (query) { socket.on("search", function (query) {
socket.emit("progress", {progress:0, message:'Fetching results...'}); socket.emit("progress", {progress:0, message:'Fetching results...'});
installer.search(query, function (er, data) { installer.search(query, function (progress) {
if (er) { if (progress.results)
socket.emit("progress", {progress:1, error:er}); socket.emit("search-result", progress);
} else { socket.emit("progress", progress);
socket.emit("search-result", {results: data});
socket.emit("progress", {progress:1, message:'Done.'});
}
}); });
}); });
socket.on("install", function (plugin_name) { socket.on("install", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."}); socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
installer.install(plugin_name, function (er) { installer.install(plugin_name, function (progress) {
if (er) socket.emit("progress", progress);
socket.emit("progress", {progress:1, error:er});
else
socket.emit("progress", {progress:1, message:'Done.'});
}); });
}); });
socket.on("uninstall", function (plugin_name) { socket.on("uninstall", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."}); socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
installer.uninstall(plugin_name, function (er) { installer.uninstall(plugin_name, function (progress) {
if (er) socket.emit("progress", progress);
socket.emit("progress", {progress:1, error:er});
else
socket.emit("progress", {progress:1, message:'Done.'});
}); });
}); });
}); });

View File

@ -23,8 +23,14 @@
"log4js" : "0.4.1", "log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10", "jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2", "async-stacktrace" : "0.0.2",
"npm" : "1.1", "npm" : "1.1",
"ejs" : "0.6.1" "ejs" : "0.6.1",
"node.extend" : "1.0.0",
"graceful-fs" : "1.1.5",
"slide" : "1.1.3",
"semver" : "1.0.13"
}, },
"devDependencies": { "devDependencies": {
"jshint" : "*" "jshint" : "*"

View File

@ -40,14 +40,14 @@ exports.callAll = function (hook_name, args) {
} }
exports.aCallAll = function (hook_name, args, cb) { exports.aCallAll = function (hook_name, args, cb) {
if (plugins.hooks[hook_name] === undefined) cb([]); if (plugins.hooks[hook_name] === undefined) return cb(null, []);
async.map( async.map(
plugins.hooks[hook_name], plugins.hooks[hook_name],
function (hook, cb) { function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); }); hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
}, },
function (err, res) { function (err, res) {
cb(exports.flatten(res)); cb(null, exports.flatten(res));
} }
); );
} }
@ -58,8 +58,8 @@ exports.callFirst = function (hook_name, args) {
} }
exports.aCallFirst = function (hook_name, args, cb) { exports.aCallFirst = function (hook_name, args, cb) {
if (plugins.hooks[hook_name][0] === undefined) cb([]); if (plugins.hooks[hook_name][0] === undefined) return cb(null, []);
hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(exports.flatten(res)); }); hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(null, exports.flatten(res)); });
} }
exports.callAllStr = function(hook_name, args, sep, pre, post) { exports.callAllStr = function(hook_name, args, sep, pre, post) {

View File

@ -3,43 +3,74 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm"); var npm = require("npm");
var registry = require("npm/lib/utils/npm-registry-client/index.js"); var registry = require("npm/lib/utils/npm-registry-client/index.js");
exports.uninstall = function(plugin_name, cb) { var withNpm = function (npmfn, cb) {
npm.load({}, function (er) { npm.load({}, function (er) {
if (er) return cb(er) if (er) return cb({progress:1, error:er});
npm.commands.uninstall([plugin_name], function (er) { npm.on("log", function (message) {
if (er) return cb(er); cb({progress: 0.5, message:message.msg + ": " + message.pref});
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er) { });
cb(er); npmfn(function (er, data) {
}); if (er) return cb({progress:1, error:er.code + ": " + er.path});
}) if (!data) data = {};
}) data.progress = 1;
} data.message = "Done.";
cb(data);
exports.install = function(plugin_name, cb) {
npm.load({}, function (er) {
if (er) return cb(er)
npm.commands.install([plugin_name], function (er) {
if (er) return cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er) {
cb(er);
});
}); });
})
}
exports.search = function(pattern, cb) {
npm.load({}, function (er) {
registry.get(
"/-/all", null, 600, false, true,
function (er, data) {
if (er) return cb(er);
var res = {};
for (key in data) {
if (/*key.indexOf(plugins.prefix) == 0 &&*/ key.indexOf(pattern) != -1)
res[key] = data[key];
}
cb(null, res);
}
);
}); });
} }
// 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) {
if (er) return cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
},
cb
);
};
exports.install = function(plugin_name, cb) {
withNpm(
function (cb) {
npm.commands.install([plugin_name], function (er) {
if (er) return cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
});
},
cb
);
};
exports.search = function(pattern, cb) {
withNpm(
function (cb) {
registry.get(
"/-/all", null, 600, false, true,
function (er, data) {
if (er) return cb(er);
var res = {};
for (key in data) {
if (/*key.indexOf(plugins.prefix) == 0 &&*/ key.indexOf(pattern) != -1)
res[key] = data[key];
}
cb(null, {results:res});
}
);
},
cb
);
};

View File

@ -2,7 +2,7 @@ exports.isClient = typeof global != "object";
if (!exports.isClient) { if (!exports.isClient) {
var npm = require("npm/lib/npm.js"); var npm = require("npm/lib/npm.js");
var readInstalled = require("npm/lib/utils/read-installed.js"); var readInstalled = require("./read-installed.js");
var relativize = require("npm/lib/utils/relativize.js"); var relativize = require("npm/lib/utils/relativize.js");
var readJson = require("npm/lib/utils/read-json.js"); var readJson = require("npm/lib/utils/read-json.js");
var path = require("path"); var path = require("path");
@ -10,6 +10,7 @@ if (!exports.isClient) {
var fs = require("fs"); var fs = require("fs");
var tsort = require("./tsort"); var tsort = require("./tsort");
var util = require("util"); var util = require("util");
var extend = require("node.extend");
} }
exports.prefix = 'ep_'; exports.prefix = 'ep_';
@ -112,14 +113,19 @@ exports.getPackages = function (cb) {
function flatten(deps) { function flatten(deps) {
Object.keys(deps).forEach(function (name) { Object.keys(deps).forEach(function (name) {
if (name.indexOf(exports.prefix) == 0) { if (name.indexOf(exports.prefix) == 0) {
packages[name] = deps[name]; packages[name] = extend({}, deps[name]);
// Delete anything that creates loops so that the plugin
// list can be sent as JSON to the web client
delete packages[name].dependencies;
delete packages[name].parent;
} }
if (deps[name].dependencies !== undefined) if (deps[name].dependencies !== undefined)
flatten(deps[name].dependencies); flatten(deps[name].dependencies);
delete deps[name].dependencies;
}); });
} }
flatten([data]); var tmp = {};
tmp[data.name] = data;
flatten(tmp);
cb(null, packages); cb(null, packages);
}); });
} }

View File

@ -20,10 +20,10 @@
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
width: 500px; width: 700px;
height: 400px; height: 500px;
margin-left: -250px; margin-left: -350px;
margin-top: -200px; margin-top: -250px;
border: 3px solid #999999; border: 3px solid #999999;
background: #eeeeee; background: #eeeeee;
} }
@ -33,6 +33,8 @@
border-bottom: 3px solid #999999; border-bottom: 3px solid #999999;
font-size: 24px; font-size: 24px;
line-height: 24px; line-height: 24px;
height: 24px;
overflow: hidden;
} }
.dialog .title .close { .dialog .title .close {
float: right; float: right;
@ -46,6 +48,7 @@
left: 10px; left: 10px;
right: 10px; right: 10px;
padding: 2px; padding: 2px;
overflow: auto;
} }
</style> </style>
<script src="../../static/js/jquery.js"></script> <script src="../../static/js/jquery.js"></script>
@ -54,6 +57,8 @@
$(document).ready(function () { $(document).ready(function () {
var socket = io.connect().of("/pluginfw/installer"); var socket = io.connect().of("/pluginfw/installer");
var doUpdate = false;
function updateHandlers() { function updateHandlers() {
$("#progress.dialog .close").click(function () { $("#progress.dialog .close").click(function () {
$("#progress.dialog").hide(); $("#progress.dialog").hide();
@ -64,14 +69,16 @@
socket.emit("search", $("#search-query")[0].value); socket.emit("search", $("#search-query")[0].value);
}); });
$("#do-install").click(function (e) { $(".do-install").click(function (e) {
var row = $(e.target).closest("tr"); var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("install", row.find(".name").html()); socket.emit("install", row.find(".name").html());
}); });
$("#do-uninstall").click(function (e) { $(".do-uninstall").click(function (e) {
var row = $(e.target).closest("tr"); var row = $(e.target).closest("tr");
socket.emit("install", row.find(".name").html()); doUpdate = true;
socket.emit("uninstall", row.find(".name").html());
}); });
} }
@ -80,17 +87,24 @@
socket.on('progress', function (data) { socket.on('progress', function (data) {
$("#progress.dialog .close").hide(); $("#progress.dialog .close").hide();
$("#progress.dialog").show(); $("#progress.dialog").show();
var message = data.message; var message = "Unknown status";
if (data.message) {
message = "<span class='status'>" + data.message.toString() + "</span>";
}
if (data.error) { if (data.error) {
message = "<div class='error'>" + data.error.toString() + "<div>"; message = "<span class='error'>" + data.error.toString() + "<span>";
} }
$("#progress.dialog .message").html(message); $("#progress.dialog .message").html(message);
$("#progress.dialog .history").append(message); $("#progress.dialog .history").append("<div>" + message + "</div>");
if (data.progress >= 1) { if (data.progress >= 1) {
if (data.error) { if (data.error) {
$("#progress.dialog .close").show(); $("#progress.dialog .close").show();
} else { } else {
if (doUpdate) {
doUpdate = false;
socket.emit("load");
}
$("#progress.dialog").hide(); $("#progress.dialog").hide();
} }
} }
@ -109,6 +123,23 @@
} }
updateHandlers(); updateHandlers();
}); });
socket.on('installed-results', function (data) {
$("#installed-plugins *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone();
for (attr in plugin.package) {
row.find("." + attr).html(plugin.package[attr]);
}
$("#installed-plugins").append(row);
}
updateHandlers();
});
socket.emit("load");
}); });
</script> </script>
</head> </head>
@ -131,17 +162,16 @@
<td></td> <td></td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="template">
<% for (var plugin_name in plugins) { %> <tr id="installed-plugin-template">
<% var plugin = plugins[plugin_name]; %> <td class="name"></td>
<tr> <td class="description"></td>
<td class="name"><%= plugin.package.name %></td> <td class="actions">
<td><%= plugin.package.description %></td> <input type="button" value="I" class="do-uninstall">
<td> </td>
<input type="submit" value="U" class="do-uninstall"> </tr>
</td> </tbody>
</tr> <tbody id="installed-plugins">
<% } %>
</tbody> </tbody>
</table> </table>