(refs #32)Add plugin install tab

This commit is contained in:
Naoki Takezoe
2014-06-15 13:11:08 +09:00
parent 3b2e42fd61
commit 4af4c4e7c6
6 changed files with 133 additions and 13 deletions

View File

@@ -4,10 +4,13 @@ import service.{AccountService, SystemSettingsService}
import SystemSettingsService._
import util.AdminAuthenticator
import util.Directory._
import util.ControlUtil._
import jp.sf.amateras.scalatra.forms._
import ssh.SshServer
import org.scalatra.Ok
import org.apache.commons.io.FileUtils
import java.io.FileInputStream
import plugin.PluginSystem
class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator
@@ -89,11 +92,28 @@ trait SystemSettingsControllerBase extends ControllerBase {
val dir = new java.io.File(PluginHome, pluginId)
if(dir.exists && dir.isDirectory){
FileUtils.deleteQuietly(dir)
PluginSystem.uninstall(pluginId)
}
}
redirect("/admin/plugins")
})
get("/admin/plugins/available")(adminOnly {
admin.plugins.html.available(getAvailablePlugins())
})
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
val dir = getPluginCacheDir()
getAvailablePlugins().filter(x => form.pluginIds.contains(x.id)).foreach { plugin =>
val pluginDir = new java.io.File(PluginHome, plugin.id)
if(!pluginDir.exists){
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
}
PluginSystem.installPlugin(plugin.id)
}
redirect("/admin/plugins")
})
get("/admin/plugins/console")(adminOnly {
admin.plugins.html.console()
})
@@ -103,4 +123,35 @@ trait SystemSettingsControllerBase extends ControllerBase {
val result = plugin.JavaScriptPlugin.evaluateJavaScript(script)
Ok(result)
})
// TODO Move to PluginSystem or Service?
private def getAvailablePlugins(): List[SystemSettingsControllerBase.AvailablePlugin] = {
val dir = getPluginCacheDir()
if(dir.exists && dir.isDirectory){
PluginSystem.repositories.flatMap { repo =>
val repoDir = new java.io.File(dir, repo.id)
if(repoDir.exists && repoDir.isDirectory){
repoDir.listFiles.filter(d => d.isDirectory && !d.getName.startsWith(".")).map { plugin =>
val propertyFile = new java.io.File(plugin, "plugin.properties")
val properties = new java.util.Properties()
if(propertyFile.exists && propertyFile.isFile){
using(new FileInputStream(propertyFile)){ in =>
properties.load(in)
}
}
SystemSettingsControllerBase.AvailablePlugin(
repo.id,
properties.getProperty("id"),
properties.getProperty("author"),
properties.getProperty("url"),
properties.getProperty("description"))
}
} else Nil
}
} else Nil
}
}
object SystemSettingsControllerBase {
case class AvailablePlugin(repository: String, id: String, author: String, url: String, description: String)
}

View File

@@ -67,12 +67,15 @@ object JavaScriptPlugin {
def define(id: String, author: String, url: String, description: String) = new JavaScriptPlugin(id, author, url, description)
def evaluateJavaScript(script: String): Any = {
def evaluateJavaScript(script: String, vars: Map[String, Any] = Map.empty): Any = {
val context = JsContext.enter()
try {
val scope = context.initStandardObjects()
scope.put("PluginSystem", scope, PluginSystem)
scope.put("JavaScriptPlugin", scope, this)
vars.foreach { case (key, value) =>
scope.put(key, scope, value)
}
val result = context.evaluateString(scope, script, "<cmd>", 1, null)
result
} finally {

View File

@@ -5,6 +5,7 @@ import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import org.slf4j.LoggerFactory
import java.util.concurrent.atomic.AtomicBoolean
import util.Directory._
import util.ControlUtil._
import org.apache.commons.io.FileUtils
/**
@@ -16,6 +17,7 @@ object PluginSystem {
private val initialized = new AtomicBoolean(false)
private val pluginsMap = scala.collection.mutable.Map[String, Plugin]()
private val repositoriesList = scala.collection.mutable.ListBuffer[PluginRepository]()
def install(plugin: Plugin): Unit = {
pluginsMap.put(plugin.id, plugin)
@@ -27,24 +29,45 @@ object PluginSystem {
pluginsMap.remove(id)
}
def repositories: List[PluginRepository] = repositoriesList.toList
/**
* Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
*/
def init(): Unit = {
if(initialized.compareAndSet(false, true)){
// Load installed plugins
val pluginDir = new java.io.File(PluginHome)
if(pluginDir.exists && pluginDir.isDirectory){
pluginDir.listFiles.filter(f => f.isDirectory && !f.getName.startsWith(".")).foreach { dir =>
val file = new java.io.File(dir, "plugin.js")
if(file.exists && file.isFile){
val script = FileUtils.readFileToString(file, "UTF-8")
installPlugin(dir.getName)
}
}
// Add default plugin repositories
repositoriesList += PluginRepository("central", "https://github.com/takezoe/gitbucket_plugins.git")
}
}
def installPlugin(id: String): Unit = {
val pluginDir = new java.io.File(PluginHome)
val javaScriptFile = new java.io.File(pluginDir, id + "/plugin.js")
if(javaScriptFile.exists && javaScriptFile.isFile){
val properties = new java.util.Properties()
using(new java.io.FileInputStream(new java.io.File(pluginDir, id + "/plugin.properties"))){ in =>
properties.load(in)
}
val script = FileUtils.readFileToString(javaScriptFile, "UTF-8")
try {
JavaScriptPlugin.evaluateJavaScript(script)
JavaScriptPlugin.evaluateJavaScript(script, Map(
"id" -> properties.getProperty("id"),
"author" -> properties.getProperty("author"),
"url" -> properties.getProperty("url"),
"description" -> properties.getProperty("description")
))
} catch {
case e: Exception => logger.warn(s"Error in plugin loading for ${file.getAbsolutePath}", e)
}
}
}
case e: Exception => logger.warn(s"Error in plugin loading for ${javaScriptFile.getAbsolutePath}", e)
}
}
}
@@ -55,6 +78,7 @@ object PluginSystem {
def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList
// Case classes to hold plug-ins information internally in GitBucket
case class PluginRepository(id: String, url: String)
case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean)
case class RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean)
case class Action(path: String, function: (HttpServletRequest, HttpServletResponse) => Any)

View File

@@ -36,6 +36,8 @@ object Directory {
val PluginHome = s"${GitBucketHome}/plugins"
val TemporaryHome = s"${GitBucketHome}/tmp"
/**
* Substance directory of the repository.
*/
@@ -57,13 +59,18 @@ object Directory {
* Root of temporary directories for the upload file.
*/
def getTemporaryDir(sessionId: String): File =
new File(s"${GitBucketHome}/tmp/_upload/${sessionId}")
new File(s"${TemporaryHome}/_upload/${sessionId}")
/**
* Root of temporary directories for the specified repository.
*/
def getTemporaryDir(owner: String, repository: String): File =
new File(s"${GitBucketHome}/tmp/${owner}/${repository}")
new File(s"${TemporaryHome}/${owner}/${repository}")
/**
* Root of plugin cache directory. Plugin repositories are cloned into this directory.
*/
def getPluginCacheDir(): File = new File(s"${TemporaryHome}/_plugins")
/**
* Temporary directory which is used to create an archive to download repository contents.

View File

@@ -0,0 +1,35 @@
@(plugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context)
@import context._
@import view.helpers._
@html.main("Plugins"){
@admin.html.menu("plugins"){
@tab("available")
<form action="@path/admin/plugins/_install" method="POST" validate="true">
<table class="table table-bordered">
<tr>
<th>ID</th>
<th>Provider</th>
<th>Description</th>
</tr>
@plugins.zipWithIndex.map { case (plugin, i) =>
<tr>
<td>
<input type="checkbox" name="pluginId[@i]" value="@plugin.id"/>
@plugin.id
</td>
<td><a href="@plugin.url">@plugin.author</a></td>
<td>@plugin.description</td>
</tr>
}
</table>
<input type="submit" id="install-plugins" class="btn btn-primary" value="Install selected plugins"/>
</form>
}
}
<script>
$(function(){
$('#install-plugins').click(function(){
return confirm('Selected plugin will be installed. Are you sure?');
});
});
</script>

View File

@@ -2,6 +2,6 @@
@import context._
<ul class="nav nav-tabs">
<li@if(active == "installed"){ class="active"}><a href="@path/admin/plugins">Installed plugins</a></li>
<li@if(active == "available"){ class="active"}><a href="@path/admin/plugins">Available plugins</a></li>
<li@if(active == "available"){ class="active"}><a href="@path/admin/plugins/available">Available plugins</a></li>
<li@if(active == "console" ){ class="active"}><a href="@path/admin/plugins/console">JavaScript console</a></li>
</ul>