2014-10-11 02:14:17 +04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2015-01-03 09:45:03 +03:00
|
|
|
"mime"
|
2014-11-06 16:21:53 +03:00
|
|
|
"path/filepath"
|
2015-01-04 04:42:56 +03:00
|
|
|
"strconv"
|
2014-10-11 02:14:17 +04:00
|
|
|
"strings"
|
2015-01-06 04:46:02 +03:00
|
|
|
"time"
|
2014-11-11 08:10:05 +03:00
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2014-10-11 02:14:17 +04:00
|
|
|
)
|
|
|
|
|
2015-01-03 09:45:03 +03:00
|
|
|
var extraMimeTypes = map[string]string{
|
2014-11-11 08:19:21 +03:00
|
|
|
".icon": "image-x-icon",
|
2015-01-03 09:54:08 +03:00
|
|
|
".ttf": "application/x-font-ttf",
|
|
|
|
".woff": "application/x-font-woff",
|
|
|
|
".eot": "application/vnd.ms-fontobject",
|
|
|
|
".svg": "image/svg+xml",
|
2014-11-11 08:19:21 +03:00
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
type Error struct {
|
|
|
|
Message string `json:"error"`
|
|
|
|
}
|
|
|
|
|
2014-11-21 06:43:51 +03:00
|
|
|
func NewError(err error) Error {
|
|
|
|
return Error{err.Error()}
|
|
|
|
}
|
|
|
|
|
2014-10-13 23:40:56 +04:00
|
|
|
func assetContentType(name string) string {
|
2015-01-03 09:45:03 +03:00
|
|
|
ext := filepath.Ext(name)
|
|
|
|
result := mime.TypeByExtension(ext)
|
2014-11-11 08:19:21 +03:00
|
|
|
|
2015-01-03 09:45:03 +03:00
|
|
|
if result == "" {
|
|
|
|
result = extraMimeTypes[ext]
|
2014-11-04 04:06:05 +03:00
|
|
|
}
|
2015-01-03 09:45:03 +03:00
|
|
|
|
|
|
|
if result == "" {
|
|
|
|
result = "text/plain; charset=utf-8"
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
2014-10-13 23:40:56 +04:00
|
|
|
}
|
|
|
|
|
2014-12-14 05:32:03 +03:00
|
|
|
func setupRoutes(router *gin.Engine) {
|
|
|
|
router.GET("/", API_Home)
|
2015-01-13 07:32:32 +03:00
|
|
|
router.GET("/static/*path", API_ServeAsset)
|
2014-12-14 05:40:25 +03:00
|
|
|
|
|
|
|
api := router.Group("/api")
|
|
|
|
{
|
2015-01-05 03:33:57 +03:00
|
|
|
api.Use(ApiMiddleware())
|
|
|
|
|
2014-12-14 05:40:25 +03:00
|
|
|
api.POST("/connect", API_Connect)
|
|
|
|
api.GET("/databases", API_GetDatabases)
|
|
|
|
api.GET("/connection", API_ConnectionInfo)
|
2015-03-21 19:46:14 +03:00
|
|
|
api.GET("/activity", API_Activity)
|
2015-03-31 07:58:04 +03:00
|
|
|
api.GET("/schemas", API_GetSchemas)
|
2014-12-14 05:40:25 +03:00
|
|
|
api.GET("/tables", API_GetTables)
|
|
|
|
api.GET("/tables/:table", API_GetTable)
|
2015-01-04 04:42:56 +03:00
|
|
|
api.GET("/tables/:table/rows", API_GetTableRows)
|
2014-12-14 05:40:25 +03:00
|
|
|
api.GET("/tables/:table/info", API_GetTableInfo)
|
|
|
|
api.GET("/tables/:table/indexes", API_TableIndexes)
|
|
|
|
api.GET("/query", API_RunQuery)
|
|
|
|
api.POST("/query", API_RunQuery)
|
|
|
|
api.GET("/explain", API_ExplainQuery)
|
|
|
|
api.POST("/explain", API_ExplainQuery)
|
|
|
|
api.GET("/history", API_History)
|
|
|
|
api.GET("/bookmarks", API_Bookmarks)
|
|
|
|
}
|
2015-01-05 03:33:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Middleware function to check database connection status before running queries
|
|
|
|
func ApiMiddleware() gin.HandlerFunc {
|
|
|
|
allowedPaths := []string{
|
|
|
|
"/api/connect",
|
|
|
|
"/api/bookmarks",
|
|
|
|
"/api/history",
|
|
|
|
}
|
|
|
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
if dbClient != nil {
|
|
|
|
c.Next()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
currentPath := c.Request.URL.Path
|
|
|
|
allowed := false
|
2014-12-14 05:40:25 +03:00
|
|
|
|
2015-01-05 03:33:57 +03:00
|
|
|
for _, path := range allowedPaths {
|
|
|
|
if path == currentPath {
|
|
|
|
allowed = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if allowed {
|
|
|
|
c.Next()
|
|
|
|
} else {
|
|
|
|
c.JSON(400, Error{"Not connected"})
|
2015-02-09 10:32:29 +03:00
|
|
|
c.Abort()
|
2015-01-05 03:33:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2014-12-14 05:32:03 +03:00
|
|
|
}
|
|
|
|
|
2014-10-13 22:55:19 +04:00
|
|
|
func API_Home(c *gin.Context) {
|
2014-10-13 23:40:56 +04:00
|
|
|
data, err := Asset("static/index.html")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.String(400, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-10-14 04:49:43 +04:00
|
|
|
c.Data(200, "text/html; charset=utf-8", data)
|
2014-10-13 22:55:19 +04:00
|
|
|
}
|
|
|
|
|
2014-11-01 06:37:58 +03:00
|
|
|
func API_Connect(c *gin.Context) {
|
|
|
|
url := c.Request.FormValue("url")
|
|
|
|
|
|
|
|
if url == "" {
|
|
|
|
c.JSON(400, Error{"Url parameter is required"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-01-01 03:23:51 +03:00
|
|
|
opts := Options{Url: url}
|
|
|
|
url, err := formatConnectionUrl(opts)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, Error{err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-11-01 06:37:58 +03:00
|
|
|
client, err := NewClientFromUrl(url)
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, Error{err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.Test()
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, Error{err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := client.Info()
|
|
|
|
|
|
|
|
if err == nil {
|
2014-11-01 23:44:24 +03:00
|
|
|
if dbClient != nil {
|
|
|
|
dbClient.db.Close()
|
|
|
|
}
|
|
|
|
|
2014-11-01 06:37:58 +03:00
|
|
|
dbClient = client
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, info.Format()[0])
|
|
|
|
}
|
|
|
|
|
2014-10-16 01:05:23 +04:00
|
|
|
func API_GetDatabases(c *gin.Context) {
|
|
|
|
names, err := dbClient.Databases()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, names)
|
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
func API_RunQuery(c *gin.Context) {
|
|
|
|
query := strings.TrimSpace(c.Request.FormValue("query"))
|
|
|
|
|
|
|
|
if query == "" {
|
|
|
|
c.JSON(400, errors.New("Query parameter is missing"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
API_HandleQuery(query, c)
|
|
|
|
}
|
|
|
|
|
2014-10-11 22:24:12 +04:00
|
|
|
func API_ExplainQuery(c *gin.Context) {
|
|
|
|
query := strings.TrimSpace(c.Request.FormValue("query"))
|
|
|
|
|
|
|
|
if query == "" {
|
|
|
|
c.JSON(400, errors.New("Query parameter is missing"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-10-17 01:58:12 +04:00
|
|
|
API_HandleQuery(fmt.Sprintf("EXPLAIN ANALYZE %s", query), c)
|
2014-10-11 22:24:12 +04:00
|
|
|
}
|
|
|
|
|
2015-03-31 07:58:04 +03:00
|
|
|
func API_GetSchemas(c *gin.Context) {
|
|
|
|
names, err := dbClient.Schemas()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, names)
|
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
func API_GetTables(c *gin.Context) {
|
|
|
|
names, err := dbClient.Tables()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, names)
|
|
|
|
}
|
|
|
|
|
|
|
|
func API_GetTable(c *gin.Context) {
|
2014-10-16 06:54:40 +04:00
|
|
|
res, err := dbClient.Table(c.Params.ByName("table"))
|
2014-10-11 02:14:17 +04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-10-13 22:55:19 +04:00
|
|
|
c.JSON(200, res)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2015-01-04 04:42:56 +03:00
|
|
|
func API_GetTableRows(c *gin.Context) {
|
|
|
|
limit := 1000 // Number of rows to fetch
|
|
|
|
limitVal := c.Request.FormValue("limit")
|
|
|
|
|
|
|
|
if limitVal != "" {
|
|
|
|
num, err := strconv.Atoi(limitVal)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, Error{"Invalid limit value"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if num <= 0 {
|
|
|
|
c.JSON(400, Error{"Limit should be greater than 0"})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
limit = num
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := RowsOptions{
|
|
|
|
Limit: limit,
|
|
|
|
SortColumn: c.Request.FormValue("sort_column"),
|
|
|
|
SortOrder: c.Request.FormValue("sort_order"),
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := dbClient.TableRows(c.Params.ByName("table"), opts)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, res)
|
|
|
|
}
|
|
|
|
|
2014-10-18 07:30:08 +04:00
|
|
|
func API_GetTableInfo(c *gin.Context) {
|
|
|
|
res, err := dbClient.TableInfo(c.Params.ByName("table"))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-10-27 01:16:35 +03:00
|
|
|
c.JSON(200, res.Format()[0])
|
2014-10-18 07:30:08 +04:00
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
func API_History(c *gin.Context) {
|
2014-10-11 06:25:02 +04:00
|
|
|
c.JSON(200, dbClient.history)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2014-12-03 06:20:04 +03:00
|
|
|
func API_ConnectionInfo(c *gin.Context) {
|
2014-10-16 06:59:43 +04:00
|
|
|
res, err := dbClient.Info()
|
2014-10-11 02:14:17 +04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, res.Format()[0])
|
|
|
|
}
|
|
|
|
|
2015-03-21 19:46:14 +03:00
|
|
|
func API_Activity(c *gin.Context) {
|
|
|
|
res, err := dbClient.Activity()
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, res)
|
|
|
|
}
|
|
|
|
|
2014-10-11 22:20:16 +04:00
|
|
|
func API_TableIndexes(c *gin.Context) {
|
|
|
|
res, err := dbClient.TableIndexes(c.Params.ByName("table"))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, res)
|
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
func API_HandleQuery(query string, c *gin.Context) {
|
|
|
|
result, err := dbClient.Query(query)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-10-11 03:56:02 +04:00
|
|
|
q := c.Request.URL.Query()
|
|
|
|
|
2015-01-06 04:46:02 +03:00
|
|
|
if len(q["format"]) > 0 && q["format"][0] == "csv" {
|
|
|
|
filename := fmt.Sprintf("pgweb-%v.csv", time.Now().Unix())
|
|
|
|
c.Writer.Header().Set("Content-disposition", "attachment;filename="+filename)
|
|
|
|
c.Data(200, "text/csv", result.CSV())
|
|
|
|
return
|
2014-10-11 03:56:02 +04:00
|
|
|
}
|
|
|
|
|
2014-10-11 02:14:17 +04:00
|
|
|
c.JSON(200, result)
|
|
|
|
}
|
2014-10-13 23:40:56 +04:00
|
|
|
|
2014-12-03 07:19:38 +03:00
|
|
|
func API_Bookmarks(c *gin.Context) {
|
2015-03-04 05:44:14 +03:00
|
|
|
bookmarks, err := readAllBookmarks(bookmarksPath())
|
2014-12-03 07:19:38 +03:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.JSON(400, NewError(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(200, bookmarks)
|
|
|
|
}
|
|
|
|
|
2014-10-13 23:40:56 +04:00
|
|
|
func API_ServeAsset(c *gin.Context) {
|
2015-01-13 07:32:32 +03:00
|
|
|
path := "static" + c.Params.ByName("path")
|
|
|
|
data, err := Asset(path)
|
2014-10-13 23:40:56 +04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
c.String(400, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(data) == 0 {
|
|
|
|
c.String(404, "Asset is empty")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-01-13 07:32:32 +03:00
|
|
|
c.Data(200, assetContentType(path), data)
|
2014-10-13 23:40:56 +04:00
|
|
|
}
|