mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
Merge branch 'purge-old-plugin-system'
This commit is contained in:
@@ -44,7 +44,6 @@ object MyBuild extends Build {
|
|||||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"org.quartz-scheduler" % "quartz" % "2.2.1",
|
|
||||||
"com.h2database" % "h2" % "1.4.180",
|
"com.h2database" % "h2" % "1.4.180",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import _root_.servlet.{PluginActionInvokeFilter, BasicAuthenticationFilter, TransactionFilter}
|
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
|
||||||
import app._
|
import app._
|
||||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
@@ -10,7 +10,6 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||||
context.addFilter("transactionFilter", new TransactionFilter)
|
context.addFilter("transactionFilter", new TransactionFilter)
|
||||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
context.addFilter("pluginActionInvokeFilter", new PluginActionInvokeFilter)
|
|
||||||
context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
|
|||||||
@@ -3,15 +3,8 @@ package app
|
|||||||
import service.{AccountService, SystemSettingsService}
|
import service.{AccountService, SystemSettingsService}
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
import util.AdminAuthenticator
|
import util.AdminAuthenticator
|
||||||
import util.Directory._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import ssh.SshServer
|
import ssh.SshServer
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import plugin.{Plugin, PluginSystem}
|
|
||||||
import org.scalatra.Ok
|
|
||||||
import util.Implicits._
|
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with AccountService with AdminAuthenticator
|
with AccountService with AdminAuthenticator
|
||||||
@@ -85,118 +78,4 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
redirect("/admin/system")
|
redirect("/admin/system")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
|
|
||||||
admin.plugins.html.installed(installedPlugins, updatablePlugins)
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
deletePlugins(form.pluginIds)
|
|
||||||
installPlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
deletePlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/plugins/available")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
|
|
||||||
admin.plugins.html.available(availablePlugins)
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
|
||||||
if(enablePluginSystem){
|
|
||||||
installPlugins(form.pluginIds)
|
|
||||||
redirect("/admin/plugins")
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/admin/plugins/console")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
admin.plugins.html.console()
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/admin/plugins/console")(adminOnly {
|
|
||||||
if(enablePluginSystem){
|
|
||||||
val script = request.getParameter("script")
|
|
||||||
val result = plugin.ScalaPlugin.eval(script)
|
|
||||||
Ok()
|
|
||||||
} else NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO Move these methods to PluginSystem or Service?
|
|
||||||
private def deletePlugins(pluginIds: List[String]): Unit = {
|
|
||||||
pluginIds.foreach { pluginId =>
|
|
||||||
plugin.PluginSystem.uninstall(pluginId)
|
|
||||||
val dir = new java.io.File(PluginHome, pluginId)
|
|
||||||
if(dir.exists && dir.isDirectory){
|
|
||||||
FileUtils.deleteQuietly(dir)
|
|
||||||
PluginSystem.uninstall(pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def installPlugins(pluginIds: List[String]): Unit = {
|
|
||||||
val dir = getPluginCacheDir()
|
|
||||||
val installedPlugins = plugin.PluginSystem.plugins
|
|
||||||
getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
|
|
||||||
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
|
||||||
if(pluginDir.exists){
|
|
||||||
FileUtils.deleteDirectory(pluginDir)
|
|
||||||
}
|
|
||||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
|
||||||
PluginSystem.installPlugin(plugin.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
|
||||||
val repositoryRoot = getPluginCacheDir()
|
|
||||||
|
|
||||||
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
|
||||||
PluginSystem.repositories.flatMap { repo =>
|
|
||||||
val repoDir = new java.io.File(repositoryRoot, 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(
|
|
||||||
repository = repo.id,
|
|
||||||
id = properties.getProperty("id"),
|
|
||||||
version = properties.getProperty("version"),
|
|
||||||
author = properties.getProperty("author"),
|
|
||||||
url = properties.getProperty("url"),
|
|
||||||
description = properties.getProperty("description"),
|
|
||||||
status = installedPlugins.find(_.id == properties.getProperty("id")) match {
|
|
||||||
case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable"
|
|
||||||
case Some(x) => "installed"
|
|
||||||
case None => "available"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else Nil
|
|
||||||
}
|
|
||||||
} else Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object SystemSettingsControllerBase {
|
|
||||||
case class AvailablePlugin(repository: String, id: String, version: String,
|
|
||||||
author: String, url: String, description: String, status: String)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import plugin.PluginSystem._
|
|
||||||
import java.sql.Connection
|
|
||||||
|
|
||||||
trait Plugin {
|
|
||||||
val id: String
|
|
||||||
val version: String
|
|
||||||
val author: String
|
|
||||||
val url: String
|
|
||||||
val description: String
|
|
||||||
|
|
||||||
def repositoryMenus : List[RepositoryMenu]
|
|
||||||
def globalMenus : List[GlobalMenu]
|
|
||||||
def repositoryActions : List[RepositoryAction]
|
|
||||||
def globalActions : List[Action]
|
|
||||||
def javaScripts : List[JavaScript]
|
|
||||||
}
|
|
||||||
|
|
||||||
object PluginConnectionHolder {
|
|
||||||
val threadLocal = new ThreadLocal[Connection]
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
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.{IOUtils, FileUtils}
|
|
||||||
import Security._
|
|
||||||
import service.PluginService
|
|
||||||
import model.Profile._
|
|
||||||
import profile.simple._
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.sql.Connection
|
|
||||||
import app.Context
|
|
||||||
import service.RepositoryService.RepositoryInfo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides extension points to plug-ins.
|
|
||||||
*/
|
|
||||||
object PluginSystem extends PluginService {
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(PluginSystem.getClass)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
def plugins: List[Plugin] = pluginsMap.values.toList
|
|
||||||
|
|
||||||
def uninstall(id: String)(implicit session: Session): Unit = {
|
|
||||||
pluginsMap.remove(id)
|
|
||||||
|
|
||||||
// Delete from PLUGIN table
|
|
||||||
deletePlugin(id)
|
|
||||||
|
|
||||||
// Drop tables
|
|
||||||
val pluginDir = new java.io.File(PluginHome)
|
|
||||||
val sqlFile = new java.io.File(pluginDir, s"${id}/sql/drop.sql")
|
|
||||||
if(sqlFile.exists){
|
|
||||||
val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8")
|
|
||||||
using(session.conn.createStatement()){ stmt =>
|
|
||||||
stmt.executeUpdate(sql)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def repositories: List[PluginRepository] = repositoriesList.toList
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
|
|
||||||
*/
|
|
||||||
def init()(implicit session: Session): 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 =>
|
|
||||||
installPlugin(dir.getName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add default plugin repositories
|
|
||||||
repositoriesList += PluginRepository("central", "https://github.com/takezoe/gitbucket_plugins.git")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Method name seems to not so good.
|
|
||||||
def installPlugin(id: String)(implicit session: Session): Unit = {
|
|
||||||
val pluginHome = new java.io.File(PluginHome)
|
|
||||||
val pluginDir = new java.io.File(pluginHome, id)
|
|
||||||
|
|
||||||
val scalaFile = new java.io.File(pluginDir, "plugin.scala")
|
|
||||||
if(scalaFile.exists && scalaFile.isFile){
|
|
||||||
val properties = new java.util.Properties()
|
|
||||||
using(new java.io.FileInputStream(new java.io.File(pluginDir, "plugin.properties"))){ in =>
|
|
||||||
properties.load(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
val pluginId = properties.getProperty("id")
|
|
||||||
val version = properties.getProperty("version")
|
|
||||||
val author = properties.getProperty("author")
|
|
||||||
val url = properties.getProperty("url")
|
|
||||||
val description = properties.getProperty("description")
|
|
||||||
|
|
||||||
val source = s"""
|
|
||||||
|val id = "${pluginId}"
|
|
||||||
|val version = "${version}"
|
|
||||||
|val author = "${author}"
|
|
||||||
|val url = "${url}"
|
|
||||||
|val description = "${description}"
|
|
||||||
""".stripMargin + FileUtils.readFileToString(scalaFile, "UTF-8")
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Compile and eval Scala source code
|
|
||||||
ScalaPlugin.eval(pluginDir.listFiles.filter(_.getName.endsWith(".scala.html")).map { file =>
|
|
||||||
ScalaPlugin.compileTemplate(
|
|
||||||
id.replace("-", ""),
|
|
||||||
file.getName.stripSuffix(".scala.html"),
|
|
||||||
IOUtils.toString(new FileInputStream(file)))
|
|
||||||
}.mkString("\n") + source)
|
|
||||||
|
|
||||||
// Migrate database
|
|
||||||
val plugin = getPlugin(pluginId)
|
|
||||||
if(plugin.isEmpty){
|
|
||||||
registerPlugin(model.Plugin(pluginId, version))
|
|
||||||
migrate(session.conn, pluginId, "0.0")
|
|
||||||
} else {
|
|
||||||
updatePlugin(model.Plugin(pluginId, version))
|
|
||||||
migrate(session.conn, pluginId, plugin.get.version)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case e: Throwable => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Should PluginSystem provide a way to migrate resources other than H2?
|
|
||||||
private def migrate(conn: Connection, pluginId: String, current: String): Unit = {
|
|
||||||
val pluginDir = new java.io.File(PluginHome)
|
|
||||||
|
|
||||||
// TODO Is ot possible to use this migration system in GitBucket migration?
|
|
||||||
val dim = current.split("\\.")
|
|
||||||
val currentVersion = Version(dim(0).toInt, dim(1).toInt)
|
|
||||||
|
|
||||||
val sqlDir = new java.io.File(pluginDir, s"${pluginId}/sql")
|
|
||||||
if(sqlDir.exists && sqlDir.isDirectory){
|
|
||||||
sqlDir.listFiles.filter(_.getName.endsWith(".sql")).map { file =>
|
|
||||||
val array = file.getName.replaceFirst("\\.sql", "").split("_")
|
|
||||||
Version(array(0).toInt, array(1).toInt)
|
|
||||||
}
|
|
||||||
.sorted.reverse.takeWhile(_ > currentVersion)
|
|
||||||
.reverse.foreach { version =>
|
|
||||||
val sqlFile = new java.io.File(pluginDir, s"${pluginId}/sql/${version.major}_${version.minor}.sql")
|
|
||||||
val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8")
|
|
||||||
using(conn.createStatement()){ stmt =>
|
|
||||||
stmt.executeUpdate(sql)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Version(major: Int, minor: Int) extends Ordered[Version] {
|
|
||||||
|
|
||||||
override def compare(that: Version): Int = {
|
|
||||||
if(major != that.major){
|
|
||||||
major.compare(that.major)
|
|
||||||
} else{
|
|
||||||
minor.compare(that.minor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def displayString: String = major + "." + minor
|
|
||||||
}
|
|
||||||
|
|
||||||
def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList
|
|
||||||
def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList
|
|
||||||
def repositoryActions : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList
|
|
||||||
def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList
|
|
||||||
def javaScripts : List[JavaScript] = pluginsMap.values.flatMap(_.javaScripts).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(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context) => Any)
|
|
||||||
case class RepositoryAction(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any)
|
|
||||||
case class Button(label: String, href: String)
|
|
||||||
case class JavaScript(filter: String => Boolean, script: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the plugin is updatable.
|
|
||||||
*/
|
|
||||||
def isUpdatable(oldVersion: String, newVersion: String): Boolean = {
|
|
||||||
if(oldVersion == newVersion){
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
val dim1 = oldVersion.split("\\.").map(_.toInt)
|
|
||||||
val dim2 = newVersion.split("\\.").map(_.toInt)
|
|
||||||
dim1.zip(dim2).foreach { case (a, b) =>
|
|
||||||
if(a < b){
|
|
||||||
return true
|
|
||||||
} else if(a > b){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import util.Directory._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.quartz.{Scheduler, JobExecutionContext, Job}
|
|
||||||
import org.quartz.JobBuilder._
|
|
||||||
import org.quartz.TriggerBuilder._
|
|
||||||
import org.quartz.SimpleScheduleBuilder._
|
|
||||||
|
|
||||||
class PluginUpdateJob extends Job {
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[PluginUpdateJob])
|
|
||||||
private var failedCount = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone or pull all plugin repositories
|
|
||||||
*
|
|
||||||
* TODO Support plugin repository access through the proxy server
|
|
||||||
*/
|
|
||||||
override def execute(context: JobExecutionContext): Unit = {
|
|
||||||
try {
|
|
||||||
if(failedCount > 3){
|
|
||||||
logger.error("Skip plugin information updating because failed count is over limit")
|
|
||||||
} else {
|
|
||||||
logger.info("Start plugin information updating")
|
|
||||||
PluginSystem.repositories.foreach { repository =>
|
|
||||||
logger.info(s"Updating ${repository.id}: ${repository.url}...")
|
|
||||||
val dir = getPluginCacheDir()
|
|
||||||
val repo = new java.io.File(dir, repository.id)
|
|
||||||
if(repo.exists){
|
|
||||||
// pull if the repository is already cloned
|
|
||||||
Git.open(repo).pull().call()
|
|
||||||
} else {
|
|
||||||
// clone if the repository is not exist
|
|
||||||
Git.cloneRepository().setURI(repository.url).setDirectory(repo).call()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("End plugin information updating")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case e: Exception => {
|
|
||||||
failedCount = failedCount + 1
|
|
||||||
logger.error("Failed to update plugin information", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object PluginUpdateJob {
|
|
||||||
|
|
||||||
def schedule(scheduler: Scheduler): Unit = {
|
|
||||||
val job = newJob(classOf[PluginUpdateJob])
|
|
||||||
.withIdentity("pluginUpdateJob")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val trigger = newTrigger()
|
|
||||||
.withIdentity("pluginUpdateTrigger")
|
|
||||||
.startNow()
|
|
||||||
.withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
scheduler.scheduleJob(job, trigger)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
|
||||||
import app.Context
|
|
||||||
import plugin.PluginSystem._
|
|
||||||
import plugin.PluginSystem.RepositoryMenu
|
|
||||||
import plugin.Security._
|
|
||||||
import service.RepositoryService.RepositoryInfo
|
|
||||||
import scala.reflect.runtime.currentMirror
|
|
||||||
import scala.tools.reflect.ToolBox
|
|
||||||
import play.twirl.compiler.TwirlCompiler
|
|
||||||
import scala.io.Codec
|
|
||||||
|
|
||||||
// TODO This is a sample implementation for Scala based plug-ins.
|
|
||||||
class ScalaPlugin(val id: String, val version: String,
|
|
||||||
val author: String, val url: String, val description: String) extends Plugin {
|
|
||||||
|
|
||||||
private val repositoryMenuList = ListBuffer[RepositoryMenu]()
|
|
||||||
private val globalMenuList = ListBuffer[GlobalMenu]()
|
|
||||||
private val repositoryActionList = ListBuffer[RepositoryAction]()
|
|
||||||
private val globalActionList = ListBuffer[Action]()
|
|
||||||
private val javaScriptList = ListBuffer[JavaScript]()
|
|
||||||
|
|
||||||
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
|
|
||||||
def globalMenus : List[GlobalMenu] = globalMenuList.toList
|
|
||||||
def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
|
|
||||||
def globalActions : List[Action] = globalActionList.toList
|
|
||||||
def javaScripts : List[JavaScript] = javaScriptList.toList
|
|
||||||
|
|
||||||
def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
|
|
||||||
repositoryMenuList += RepositoryMenu(label, name, url, icon, condition)
|
|
||||||
}
|
|
||||||
|
|
||||||
def addGlobalMenu(label: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
|
|
||||||
globalMenuList += GlobalMenu(label, url, icon, condition)
|
|
||||||
}
|
|
||||||
|
|
||||||
def addGlobalAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = {
|
|
||||||
globalActionList += Action(method, path, security, function)
|
|
||||||
}
|
|
||||||
|
|
||||||
def addRepositoryAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = {
|
|
||||||
repositoryActionList += RepositoryAction(method, path, security, function)
|
|
||||||
}
|
|
||||||
|
|
||||||
def addJavaScript(filter: String => Boolean, script: String): Unit = {
|
|
||||||
javaScriptList += JavaScript(filter, script)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object ScalaPlugin {
|
|
||||||
|
|
||||||
def define(id: String, version: String, author: String, url: String, description: String)
|
|
||||||
= new ScalaPlugin(id, version, author, url, description)
|
|
||||||
|
|
||||||
def eval(source: String): Any = {
|
|
||||||
val toolbox = currentMirror.mkToolBox()
|
|
||||||
val tree = toolbox.parse(source)
|
|
||||||
toolbox.eval(tree)
|
|
||||||
}
|
|
||||||
|
|
||||||
def compileTemplate(packageName: String, name: String, source: String): String = {
|
|
||||||
val result = TwirlCompiler.parseAndGenerateCodeNewParser(
|
|
||||||
Array(packageName, name),
|
|
||||||
source.getBytes("UTF-8"),
|
|
||||||
Codec(scala.util.Properties.sourceEncoding),
|
|
||||||
"",
|
|
||||||
"play.twirl.api.HtmlFormat.Appendable",
|
|
||||||
"play.twirl.api.HtmlFormat",
|
|
||||||
"",
|
|
||||||
false)
|
|
||||||
|
|
||||||
result.replaceFirst("package .*", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines enum case classes to specify permission for actions which is provided by plugin.
|
|
||||||
*/
|
|
||||||
object Security {
|
|
||||||
|
|
||||||
sealed trait Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All users and guests
|
|
||||||
*/
|
|
||||||
case class All() extends Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only signed-in users
|
|
||||||
*/
|
|
||||||
case class Login() extends Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only repository owner and collaborators
|
|
||||||
*/
|
|
||||||
case class Member() extends Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only repository owner and managers of group repository
|
|
||||||
*/
|
|
||||||
case class Owner() extends Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only administrators
|
|
||||||
*/
|
|
||||||
case class Admin() extends Security
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import java.sql.PreparedStatement
|
|
||||||
import play.twirl.api.Html
|
|
||||||
import util.ControlUtil._
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
|
|
||||||
package object plugin {
|
|
||||||
|
|
||||||
case class Redirect(path: String)
|
|
||||||
case class Fragment(html: Html)
|
|
||||||
case class RawData(contentType: String, content: Array[Byte])
|
|
||||||
|
|
||||||
object db {
|
|
||||||
// TODO labelled place holder support
|
|
||||||
def select(sql: String, params: Any*): Seq[Map[String, String]] = {
|
|
||||||
defining(PluginConnectionHolder.threadLocal.get){ conn =>
|
|
||||||
using(conn.prepareStatement(sql)){ stmt =>
|
|
||||||
setParams(stmt, params: _*)
|
|
||||||
using(stmt.executeQuery()){ rs =>
|
|
||||||
val list = new ListBuffer[Map[String, String]]()
|
|
||||||
while(rs.next){
|
|
||||||
defining(rs.getMetaData){ meta =>
|
|
||||||
val map = Range(1, meta.getColumnCount + 1).map { i =>
|
|
||||||
val name = meta.getColumnName(i)
|
|
||||||
(name, rs.getString(name))
|
|
||||||
}.toMap
|
|
||||||
list += map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO labelled place holder support
|
|
||||||
def update(sql: String, params: Any*): Int = {
|
|
||||||
defining(PluginConnectionHolder.threadLocal.get){ conn =>
|
|
||||||
using(conn.prepareStatement(sql)){ stmt =>
|
|
||||||
setParams(stmt, params: _*)
|
|
||||||
stmt.executeUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def setParams(stmt: PreparedStatement, params: Any*): Unit = {
|
|
||||||
params.zipWithIndex.foreach { case (p, i) =>
|
|
||||||
p match {
|
|
||||||
case x: String => stmt.setString(i + 1, x)
|
|
||||||
case x: Int => stmt.setInt(i + 1, x)
|
|
||||||
case x: Boolean => stmt.setBoolean(i + 1, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -195,7 +195,7 @@ object SystemSettingsService {
|
|||||||
else value
|
else value
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO temporary flag
|
// // TODO temporary flag
|
||||||
val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean
|
// val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import util.ControlUtil._
|
|||||||
import util.JDBCUtil._
|
import util.JDBCUtil._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import util.Directory
|
import util.Directory
|
||||||
import plugin.PluginUpdateJob
|
|
||||||
import service.SystemSettingsService
|
|
||||||
|
|
||||||
object AutoUpdate {
|
object AutoUpdate {
|
||||||
|
|
||||||
@@ -197,11 +195,10 @@ object AutoUpdate {
|
|||||||
* Update database schema automatically in the context initializing.
|
* Update database schema automatically in the context initializing.
|
||||||
*/
|
*/
|
||||||
class AutoUpdateListener extends ServletContextListener {
|
class AutoUpdateListener extends ServletContextListener {
|
||||||
import org.quartz.impl.StdSchedulerFactory
|
|
||||||
import AutoUpdate._
|
import AutoUpdate._
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
||||||
private val scheduler = StdSchedulerFactory.getDefaultScheduler
|
// private val scheduler = StdSchedulerFactory.getDefaultScheduler
|
||||||
|
|
||||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||||
@@ -236,31 +233,9 @@ class AutoUpdateListener extends ServletContextListener {
|
|||||||
}
|
}
|
||||||
logger.debug("End schema update")
|
logger.debug("End schema update")
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SystemSettingsService.enablePluginSystem){
|
|
||||||
getDatabase(context).withSession { implicit session =>
|
|
||||||
logger.debug("Starting plugin system...")
|
|
||||||
try {
|
|
||||||
plugin.PluginSystem.init()
|
|
||||||
|
|
||||||
scheduler.start()
|
|
||||||
PluginUpdateJob.schedule(scheduler)
|
|
||||||
logger.debug("PluginUpdateJob is started.")
|
|
||||||
|
|
||||||
logger.debug("Plugin system is initialized.")
|
|
||||||
} catch {
|
|
||||||
case ex: Throwable => {
|
|
||||||
logger.error("Failed to initialize plugin system", ex)
|
|
||||||
ex.printStackTrace()
|
|
||||||
throw ex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||||
scheduler.shutdown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getConnection(servletContext: ServletContext): Connection =
|
private def getConnection(servletContext: ServletContext): Connection =
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
package servlet
|
|
||||||
|
|
||||||
import javax.servlet._
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
|
||||||
import org.apache.commons.io.IOUtils
|
|
||||||
import play.twirl.api.Html
|
|
||||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
|
||||||
import model.{Account, Session}
|
|
||||||
import util.{JGitUtil, Keys}
|
|
||||||
import plugin.{RawData, Fragment, PluginConnectionHolder, Redirect}
|
|
||||||
import service.RepositoryService.RepositoryInfo
|
|
||||||
import plugin.Security._
|
|
||||||
|
|
||||||
class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
|
|
||||||
|
|
||||||
def init(config: FilterConfig) = {}
|
|
||||||
|
|
||||||
def destroy(): Unit = {}
|
|
||||||
|
|
||||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
|
||||||
(req, res) match {
|
|
||||||
case (request: HttpServletRequest, response: HttpServletResponse) => {
|
|
||||||
Database(req.getServletContext) withTransaction { implicit session =>
|
|
||||||
val path = request.getRequestURI.substring(request.getServletContext.getContextPath.length)
|
|
||||||
if(!processGlobalAction(path, request, response) && !processRepositoryAction(path, request, response)){
|
|
||||||
chain.doFilter(req, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
|
|
||||||
(implicit session: Session): Boolean = {
|
|
||||||
plugin.PluginSystem.globalActions.find(x =>
|
|
||||||
x.method.toLowerCase == request.getMethod.toLowerCase && path.matches(x.path)
|
|
||||||
).map { action =>
|
|
||||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
|
||||||
val systemSettings = loadSystemSettings()
|
|
||||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
|
||||||
|
|
||||||
if(authenticate(action.security, context)){
|
|
||||||
val result = try {
|
|
||||||
PluginConnectionHolder.threadLocal.set(session.conn)
|
|
||||||
action.function(request, response, context)
|
|
||||||
} finally {
|
|
||||||
PluginConnectionHolder.threadLocal.remove()
|
|
||||||
}
|
|
||||||
processActionResult(result, request, response, context)
|
|
||||||
} else {
|
|
||||||
// TODO NotFound or Error?
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} getOrElse false
|
|
||||||
}
|
|
||||||
|
|
||||||
private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
|
|
||||||
(implicit session: Session): Boolean = {
|
|
||||||
val elements = path.split("/")
|
|
||||||
if(elements.length > 3){
|
|
||||||
val owner = elements(1)
|
|
||||||
val name = elements(2)
|
|
||||||
val remain = elements.drop(3).mkString("/", "/", "")
|
|
||||||
|
|
||||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
|
||||||
val systemSettings = loadSystemSettings()
|
|
||||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
|
||||||
|
|
||||||
getRepository(owner, name, systemSettings.baseUrl(request)).flatMap { repository =>
|
|
||||||
plugin.PluginSystem.repositoryActions.find(x => remain.matches(x.path)).map { action =>
|
|
||||||
if(authenticate(action.security, context, repository)){
|
|
||||||
val result = try {
|
|
||||||
PluginConnectionHolder.threadLocal.set(session.conn)
|
|
||||||
action.function(request, response, context, repository)
|
|
||||||
} finally {
|
|
||||||
PluginConnectionHolder.threadLocal.remove()
|
|
||||||
}
|
|
||||||
processActionResult(result, request, response, context)
|
|
||||||
} else {
|
|
||||||
// TODO NotFound or Error?
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} getOrElse false
|
|
||||||
} else false
|
|
||||||
}
|
|
||||||
|
|
||||||
private def processActionResult(result: Any, request: HttpServletRequest, response: HttpServletResponse,
|
|
||||||
context: app.Context): Unit = {
|
|
||||||
result match {
|
|
||||||
case null|None => renderError(request, response, context, 404)
|
|
||||||
case x: String => renderGlobalHtml(request, response, context, x)
|
|
||||||
case Some(x: String) => renderGlobalHtml(request, response, context, x)
|
|
||||||
case x: Html => renderGlobalHtml(request, response, context, x.toString)
|
|
||||||
case Some(x: Html) => renderGlobalHtml(request, response, context, x.toString)
|
|
||||||
case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
|
|
||||||
case Some(x: Fragment) => renderFragmentHtml(request, response, context, x.html.toString)
|
|
||||||
case x: RawData => renderRawData(request, response, context, x)
|
|
||||||
case Some(x: RawData) => renderRawData(request, response, context, x)
|
|
||||||
case x: Redirect => response.sendRedirect(x.path)
|
|
||||||
case Some(x: Redirect) => response.sendRedirect(x.path)
|
|
||||||
case x: AnyRef => renderJson(request, response, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authentication for global action
|
|
||||||
*/
|
|
||||||
private def authenticate(security: Security, context: app.Context)(implicit session: Session): Boolean = {
|
|
||||||
// Global Action
|
|
||||||
security match {
|
|
||||||
case All() => true
|
|
||||||
case Login() => context.loginAccount.isDefined
|
|
||||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
|
||||||
case _ => false // TODO throw Exception?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticate for repository action
|
|
||||||
*/
|
|
||||||
private def authenticate(security: Security, context: app.Context, repository: RepositoryInfo)(implicit session: Session): Boolean = {
|
|
||||||
if(repository.repository.isPrivate){
|
|
||||||
// Private Repository
|
|
||||||
security match {
|
|
||||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
|
||||||
case Owner() => context.loginAccount.exists { account =>
|
|
||||||
account.userName == repository.owner ||
|
|
||||||
getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
|
|
||||||
}
|
|
||||||
case _ => context.loginAccount.exists { account =>
|
|
||||||
account.isAdmin || account.userName == repository.owner ||
|
|
||||||
getCollaborators(repository.owner, repository.name).contains(account.userName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Public Repository
|
|
||||||
security match {
|
|
||||||
case All() => true
|
|
||||||
case Login() => context.loginAccount.isDefined
|
|
||||||
case Owner() => context.loginAccount.exists { account =>
|
|
||||||
account.userName == repository.owner ||
|
|
||||||
getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
|
|
||||||
}
|
|
||||||
case Member() => context.loginAccount.exists { account =>
|
|
||||||
account.userName == repository.owner ||
|
|
||||||
getCollaborators(repository.owner, repository.name).contains(account.userName)
|
|
||||||
}
|
|
||||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderError(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, error: Int): Unit = {
|
|
||||||
response.sendError(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
|
|
||||||
response.setContentType("text/html; charset=UTF-8")
|
|
||||||
val html = _root_.html.main("GitBucket", None)(Html(body))(context)
|
|
||||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderRepositoryHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, repository: RepositoryInfo, body: String): Unit = {
|
|
||||||
response.setContentType("text/html; charset=UTF-8")
|
|
||||||
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(body))(context))(context) // TODO specify active side menu
|
|
||||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderFragmentHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
|
|
||||||
response.setContentType("text/html; charset=UTF-8")
|
|
||||||
IOUtils.write(body.getBytes("UTF-8"), response.getOutputStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderRawData(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, rawData: RawData): Unit = {
|
|
||||||
response.setContentType(rawData.contentType)
|
|
||||||
IOUtils.write(rawData.content, response.getOutputStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def renderJson(request: HttpServletRequest, response: HttpServletResponse, obj: AnyRef): Unit = {
|
|
||||||
import org.json4s._
|
|
||||||
import org.json4s.jackson.Serialization
|
|
||||||
import org.json4s.jackson.Serialization.write
|
|
||||||
implicit val formats = Serialization.formats(NoTypeHints)
|
|
||||||
|
|
||||||
val json = write(obj)
|
|
||||||
|
|
||||||
response.setContentType("application/json; charset=UTF-8")
|
|
||||||
IOUtils.write(json.getBytes("UTF-8"), response.getOutputStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -11,11 +11,6 @@
|
|||||||
<li@if(active=="system"){ class="active"}>
|
<li@if(active=="system"){ class="active"}>
|
||||||
<a href="@path/admin/system">System Settings</a>
|
<a href="@path/admin/system">System Settings</a>
|
||||||
</li>
|
</li>
|
||||||
@if(service.SystemSettingsService.enablePluginSystem){
|
|
||||||
<li@if(active=="plugins"){ class="active"}>
|
|
||||||
<a href="@path/admin/plugins">Plugins</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
<li>
|
<li>
|
||||||
<a href="@path/console/login.jsp">H2 Console</a>
|
<a href="@path/console/login.jsp">H2 Console</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
@(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>Version</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>@plugin.version</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-success" value="Install selected plugins"/>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
$('#install-plugins').click(function(){
|
|
||||||
return confirm('Selected plugin will be installed. Are you sure?');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
@()(implicit context: app.Context)
|
|
||||||
@import context._
|
|
||||||
@import view.helpers._
|
|
||||||
@html.main("JavaScript Console"){
|
|
||||||
@admin.html.menu("plugins"){
|
|
||||||
@tab("console")
|
|
||||||
<form method="POST">
|
|
||||||
<div class="box">
|
|
||||||
<div class="box-header">JavaScript Console</div>
|
|
||||||
<div class="box-content">
|
|
||||||
<div id="editor" style="width: 100%; height: 400px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset>
|
|
||||||
<input type="button" id="evaluate" class="btn btn-success" value="Evaluate"/>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<script src="@assets/vendors/ace/ace.js" type="text/javascript" charset="utf-8"></script>
|
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
var editor = ace.edit("editor");
|
|
||||||
editor.setTheme("ace/theme/monokai");
|
|
||||||
editor.getSession().setMode("ace/mode/javascript");
|
|
||||||
|
|
||||||
$('#evaluate').click(function(){
|
|
||||||
$.post('@path/admin/plugins/console', {
|
|
||||||
script: editor.getValue()
|
|
||||||
}, function(data){
|
|
||||||
alert('Success: ' + data);
|
|
||||||
}).fail(function(error){
|
|
||||||
alert(error.statusText);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
@(plugins: List[plugin.Plugin],
|
|
||||||
updatablePlugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context)
|
|
||||||
@import context._
|
|
||||||
@import view.helpers._
|
|
||||||
@html.main("Plugins"){
|
|
||||||
@admin.html.menu("plugins"){
|
|
||||||
@tab("installed")
|
|
||||||
<form method="POST" validate="true">
|
|
||||||
<table class="table table-bordered">
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Version</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>
|
|
||||||
@plugin.version
|
|
||||||
@updatablePlugins.find(_.id == plugin.id).map { x =>
|
|
||||||
(@x.version is available)
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td><a href="@plugin.url">@plugin.author</a></td>
|
|
||||||
<td>@plugin.description</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
<input type="submit" id="update-plugins" class="btn btn-success" value="Update selected plugins" formaction="@path/admin/plugins/_update"/>
|
|
||||||
<input type="submit" id="delete-plugins" class="btn btn-danger" value="Uninstall selected plugins" formaction="@path/admin/plugins/_delete"/>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
$('#update-plugins').click(function(){
|
|
||||||
return confirm('Selected plugin will be updated. Are you sure?');
|
|
||||||
});
|
|
||||||
$('#delete-plugins').click(function(){
|
|
||||||
return confirm('Selected plugin will be removed permanently. Are you sure?');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
@(active: String)(implicit context: app.Context)
|
|
||||||
@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">Available plugins</a></li>
|
|
||||||
@*
|
|
||||||
<li@if(active == "console" ){ class="active"}><a href="@path/admin/plugins/console">JavaScript console</a></li>
|
|
||||||
*@
|
|
||||||
</ul>
|
|
||||||
@@ -62,21 +62,11 @@
|
|||||||
<li><a href="@path/groups/new">New group</a></li>
|
<li><a href="@path/groups/new">New group</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<a href="@url(loginAccount.get.userName)/_edit" class="menu" data-toggle="tooltip" data-placement="bottom" title="Account settings"><i class="icon-user"></i></a>
|
<a href="@url(loginAccount.get.userName)/_edit" class="menu" data-toggle="tooltip" data-placement="bottom" title="Account settings"><i class="icon-user"></i></a>
|
||||||
@plugin.PluginSystem.globalMenus.map { menu =>
|
|
||||||
@if(menu.condition(context)){
|
|
||||||
<a href="@menu.url" class="menu" data-toggle="tooltip" data-placement="bottom" title="@menu.label">@if(menu.icon.nonEmpty){<img src="@menu.icon" class="plugin-global-menu"/>} else {@menu.label}</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if(loginAccount.get.isAdmin){
|
@if(loginAccount.get.isAdmin){
|
||||||
<a href="@path/admin/users" class="menu" data-toggle="tooltip" data-placement="bottom" title="Administration"><i class="icon-wrench"></i></a>
|
<a href="@path/admin/users" class="menu" data-toggle="tooltip" data-placement="bottom" title="Administration"><i class="icon-wrench"></i></a>
|
||||||
}
|
}
|
||||||
<a href="@path/signout" class="menu-last" data-toggle="tooltip" data-placement="bottom" title="Sign out"><i class="icon-share-alt"></i></a>
|
<a href="@path/signout" class="menu-last" data-toggle="tooltip" data-placement="bottom" title="Sign out"><i class="icon-share-alt"></i></a>
|
||||||
} else {
|
} else {
|
||||||
@plugin.PluginSystem.globalMenus.map { menu =>
|
|
||||||
@if(menu.condition(context)){
|
|
||||||
<a href="@menu.url" class="menu" data-toggle="tooltip" data-placement="bottom" title="@menu.label">@if(menu.icon.nonEmpty){<img src="@menu.icon" class="plugin-global-menu"/>} else {@menu.label}</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-last" id="signin">Sign in</a>
|
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-last" id="signin">Sign in</a>
|
||||||
}
|
}
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
@@ -90,9 +80,6 @@
|
|||||||
$('#search').submit(function(){
|
$('#search').submit(function(){
|
||||||
return $.trim($(this).find('input[name=query]').val()) != '';
|
return $.trim($(this).find('input[name=query]').val()) != '';
|
||||||
});
|
});
|
||||||
@plugin.PluginSystem.javaScripts.filter(_.filter(context.currentPath)).map { js =>
|
|
||||||
@Html(js.script)
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -74,11 +74,6 @@
|
|||||||
@sidemenu("/issues", "issues", "Issues", repository.issueCount)
|
@sidemenu("/issues", "issues", "Issues", repository.issueCount)
|
||||||
@sidemenu("/pulls" , "pulls" , "Pull Requests", repository.pullCount)
|
@sidemenu("/pulls" , "pulls" , "Pull Requests", repository.pullCount)
|
||||||
@sidemenu("/wiki" , "wiki" , "Wiki")
|
@sidemenu("/wiki" , "wiki" , "Wiki")
|
||||||
@plugin.PluginSystem.repositoryMenus.map { menu =>
|
|
||||||
@if(menu.condition(context)){
|
|
||||||
@sidemenuPlugin(menu.url, menu.label, menu.label, menu.icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
||||||
@sidemenu("/settings", "settings", "Settings")
|
@sidemenu("/settings", "settings", "Settings")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user