mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-06 21:45:50 +01:00
(refs #32)Add plugin install tab
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
35
src/main/twirl/admin/plugins/available.scala.html
Normal file
35
src/main/twirl/admin/plugins/available.scala.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user