Implement displaying result as a scrollable table

This commit is contained in:
Naoki Takezoe
2017-12-30 01:01:22 +09:00
parent ae9ee4779f
commit a64741011c
9 changed files with 103 additions and 55 deletions

View File

@@ -43,25 +43,11 @@ abstract class ControllerBase extends ScalatraFilter
} }
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try { override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
val httpRequest = request.asInstanceOf[HttpServletRequest] val httpRequest = request.asInstanceOf[HttpServletRequest]
val httpResponse = response.asInstanceOf[HttpServletResponse] val context = request.getServletContext.getContextPath
val context = request.getServletContext.getContextPath val path = httpRequest.getRequestURI.substring(context.length)
val path = httpRequest.getRequestURI.substring(context.length)
if(path.startsWith("/console/")){ if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
val baseUrl = this.baseUrl(httpRequest)
if(account == null){
// Redirect to login form
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
} else if(account.isAdmin){
// H2 Console (administrators only)
chain.doFilter(request, response)
} else {
// Redirect to dashboard
httpResponse.sendRedirect(baseUrl + "/")
}
} else if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){
// Git repository // Git repository
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {

View File

@@ -17,6 +17,8 @@ import org.apache.commons.io.IOUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import com.github.zafarkhaja.semver.{Version => Semver} import com.github.zafarkhaja.semver.{Version => Semver}
import gitbucket.core.GitBucketCoreModule import gitbucket.core.GitBucketCoreModule
import org.scalatra._
import org.json4s.jackson.Serialization
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@@ -176,6 +178,42 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
html.dbviewer(tables) html.dbviewer(tables)
}) })
post("/admin/dbviewer/_query")(adminOnly {
contentType = formats("json")
params.get("query").collectFirst { case query if query.trim.nonEmpty =>
val trimmedQuery = query.trim
if(trimmedQuery.nonEmpty){
try {
val conn = request2Session(request).conn
using(conn.prepareStatement(query)){ stmt =>
if(trimmedQuery.toUpperCase.startsWith("SELECT")){
using(stmt.executeQuery()){ rs =>
val meta = rs.getMetaData
val columns = for(i <- 1 to meta.getColumnCount) yield {
meta.getColumnName(i)
}
val result = ListBuffer[Map[String, String]]()
while(rs.next()){
val row = columns.map { columnName =>
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
}.toMap
result += row
}
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
}
} else {
val rows = stmt.executeUpdate()
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows)))
}
}
} catch {
case e: Exception =>
Ok(Serialization.write(Map("type" -> "error", "message" -> e.toString)))
}
}
} getOrElse Ok(Serialization.write(Map("type" -> "error", "message" -> "query is empty")))
})
get("/admin/system")(adminOnly { get("/admin/system")(adminOnly {
html.system(flash.get("info")) html.system(flash.get("info"))
}) })

View File

@@ -37,7 +37,7 @@ class CompositeScalatraFilter extends Filter {
} }
if(!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") && if(!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
!checkPath.startsWith("/plugin-assets/") && !checkPath.startsWith("/console/")){ !checkPath.startsWith("/plugin-assets/")){
filters filters
.filter { case (_, path) => .filter { case (_, path) =>
val start = path.replaceFirst("/\\*$", "/") val start = path.replaceFirst("/\\*$", "/")

View File

@@ -23,7 +23,7 @@ class TransactionFilter extends Filter {
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
val servletPath = req.asInstanceOf[HttpServletRequest].getServletPath() val servletPath = req.asInstanceOf[HttpServletRequest].getServletPath()
if(servletPath.startsWith("/assets/") || servletPath == "/console" || servletPath == "/git" || servletPath == "/git-lfs"){ if(servletPath.startsWith("/assets/") || servletPath == "/git" || servletPath == "/git-lfs"){
// assets and git-lfs don't need transaction // assets and git-lfs don't need transaction
chain.doFilter(req, res) chain.doFilter(req, res)
} else { } else {

View File

@@ -1,17 +1,18 @@
@(tables: Seq[gitbucket.core.controller.Table])(implicit context: gitbucket.core.controller.Context) @(tables: Seq[gitbucket.core.controller.Table])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Database viewer") { @gitbucket.core.html.main("Database viewer") {
@gitbucket.core.admin.html.menu("dbviewer") { @gitbucket.core.admin.html.menu("dbviewer") {
<div> <div class="container">
<div class="col-md-3"> <div class="col-md-3">
<div id="table-tree"> <div id="table-tree">
<ul> <ul>
@tables.map { table => @tables.map { table =>
<li>@table.name <li data-jstree='{"icon":"@context.path/assets/common/images/table.gif"}'><a href="javascript:void(0);" class="table-link">@table.name</a>
<ul> <ul>
@table.columns.map { column => @table.columns.map { column =>
<li>@column.name</li> <li data-jstree='{"icon":"@context.path/assets/common/images/column.gif"}'>@column.name</li>
} }
</ul> </ul>
</li> </li>
} }
</ul> </ul>
@@ -27,19 +28,60 @@
</div> </div>
} }
} }
<script src="@gitbucket.core.view.helpers.assets("/vendors/ace/ace.js")" type="text/javascript" charset="utf-8"></script> <script src="@helpers.assets("/vendors/ace/ace.js")" type="text/javascript" charset="utf-8"></script>
<script src="@gitbucket.core.view.helpers.assets("/vendors/vakata-jstree-3.3.4/jstree.min.js")" type="text/javascript" charset="utf-8"></script> <script src="@helpers.assets("/vendors/vakata-jstree-3.3.4/jstree.min.js")" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" href="@gitbucket.core.view.helpers.assets("/vendors/vakata-jstree-3.3.4/themes/default/style.min.css")" /> <link rel="stylesheet" href="@helpers.assets("/vendors/vakata-jstree-3.3.4/themes/default/style.min.css")" />
<script> <script>
$(function(){ $(function(){
$('#editor').text($('#initial').val()); $('#editor').text($('#initial').val());
var editor = ace.edit("editor"); var editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai"); editor.setTheme("ace/theme/monokai");
editor.getSession().setMode("ace/mode/sql");
$('#table-tree').jstree(); $('#table-tree').jstree();
$('.table-link').click(function(e){
var query = editor.getValue();
if(query != ''){
query = query + '\n';
}
console.log(e);
editor.setValue(query + 'SELECT * FROM ' + $(e.target).text());
});
$('#run-query').click(function(){ $('#run-query').click(function(){
console.log(editor.getValue()); console.log(editor.getValue());
$.post('@context.path/admin/dbviewer/_query', { query: editor.getValue() }, function(data){
if(data.type == "query"){
var table = $('<table class="table table-bordered table-hover table-scroll">');
var header = $('<tr>');
$.each(data.columns, function(i, column){
header.append($('<th>').text(column));
});
table.append($('<thead>').append(header));
var body = $('<tbody>');
$.each(data.rows, function(i, rs){
var row = $('<tr>');
$.each(data.columns, function(i, column){
row.append($('<td>').text(rs[column]));
});
body.append(row);
});
table.append(body);
$('#result').empty().append(table);
} else if(data.type == "update"){
$('#result').empty().append($('<span>').text('Updated ' + data.rows + ' rows.'));
} else if(data.type == "error"){
$('#result').empty().append($('<span class="error">').text(data.message));
}
});
}); });
}); });
</script> </script>

View File

@@ -40,7 +40,7 @@
<servlet-name>GitRepositoryServlet</servlet-name> <servlet-name>GitRepositoryServlet</servlet-name>
<servlet-class>gitbucket.core.servlet.GitRepositoryServlet</servlet-class> <servlet-class>gitbucket.core.servlet.GitRepositoryServlet</servlet-class>
</servlet> </servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>GitRepositoryServlet</servlet-name> <servlet-name>GitRepositoryServlet</servlet-name>
<url-pattern>/git/*</url-pattern> <url-pattern>/git/*</url-pattern>
@@ -70,30 +70,6 @@
<url-pattern>/plugin-assets/*</url-pattern> <url-pattern>/plugin-assets/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- ===================================================================== -->
<!-- H2 console configuration -->
<!-- ===================================================================== -->
<servlet>
<servlet-name>H2Console</servlet-name>
<servlet-class>org.h2.server.web.WebServlet</servlet-class>
<init-param>
<param-name>webAllowOthers</param-name>
<param-value></param-value>
</init-param>
<!--
<init-param>
<param-name>trace</param-name>
<param-value></param-value>
</init-param>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>H2Console</servlet-name>
<url-pattern>/console/*</url-pattern>
</servlet-mapping>
<!-- ===================================================================== --> <!-- ===================================================================== -->
<!-- Session timeout --> <!-- Session timeout -->
<!-- ===================================================================== --> <!-- ===================================================================== -->

View File

@@ -124,6 +124,12 @@ div.content-wrapper {
background-color: white; background-color: white;
} }
.table-scroll {
display: block;
position: relative;
overflow: scroll;
}
/* ======================================================================== */ /* ======================================================================== */
/* Global Header */ /* Global Header */
/* ======================================================================== */ /* ======================================================================== */

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B