2015-04-30 19:47:07 +03:00
|
|
|
package api
|
2014-10-11 02:14:17 +04:00
|
|
|
|
|
|
|
import (
|
2015-08-16 04:53:51 +03:00
|
|
|
"encoding/base64"
|
2014-10-11 02:14:17 +04:00
|
|
|
"fmt"
|
2021-03-05 03:38:08 +03:00
|
|
|
"github.com/sosedoff/pgweb/static"
|
|
|
|
"net/http"
|
2016-11-06 06:23:26 +03:00
|
|
|
neturl "net/url"
|
2019-07-31 15:39:31 +03:00
|
|
|
"regexp"
|
2017-09-17 04:32:41 +03:00
|
|
|
"strings"
|
2015-01-06 04:46:02 +03:00
|
|
|
"time"
|
2014-11-11 08:10:05 +03:00
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2017-09-16 02:54:14 +03:00
|
|
|
"github.com/tuvistavie/securerandom"
|
2016-01-15 04:50:01 +03:00
|
|
|
|
2015-04-30 19:47:07 +03:00
|
|
|
"github.com/sosedoff/pgweb/pkg/bookmarks"
|
|
|
|
"github.com/sosedoff/pgweb/pkg/client"
|
|
|
|
"github.com/sosedoff/pgweb/pkg/command"
|
|
|
|
"github.com/sosedoff/pgweb/pkg/connection"
|
2016-01-15 04:50:01 +03:00
|
|
|
"github.com/sosedoff/pgweb/pkg/shared"
|
2014-10-11 02:14:17 +04:00
|
|
|
)
|
|
|
|
|
2016-01-09 04:44:31 +03:00
|
|
|
var (
|
2018-12-01 06:40:28 +03:00
|
|
|
// DbClient represents the active database connection in a single-session mode
|
|
|
|
DbClient *client.Client
|
|
|
|
|
|
|
|
// DbSessions represents the mapping for client connections
|
2016-01-09 04:44:31 +03:00
|
|
|
DbSessions = map[string]*client.Client{}
|
|
|
|
)
|
2016-01-09 04:10:11 +03:00
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
// DB returns a database connection from the client context
|
2016-01-09 04:10:11 +03:00
|
|
|
func DB(c *gin.Context) *client.Client {
|
2016-01-09 04:44:31 +03:00
|
|
|
if command.Opts.Sessions {
|
2016-02-26 19:48:55 +03:00
|
|
|
return DbSessions[getSessionId(c.Request)]
|
2016-01-09 04:44:31 +03:00
|
|
|
}
|
2019-09-29 20:16:42 +03:00
|
|
|
return DbClient
|
2016-01-09 04:44:31 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// setClient sets the database client connection for the sessions
|
2016-01-09 04:44:31 +03:00
|
|
|
func setClient(c *gin.Context, newClient *client.Client) error {
|
|
|
|
currentClient := DB(c)
|
|
|
|
if currentClient != nil {
|
|
|
|
currentClient.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
if !command.Opts.Sessions {
|
|
|
|
DbClient = newClient
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
sid := getSessionId(c.Request)
|
|
|
|
if sid == "" {
|
|
|
|
return errSessionRequired
|
2016-01-09 04:44:31 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
DbSessions[sid] = newClient
|
2016-01-09 04:44:31 +03:00
|
|
|
return nil
|
2016-01-09 04:10:11 +03:00
|
|
|
}
|
2015-04-30 19:47:07 +03:00
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetHome renderes the home page
|
2021-03-05 03:38:08 +03:00
|
|
|
func GetHome() http.Handler {
|
|
|
|
return http.FileServer(http.FS(static.Static))
|
2015-05-03 04:10:14 +03:00
|
|
|
}
|
2014-10-13 23:40:56 +04:00
|
|
|
|
2021-03-05 03:38:08 +03:00
|
|
|
func GetAssets() http.Handler {
|
|
|
|
return http.StripPrefix("/static/", http.FileServer(http.FS(static.Static)))
|
2014-10-13 22:55:19 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetSessions renders the number of active sessions
|
2016-01-09 04:10:11 +03:00
|
|
|
func GetSessions(c *gin.Context) {
|
2016-01-11 00:16:31 +03:00
|
|
|
// In debug mode endpoint will return a lot of sensitive information
|
|
|
|
// like full database connection string and all query history.
|
|
|
|
if command.Opts.Debug {
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, DbSessions)
|
2016-01-11 00:16:31 +03:00
|
|
|
return
|
|
|
|
}
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, gin.H{"sessions": len(DbSessions)})
|
2016-01-09 04:10:11 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// ConnectWithBackend creates a new connection based on backend resource
|
2017-09-16 02:54:14 +03:00
|
|
|
func ConnectWithBackend(c *gin.Context) {
|
2017-09-21 09:21:26 +03:00
|
|
|
// Setup a new backend client
|
|
|
|
backend := Backend{
|
|
|
|
Endpoint: command.Opts.ConnectBackend,
|
|
|
|
Token: command.Opts.ConnectToken,
|
|
|
|
PassHeaders: command.Opts.ConnectHeaders,
|
2017-09-16 03:23:00 +03:00
|
|
|
}
|
|
|
|
|
2017-09-21 09:21:26 +03:00
|
|
|
// Fetch connection credentials
|
|
|
|
cred, err := backend.FetchCredential(c.Param("resource"), c)
|
2017-09-16 02:54:14 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-16 02:54:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-21 09:21:26 +03:00
|
|
|
// Make the new session
|
2019-09-29 20:16:42 +03:00
|
|
|
sid, err := securerandom.Uuid()
|
2017-09-16 02:54:14 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-16 02:54:14 +03:00
|
|
|
return
|
|
|
|
}
|
2019-09-29 20:16:42 +03:00
|
|
|
c.Request.Header.Add("x-session-id", sid)
|
2017-09-16 02:54:14 +03:00
|
|
|
|
2017-09-21 09:21:26 +03:00
|
|
|
// Connect to the database
|
2018-12-02 07:35:11 +03:00
|
|
|
cl, err := client.NewFromUrl(cred.DatabaseURL, nil)
|
2017-09-16 02:54:14 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-16 02:54:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
cl.External = true
|
|
|
|
|
2017-09-21 09:21:26 +03:00
|
|
|
// Finalize session seetup
|
2017-09-16 02:54:14 +03:00
|
|
|
_, err = cl.Info()
|
|
|
|
if err == nil {
|
|
|
|
err = setClient(c, cl)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
cl.Close()
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-16 02:54:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
redirectURI := fmt.Sprintf("/%s?session=%s", command.Opts.Prefix, sid)
|
|
|
|
c.Redirect(301, redirectURI)
|
2017-09-16 02:54:14 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// Connect creates a new client connection
|
2015-05-03 04:13:04 +03:00
|
|
|
func Connect(c *gin.Context) {
|
2016-11-06 05:23:37 +03:00
|
|
|
if command.Opts.LockSession {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errSessionLocked)
|
2016-11-06 05:23:37 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-15 04:50:01 +03:00
|
|
|
var sshInfo *shared.SSHInfo
|
2014-11-01 06:37:58 +03:00
|
|
|
url := c.Request.FormValue("url")
|
|
|
|
|
|
|
|
if url == "" {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errURLRequired)
|
2014-11-01 06:37:58 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-11-02 21:00:23 +03:00
|
|
|
opts := command.Options{URL: url}
|
2019-01-28 23:03:45 +03:00
|
|
|
url, err := connection.FormatURL(opts)
|
2015-01-01 03:23:51 +03:00
|
|
|
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2015-01-01 03:23:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-15 04:50:01 +03:00
|
|
|
if c.Request.FormValue("ssh") != "" {
|
|
|
|
sshInfo = parseSshInfo(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
cl, err := client.NewFromUrl(url, sshInfo)
|
2014-11-01 06:37:58 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2014-11-01 06:37:58 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-30 19:47:07 +03:00
|
|
|
err = cl.Test()
|
2014-11-01 06:37:58 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2014-11-01 06:37:58 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-30 19:47:07 +03:00
|
|
|
info, err := cl.Info()
|
2014-11-01 06:37:58 +03:00
|
|
|
if err == nil {
|
2016-01-09 04:44:31 +03:00
|
|
|
err = setClient(c, cl)
|
2017-04-29 23:56:31 +03:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
cl.Close()
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-04-29 23:56:31 +03:00
|
|
|
return
|
2014-11-01 06:37:58 +03:00
|
|
|
}
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, info.Format()[0])
|
2014-11-01 06:37:58 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// SwitchDb perform database switch for the client connection
|
2016-11-06 06:23:26 +03:00
|
|
|
func SwitchDb(c *gin.Context) {
|
|
|
|
if command.Opts.LockSession {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errSessionLocked)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
name := c.Request.URL.Query().Get("db")
|
2016-11-06 06:49:17 +03:00
|
|
|
if name == "" {
|
|
|
|
name = c.Request.FormValue("db")
|
|
|
|
}
|
2016-11-06 06:23:26 +03:00
|
|
|
if name == "" {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errDatabaseNameRequired)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
conn := DB(c)
|
|
|
|
if conn == nil {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errNotConnected)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-16 02:54:14 +03:00
|
|
|
// Do not allow switching databases for connections from third-party backends
|
|
|
|
if conn.External {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errSessionLocked)
|
2017-09-16 02:54:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
currentURL, err := neturl.Parse(conn.ConnectionString)
|
2016-11-06 06:23:26 +03:00
|
|
|
if err != nil {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errInvalidConnString)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
2019-09-29 20:16:42 +03:00
|
|
|
currentURL.Path = name
|
2016-11-06 06:23:26 +03:00
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
cl, err := client.NewFromUrl(currentURL.String(), nil)
|
2016-11-06 06:23:26 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = cl.Test()
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-11-06 06:23:26 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := cl.Info()
|
|
|
|
if err == nil {
|
|
|
|
err = setClient(c, cl)
|
2017-04-29 23:56:31 +03:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
cl.Close()
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-04-29 23:56:31 +03:00
|
|
|
return
|
2016-11-06 06:23:26 +03:00
|
|
|
}
|
|
|
|
|
2016-11-06 06:49:17 +03:00
|
|
|
conn.Close()
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, info.Format()[0])
|
2016-11-06 06:23:26 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// Disconnect closes the current database connection
|
2016-02-05 08:05:42 +03:00
|
|
|
func Disconnect(c *gin.Context) {
|
2016-11-06 05:23:37 +03:00
|
|
|
if command.Opts.LockSession {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errSessionLocked)
|
2016-11-06 05:23:37 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-02-05 08:05:42 +03:00
|
|
|
conn := DB(c)
|
|
|
|
if conn == nil {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errNotConnected)
|
2016-02-05 08:05:42 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err := conn.Close()
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-02-05 08:05:42 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, gin.H{"success": true})
|
2016-02-05 08:05:42 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// RunQuery executes the query
|
2015-05-03 04:13:04 +03:00
|
|
|
func RunQuery(c *gin.Context) {
|
2016-02-19 07:18:07 +03:00
|
|
|
query := cleanQuery(c.Request.FormValue("query"))
|
2014-10-11 02:14:17 +04:00
|
|
|
|
|
|
|
if query == "" {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errQueryRequired)
|
2014-10-11 02:14:17 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-05-03 04:32:16 +03:00
|
|
|
HandleQuery(query, c)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// ExplainQuery renders query analyze profile
|
2015-05-03 04:13:04 +03:00
|
|
|
func ExplainQuery(c *gin.Context) {
|
2016-02-19 07:18:07 +03:00
|
|
|
query := cleanQuery(c.Request.FormValue("query"))
|
2014-10-11 22:24:12 +04:00
|
|
|
|
|
|
|
if query == "" {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errQueryRequired)
|
2014-10-11 22:24:12 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-05-03 04:32:16 +03:00
|
|
|
HandleQuery(fmt.Sprintf("EXPLAIN ANALYZE %s", query), c)
|
2014-10-11 22:24:12 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetDatabases renders a list of all databases on the server
|
|
|
|
func GetDatabases(c *gin.Context) {
|
2020-07-15 19:11:05 +03:00
|
|
|
if command.Opts.LockSession {
|
|
|
|
serveResult(c, []string{}, nil)
|
|
|
|
return
|
|
|
|
}
|
2019-09-29 20:16:42 +03:00
|
|
|
conn := DB(c)
|
|
|
|
if conn.External {
|
|
|
|
errorResponse(c, 403, errNotPermitted)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
names, err := DB(c).Databases()
|
|
|
|
serveResult(c, names, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetObjects renders a list of database objects
|
|
|
|
func GetObjects(c *gin.Context) {
|
|
|
|
result, err := DB(c).Objects()
|
|
|
|
if err != nil {
|
|
|
|
badRequest(c, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
successResponse(c, client.ObjectsFromResult(result))
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSchemas renders list of available schemas
|
2015-05-03 04:13:04 +03:00
|
|
|
func GetSchemas(c *gin.Context) {
|
2016-01-13 06:33:44 +03:00
|
|
|
res, err := DB(c).Schemas()
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetTable renders table information
|
2015-05-03 04:13:04 +03:00
|
|
|
func GetTable(c *gin.Context) {
|
2016-01-18 00:00:33 +03:00
|
|
|
var res *client.Result
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if c.Request.FormValue("type") == "materialized_view" {
|
|
|
|
res, err = DB(c).MaterializedView(c.Params.ByName("table"))
|
|
|
|
} else {
|
|
|
|
res, err = DB(c).Table(c.Params.ByName("table"))
|
|
|
|
}
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetTableRows renders table rows
|
2015-05-03 04:13:04 +03:00
|
|
|
func GetTableRows(c *gin.Context) {
|
2016-01-08 06:18:22 +03:00
|
|
|
offset, err := parseIntFormValue(c, "offset", 0)
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-01-08 06:18:22 +03:00
|
|
|
return
|
|
|
|
}
|
2015-01-04 04:42:56 +03:00
|
|
|
|
2016-01-08 06:18:22 +03:00
|
|
|
limit, err := parseIntFormValue(c, "limit", 100)
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-01-08 06:18:22 +03:00
|
|
|
return
|
2015-01-04 04:42:56 +03:00
|
|
|
}
|
|
|
|
|
2015-04-30 19:47:07 +03:00
|
|
|
opts := client.RowsOptions{
|
2015-01-04 04:42:56 +03:00
|
|
|
Limit: limit,
|
2016-01-08 06:18:22 +03:00
|
|
|
Offset: offset,
|
2015-01-04 04:42:56 +03:00
|
|
|
SortColumn: c.Request.FormValue("sort_column"),
|
|
|
|
SortOrder: c.Request.FormValue("sort_order"),
|
2016-01-08 23:16:53 +03:00
|
|
|
Where: c.Request.FormValue("where"),
|
2015-01-04 04:42:56 +03:00
|
|
|
}
|
|
|
|
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).TableRows(c.Params.ByName("table"), opts)
|
2016-01-08 23:16:53 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-01-08 23:16:53 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-09 04:10:11 +03:00
|
|
|
countRes, err := DB(c).TableRowsCount(c.Params.ByName("table"), opts)
|
2016-01-08 23:16:53 +03:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2016-01-08 23:16:53 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
numFetch := int64(opts.Limit)
|
|
|
|
numOffset := int64(opts.Offset)
|
|
|
|
numRows := countRes.Rows[0][0].(int64)
|
|
|
|
numPages := numRows / numFetch
|
|
|
|
|
|
|
|
if numPages*numFetch < numRows {
|
|
|
|
numPages++
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Pagination = &client.Pagination{
|
|
|
|
Rows: numRows,
|
|
|
|
Page: (numOffset / numFetch) + 1,
|
|
|
|
Pages: numPages,
|
|
|
|
PerPage: numFetch,
|
|
|
|
}
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2015-01-04 04:42:56 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetTableInfo renders a selected table information
|
2015-05-03 04:13:04 +03:00
|
|
|
func GetTableInfo(c *gin.Context) {
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).TableInfo(c.Params.ByName("table"))
|
2018-12-01 06:40:28 +03:00
|
|
|
if err == nil {
|
|
|
|
successResponse(c, res.Format()[0])
|
|
|
|
} else {
|
|
|
|
badRequest(c, err)
|
2014-10-18 07:30:08 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetHistory renders a list of recent queries
|
2015-05-01 03:59:48 +03:00
|
|
|
func GetHistory(c *gin.Context) {
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, DB(c).History)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetConnectionInfo renders information about current connection
|
2015-05-01 03:59:48 +03:00
|
|
|
func GetConnectionInfo(c *gin.Context) {
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).Info()
|
2014-10-11 02:14:17 +04:00
|
|
|
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2014-10-11 02:14:17 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-06 05:23:37 +03:00
|
|
|
info := res.Format()[0]
|
|
|
|
info["session_lock"] = command.Opts.LockSession
|
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, info)
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetActivity renders a list of running queries
|
2015-05-01 03:59:48 +03:00
|
|
|
func GetActivity(c *gin.Context) {
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).Activity()
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2015-03-21 19:46:14 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetTableIndexes renders a list of database table indexes
|
2015-05-01 03:59:48 +03:00
|
|
|
func GetTableIndexes(c *gin.Context) {
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).TableIndexes(c.Params.ByName("table"))
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2014-10-11 22:20:16 +04:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetTableConstraints renders a list of database constraints
|
2015-12-05 03:14:03 +03:00
|
|
|
func GetTableConstraints(c *gin.Context) {
|
2016-01-09 04:10:11 +03:00
|
|
|
res, err := DB(c).TableConstraints(c.Params.ByName("table"))
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, res, err)
|
2015-12-05 03:14:03 +03:00
|
|
|
}
|
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// HandleQuery runs the database query
|
2015-05-03 04:32:16 +03:00
|
|
|
func HandleQuery(query string, c *gin.Context) {
|
2016-02-19 06:17:31 +03:00
|
|
|
rawQuery, err := base64.StdEncoding.DecodeString(desanitize64(query))
|
2015-08-16 04:53:51 +03:00
|
|
|
if err == nil {
|
|
|
|
query = string(rawQuery)
|
|
|
|
}
|
2014-10-11 02:14:17 +04:00
|
|
|
|
2016-01-09 04:10:11 +03:00
|
|
|
result, err := DB(c).Query(query)
|
2014-10-11 02:14:17 +04:00
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2014-10-11 02:14:17 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-05 03:03:53 +03:00
|
|
|
format := getQueryParam(c, "format")
|
|
|
|
filename := getQueryParam(c, "filename")
|
2014-10-11 03:56:02 +04:00
|
|
|
|
2016-01-05 03:03:53 +03:00
|
|
|
if filename == "" {
|
|
|
|
filename = fmt.Sprintf("pgweb-%v.%v", time.Now().Unix(), format)
|
|
|
|
}
|
2015-05-19 20:24:52 +03:00
|
|
|
|
2016-01-05 03:03:53 +03:00
|
|
|
if format != "" {
|
2015-01-06 04:46:02 +03:00
|
|
|
c.Writer.Header().Set("Content-disposition", "attachment;filename="+filename)
|
2014-10-11 03:56:02 +04:00
|
|
|
}
|
|
|
|
|
2016-01-05 03:03:53 +03:00
|
|
|
switch format {
|
|
|
|
case "csv":
|
|
|
|
c.Data(200, "text/csv", result.CSV())
|
|
|
|
case "json":
|
2017-01-24 03:55:18 +03:00
|
|
|
c.Data(200, "application/json", result.JSON())
|
2016-01-05 03:03:53 +03:00
|
|
|
case "xml":
|
|
|
|
c.XML(200, result)
|
|
|
|
default:
|
|
|
|
c.JSON(200, result)
|
|
|
|
}
|
2014-10-11 02:14:17 +04:00
|
|
|
}
|
2014-10-13 23:40:56 +04:00
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetBookmarks renders the list of available bookmarks
|
2015-05-01 03:59:48 +03:00
|
|
|
func GetBookmarks(c *gin.Context) {
|
2017-01-24 03:55:18 +03:00
|
|
|
bookmarks, err := bookmarks.ReadAll(bookmarks.Path(command.Opts.BookmarksDir))
|
2018-12-01 06:40:28 +03:00
|
|
|
serveResult(c, bookmarks, err)
|
2014-12-03 07:19:38 +03:00
|
|
|
}
|
2015-05-05 08:34:23 +03:00
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// GetInfo renders the pgweb system information
|
2015-05-05 08:34:23 +03:00
|
|
|
func GetInfo(c *gin.Context) {
|
2018-12-01 06:40:28 +03:00
|
|
|
successResponse(c, gin.H{
|
2018-12-14 07:35:43 +03:00
|
|
|
"version": command.Version,
|
2019-12-05 05:29:23 +03:00
|
|
|
"go_version": command.GoVersion,
|
2015-05-05 08:34:23 +03:00
|
|
|
"git_sha": command.GitCommit,
|
|
|
|
"build_time": command.BuildTime,
|
2018-12-01 06:40:28 +03:00
|
|
|
})
|
2015-05-05 08:34:23 +03:00
|
|
|
}
|
2017-09-17 04:32:41 +03:00
|
|
|
|
2019-09-29 20:16:42 +03:00
|
|
|
// DataExport performs database table export
|
2017-09-17 04:32:41 +03:00
|
|
|
func DataExport(c *gin.Context) {
|
|
|
|
db := DB(c)
|
|
|
|
|
|
|
|
info, err := db.Info()
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-17 04:32:41 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
dump := client.Dump{
|
|
|
|
Table: strings.TrimSpace(c.Request.FormValue("table")),
|
|
|
|
}
|
|
|
|
|
2018-04-26 07:29:18 +03:00
|
|
|
// If pg_dump is not available the following code will not show an error in browser.
|
|
|
|
// This is due to the headers being written first.
|
|
|
|
if !dump.CanExport() {
|
2019-09-29 20:16:42 +03:00
|
|
|
badRequest(c, errPgDumpNotFound)
|
2018-04-26 07:29:18 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-17 04:32:41 +03:00
|
|
|
formattedInfo := info.Format()[0]
|
|
|
|
filename := formattedInfo["current_database"].(string)
|
|
|
|
if dump.Table != "" {
|
|
|
|
filename = filename + "_" + dump.Table
|
|
|
|
}
|
2019-07-31 15:39:31 +03:00
|
|
|
reg := regexp.MustCompile("[^._\\w]+")
|
|
|
|
cleanFilename := reg.ReplaceAllString(filename, "")
|
2017-09-17 04:32:41 +03:00
|
|
|
|
2018-12-01 06:40:28 +03:00
|
|
|
c.Header(
|
|
|
|
"Content-Disposition",
|
2019-07-31 15:39:31 +03:00
|
|
|
fmt.Sprintf(`attachment; filename="%s.sql.gz"`, cleanFilename),
|
2018-12-01 06:40:28 +03:00
|
|
|
)
|
2017-09-17 04:32:41 +03:00
|
|
|
|
|
|
|
err = dump.Export(db.ConnectionString, c.Writer)
|
|
|
|
if err != nil {
|
2018-12-01 06:40:28 +03:00
|
|
|
badRequest(c, err)
|
2017-09-17 04:32:41 +03:00
|
|
|
}
|
|
|
|
}
|