Restructure application

This commit is contained in:
Dan Sosedoff 2015-04-30 11:47:07 -05:00
parent 7a75447364
commit c513930e27
18 changed files with 686 additions and 710 deletions

View File

@ -22,7 +22,7 @@ test:
godep go test -cover godep go test -cover
assets: static/ assets: static/
go-bindata $(BINDATA_OPTS) $(BINDATA_IGNORE) -ignore=[.]gitignore -ignore=[.]gitkeep $<... go-bindata -o pkg/data/bindata.go -pkg data $(BINDATA_OPTS) $(BINDATA_IGNORE) -ignore=[.]gitignore -ignore=[.]gitkeep $<...
dev-assets: dev-assets:
@$(MAKE) --no-print-directory assets BINDATA_OPTS="-debug" @$(MAKE) --no-print-directory assets BINDATA_OPTS="-debug"

File diff suppressed because one or more lines are too long

View File

@ -1,21 +0,0 @@
package main
import (
"time"
)
type HistoryRecord struct {
Query string `json:"query"`
Timestamp string `json:"timestamp"`
}
func NewHistory() []HistoryRecord {
return make([]HistoryRecord, 0)
}
func NewHistoryRecord(query string) HistoryRecord {
return HistoryRecord{
Query: query,
Timestamp: time.Now().String(),
}
}

61
main.go
View File

@ -7,31 +7,18 @@ import (
"os/signal" "os/signal"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jessevdk/go-flags"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/sosedoff/pgweb/pkg/api"
"github.com/sosedoff/pgweb/pkg/client"
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/connection"
"github.com/sosedoff/pgweb/pkg/util"
) )
const VERSION = "0.5.2" const VERSION = "0.5.2"
type Options struct { var options command.Options
Version bool `short:"v" long:"version" description:"Print version"`
Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"`
Url string `long:"url" description:"Database connection string"`
Host string `long:"host" description:"Server hostname or IP"`
Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"`
DbName string `long:"db" description:"Database name"`
Ssl string `long:"ssl" description:"SSL option"`
HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"`
HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"`
AuthUser string `long:"auth-user" description:"HTTP basic auth user"`
AuthPass string `long:"auth-pass" description:"HTTP basic auth password"`
SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"`
}
var dbClient *Client
var options Options
func exitWithMessage(message string) { func exitWithMessage(message string) {
fmt.Println("Error:", message) fmt.Println("Error:", message)
@ -39,49 +26,48 @@ func exitWithMessage(message string) {
} }
func initClient() { func initClient() {
if connectionSettingsBlank(options) { if connection.IsBlank(command.Opts) {
return return
} }
client, err := NewClient() cl, err := client.New()
if err != nil { if err != nil {
exitWithMessage(err.Error()) exitWithMessage(err.Error())
} }
if options.Debug { if command.Opts.Debug {
fmt.Println("Server connection string:", client.connectionString) fmt.Println("Server connection string:", cl.ConnectionString)
} }
fmt.Println("Connecting to server...") fmt.Println("Connecting to server...")
err = client.Test() err = cl.Test()
if err != nil { if err != nil {
exitWithMessage(err.Error()) exitWithMessage(err.Error())
} }
fmt.Println("Checking tables...") fmt.Println("Checking tables...")
_, err = client.Tables() _, err = cl.Tables()
if err != nil { if err != nil {
exitWithMessage(err.Error()) exitWithMessage(err.Error())
} }
dbClient = client api.DbClient = cl
} }
func initOptions() { func initOptions() {
_, err := flags.ParseArgs(&options, os.Args) err := command.ParseOptions()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
} }
if options.Url == "" { options = command.Opts
options.Url = os.Getenv("DATABASE_URL")
}
if options.Version { if options.Version {
fmt.Printf("pgweb v%s\n", VERSION) fmt.Printf("pgweb v%s\n", VERSION)
os.Exit(0) os.Exit(0)
} }
fmt.Println("Pgweb version", VERSION)
} }
func startServer() { func startServer() {
@ -93,7 +79,7 @@ func startServer() {
router.Use(gin.BasicAuth(auth)) router.Use(gin.BasicAuth(auth))
} }
setupRoutes(router) api.SetupRoutes(router)
fmt.Println("Starting server...") fmt.Println("Starting server...")
go func() { go func() {
@ -129,20 +115,19 @@ func openPage() {
func main() { func main() {
initOptions() initOptions()
fmt.Println("Pgweb version", VERSION)
initClient() initClient()
if dbClient != nil { if api.DbClient != nil {
defer dbClient.db.Close() defer api.DbClient.Close()
} }
if !options.Debug { if !options.Debug {
gin.SetMode("release") gin.SetMode("release")
} }
// Print memory usage every 30 seconds with debug flag
if options.Debug { if options.Debug {
startRuntimeProfiler() util.StartProfiler()
} }
startServer() startServer()

View File

@ -1,4 +1,4 @@
package main package api
import ( import (
"errors" "errors"
@ -10,6 +10,11 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sosedoff/pgweb/pkg/bookmarks"
"github.com/sosedoff/pgweb/pkg/client"
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/connection"
"github.com/sosedoff/pgweb/pkg/data"
) )
var extraMimeTypes = map[string]string{ var extraMimeTypes = map[string]string{
@ -20,6 +25,8 @@ var extraMimeTypes = map[string]string{
".svg": "image/svg+xml", ".svg": "image/svg+xml",
} }
var DbClient *client.Client
type Error struct { type Error struct {
Message string `json:"error"` Message string `json:"error"`
} }
@ -43,7 +50,7 @@ func assetContentType(name string) string {
return result return result
} }
func setupRoutes(router *gin.Engine) { func SetupRoutes(router *gin.Engine) {
router.GET("/", API_Home) router.GET("/", API_Home)
router.GET("/static/*path", API_ServeAsset) router.GET("/static/*path", API_ServeAsset)
@ -79,7 +86,7 @@ func ApiMiddleware() gin.HandlerFunc {
} }
return func(c *gin.Context) { return func(c *gin.Context) {
if dbClient != nil { if DbClient != nil {
c.Next() c.Next()
return return
} }
@ -106,7 +113,7 @@ func ApiMiddleware() gin.HandlerFunc {
} }
func API_Home(c *gin.Context) { func API_Home(c *gin.Context) {
data, err := Asset("static/index.html") data, err := data.Asset("static/index.html")
if err != nil { if err != nil {
c.String(400, err.Error()) c.String(400, err.Error())
@ -124,41 +131,41 @@ func API_Connect(c *gin.Context) {
return return
} }
opts := Options{Url: url} opts := command.Options{Url: url}
url, err := formatConnectionUrl(opts) url, err := connection.FormatUrl(opts)
if err != nil { if err != nil {
c.JSON(400, Error{err.Error()}) c.JSON(400, Error{err.Error()})
return return
} }
client, err := NewClientFromUrl(url) cl, err := client.NewFromUrl(url)
if err != nil { if err != nil {
c.JSON(400, Error{err.Error()}) c.JSON(400, Error{err.Error()})
return return
} }
err = client.Test() err = cl.Test()
if err != nil { if err != nil {
c.JSON(400, Error{err.Error()}) c.JSON(400, Error{err.Error()})
return return
} }
info, err := client.Info() info, err := cl.Info()
if err == nil { if err == nil {
if dbClient != nil { if DbClient != nil {
dbClient.db.Close() DbClient.Close()
} }
dbClient = client DbClient = cl
} }
c.JSON(200, info.Format()[0]) c.JSON(200, info.Format()[0])
} }
func API_GetDatabases(c *gin.Context) { func API_GetDatabases(c *gin.Context) {
names, err := dbClient.Databases() names, err := DbClient.Databases()
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -191,7 +198,7 @@ func API_ExplainQuery(c *gin.Context) {
} }
func API_GetSchemas(c *gin.Context) { func API_GetSchemas(c *gin.Context) {
names, err := dbClient.Schemas() names, err := DbClient.Schemas()
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -202,7 +209,7 @@ func API_GetSchemas(c *gin.Context) {
} }
func API_GetTables(c *gin.Context) { func API_GetTables(c *gin.Context) {
names, err := dbClient.Tables() names, err := DbClient.Tables()
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -213,7 +220,7 @@ func API_GetTables(c *gin.Context) {
} }
func API_GetTable(c *gin.Context) { func API_GetTable(c *gin.Context) {
res, err := dbClient.Table(c.Params.ByName("table")) res, err := DbClient.Table(c.Params.ByName("table"))
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -243,13 +250,13 @@ func API_GetTableRows(c *gin.Context) {
limit = num limit = num
} }
opts := RowsOptions{ opts := client.RowsOptions{
Limit: limit, Limit: limit,
SortColumn: c.Request.FormValue("sort_column"), SortColumn: c.Request.FormValue("sort_column"),
SortOrder: c.Request.FormValue("sort_order"), SortOrder: c.Request.FormValue("sort_order"),
} }
res, err := dbClient.TableRows(c.Params.ByName("table"), opts) res, err := DbClient.TableRows(c.Params.ByName("table"), opts)
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -260,7 +267,7 @@ func API_GetTableRows(c *gin.Context) {
} }
func API_GetTableInfo(c *gin.Context) { func API_GetTableInfo(c *gin.Context) {
res, err := dbClient.TableInfo(c.Params.ByName("table")) res, err := DbClient.TableInfo(c.Params.ByName("table"))
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -271,11 +278,11 @@ func API_GetTableInfo(c *gin.Context) {
} }
func API_History(c *gin.Context) { func API_History(c *gin.Context) {
c.JSON(200, dbClient.history) c.JSON(200, DbClient.History)
} }
func API_ConnectionInfo(c *gin.Context) { func API_ConnectionInfo(c *gin.Context) {
res, err := dbClient.Info() res, err := DbClient.Info()
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -286,7 +293,7 @@ func API_ConnectionInfo(c *gin.Context) {
} }
func API_Activity(c *gin.Context) { func API_Activity(c *gin.Context) {
res, err := dbClient.Activity() res, err := DbClient.Activity()
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
return return
@ -296,7 +303,7 @@ func API_Activity(c *gin.Context) {
} }
func API_TableIndexes(c *gin.Context) { func API_TableIndexes(c *gin.Context) {
res, err := dbClient.TableIndexes(c.Params.ByName("table")) res, err := DbClient.TableIndexes(c.Params.ByName("table"))
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -307,7 +314,7 @@ func API_TableIndexes(c *gin.Context) {
} }
func API_HandleQuery(query string, c *gin.Context) { func API_HandleQuery(query string, c *gin.Context) {
result, err := dbClient.Query(query) result, err := DbClient.Query(query)
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -327,7 +334,7 @@ func API_HandleQuery(query string, c *gin.Context) {
} }
func API_Bookmarks(c *gin.Context) { func API_Bookmarks(c *gin.Context) {
bookmarks, err := readAllBookmarks(bookmarksPath()) bookmarks, err := bookmarks.ReadAll(bookmarks.Path())
if err != nil { if err != nil {
c.JSON(400, NewError(err)) c.JSON(400, NewError(err))
@ -339,7 +346,7 @@ func API_Bookmarks(c *gin.Context) {
func API_ServeAsset(c *gin.Context) { func API_ServeAsset(c *gin.Context) {
path := "static" + c.Params.ByName("path") path := "static" + c.Params.ByName("path")
data, err := Asset(path) data, err := data.Asset(path)
if err != nil { if err != nil {
c.String(400, err.Error()) c.String(400, err.Error())

View File

@ -1,4 +1,4 @@
package main package api
import ( import (
"testing" "testing"

View File

@ -1,4 +1,4 @@
package main package bookmarks
import ( import (
"fmt" "fmt"
@ -37,12 +37,12 @@ func fileBasename(path string) string {
return strings.Replace(filename, filepath.Ext(path), "", 1) return strings.Replace(filename, filepath.Ext(path), "", 1)
} }
func bookmarksPath() string { func Path() string {
path, _ := homedir.Dir() path, _ := homedir.Dir()
return fmt.Sprintf("%s/.pgweb/bookmarks", path) return fmt.Sprintf("%s/.pgweb/bookmarks", path)
} }
func readAllBookmarks(path string) (map[string]Bookmark, error) { func ReadAll(path string) (map[string]Bookmark, error) {
results := map[string]Bookmark{} results := map[string]Bookmark{}
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(path)

View File

@ -1,4 +1,4 @@
package main package bookmarks
import ( import (
"testing" "testing"

View File

@ -1,4 +1,4 @@
package main package client
import ( import (
"bytes" "bytes"
@ -7,12 +7,16 @@ import (
"reflect" "reflect"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/connection"
"github.com/sosedoff/pgweb/pkg/history"
"github.com/sosedoff/pgweb/pkg/statements"
) )
type Client struct { type Client struct {
db *sqlx.DB db *sqlx.DB
history []HistoryRecord History []history.Record
connectionString string ConnectionString string
} }
type Row []interface{} type Row []interface{}
@ -29,10 +33,10 @@ type RowsOptions struct {
SortOrder string // Sort direction (ASC, DESC) SortOrder string // Sort direction (ASC, DESC)
} }
func NewClient() (*Client, error) { func New() (*Client, error) {
str, err := buildConnectionString(options) str, err := connection.BuildString(command.Opts)
if options.Debug && str != "" { if command.Opts.Debug && str != "" {
fmt.Println("Creating a new client for:", str) fmt.Println("Creating a new client for:", str)
} }
@ -48,15 +52,15 @@ func NewClient() (*Client, error) {
client := Client{ client := Client{
db: db, db: db,
connectionString: str, ConnectionString: str,
history: NewHistory(), History: history.New(),
} }
return &client, nil return &client, nil
} }
func NewClientFromUrl(url string) (*Client, error) { func NewFromUrl(url string) (*Client, error) {
if options.Debug { if command.Opts.Debug {
fmt.Println("Creating a new client for:", url) fmt.Println("Creating a new client for:", url)
} }
@ -68,8 +72,8 @@ func NewClientFromUrl(url string) (*Client, error) {
client := Client{ client := Client{
db: db, db: db,
connectionString: url, ConnectionString: url,
history: NewHistory(), History: history.New(),
} }
return &client, nil return &client, nil
@ -80,23 +84,23 @@ func (client *Client) Test() error {
} }
func (client *Client) Info() (*Result, error) { func (client *Client) Info() (*Result, error) {
return client.query(PG_INFO) return client.query(statements.PG_INFO)
} }
func (client *Client) Databases() ([]string, error) { func (client *Client) Databases() ([]string, error) {
return client.fetchRows(PG_DATABASES) return client.fetchRows(statements.PG_DATABASES)
} }
func (client *Client) Schemas() ([]string, error) { func (client *Client) Schemas() ([]string, error) {
return client.fetchRows(PG_SCHEMAS) return client.fetchRows(statements.PG_SCHEMAS)
} }
func (client *Client) Tables() ([]string, error) { func (client *Client) Tables() ([]string, error) {
return client.fetchRows(PG_TABLES) return client.fetchRows(statements.PG_TABLES)
} }
func (client *Client) Table(table string) (*Result, error) { func (client *Client) Table(table string) (*Result, error) {
return client.query(PG_TABLE_SCHEMA, table) return client.query(statements.PG_TABLE_SCHEMA, table)
} }
func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) { func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error) {
@ -118,11 +122,11 @@ func (client *Client) TableRows(table string, opts RowsOptions) (*Result, error)
} }
func (client *Client) TableInfo(table string) (*Result, error) { func (client *Client) TableInfo(table string) (*Result, error) {
return client.query(PG_TABLE_INFO, table) return client.query(statements.PG_TABLE_INFO, table)
} }
func (client *Client) TableIndexes(table string) (*Result, error) { func (client *Client) TableIndexes(table string) (*Result, error) {
res, err := client.query(PG_TABLE_INDEXES, table) res, err := client.query(statements.PG_TABLE_INDEXES, table)
if err != nil { if err != nil {
return nil, err return nil, err
@ -133,7 +137,7 @@ func (client *Client) TableIndexes(table string) (*Result, error) {
// Returns all active queriers on the server // Returns all active queriers on the server
func (client *Client) Activity() (*Result, error) { func (client *Client) Activity() (*Result, error) {
return client.query(PG_ACTIVITY) return client.query(statements.PG_ACTIVITY)
} }
func (client *Client) Query(query string) (*Result, error) { func (client *Client) Query(query string) (*Result, error) {
@ -141,7 +145,7 @@ func (client *Client) Query(query string) (*Result, error) {
// Save history records only if query did not fail // Save history records only if query did not fail
if err == nil { if err == nil {
client.history = append(client.history, NewHistoryRecord(query)) client.History = append(client.History, history.NewRecord(query))
} }
return res, err return res, err
@ -231,6 +235,11 @@ func (res *Result) CSV() []byte {
return buff.Bytes() return buff.Bytes()
} }
// Close database connection
func (client *Client) Close() error {
return client.db.Close()
}
// Fetch all rows as strings for a single column // Fetch all rows as strings for a single column
func (client *Client) fetchRows(q string) ([]string, error) { func (client *Client) fetchRows(q string) ([]string, error) {
res, err := client.query(q) res, err := client.query(q)

View File

@ -1,4 +1,4 @@
package main package client
import ( import (
"fmt" "fmt"

39
pkg/command/options.go Normal file
View File

@ -0,0 +1,39 @@
package command
import (
"os"
"github.com/jessevdk/go-flags"
)
type Options struct {
Version bool `short:"v" long:"version" description:"Print version"`
Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"`
Url string `long:"url" description:"Database connection string"`
Host string `long:"host" description:"Server hostname or IP"`
Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"`
DbName string `long:"db" description:"Database name"`
Ssl string `long:"ssl" description:"SSL option"`
HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"`
HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"`
AuthUser string `long:"auth-user" description:"HTTP basic auth user"`
AuthPass string `long:"auth-pass" description:"HTTP basic auth password"`
SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"`
}
var Opts Options
func ParseOptions() error {
_, err := flags.ParseArgs(&Opts, os.Args)
if err != nil {
return err
}
if Opts.Url == "" {
Opts.Url = os.Getenv("DATABASE_URL")
}
return nil
}

View File

@ -1,4 +1,4 @@
package main package connection
import ( import (
"errors" "errors"
@ -6,6 +6,8 @@ import (
"os" "os"
"os/user" "os/user"
"strings" "strings"
"github.com/sosedoff/pgweb/pkg/command"
) )
func currentUser() (string, error) { func currentUser() (string, error) {
@ -22,7 +24,7 @@ func currentUser() (string, error) {
return "", errors.New("Unable to detect OS user") return "", errors.New("Unable to detect OS user")
} }
func formatConnectionUrl(opts Options) (string, error) { func FormatUrl(opts command.Options) (string, error) {
url := opts.Url url := opts.Url
// Make sure to only accept urls in a standard format // Make sure to only accept urls in a standard format
@ -50,13 +52,13 @@ func formatConnectionUrl(opts Options) (string, error) {
return url, nil return url, nil
} }
func connectionSettingsBlank(opts Options) bool { func IsBlank(opts command.Options) bool {
return opts.Host == "" && opts.User == "" && opts.DbName == "" && opts.Url == "" return opts.Host == "" && opts.User == "" && opts.DbName == "" && opts.Url == ""
} }
func buildConnectionString(opts Options) (string, error) { func BuildString(opts command.Options) (string, error) {
if opts.Url != "" { if opts.Url != "" {
return formatConnectionUrl(opts) return FormatUrl(opts)
} }
// Try to detect user from current OS user // Try to detect user from current OS user

View File

@ -1,4 +1,4 @@
package main package connection
import ( import (
"fmt" "fmt"

492
pkg/data/bindata.go Normal file
View File

@ -0,0 +1,492 @@
package data
import (
"fmt"
"io/ioutil"
"strings"
"os"
"path"
"path/filepath"
)
// bindata_read reads the given file from disk. It returns an error on failure.
func bindata_read(path, name string) ([]byte, error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
err = fmt.Errorf("Error reading asset %s at %s: %v", name, path, err)
}
return buf, err
}
type asset struct {
bytes []byte
info os.FileInfo
}
// static_css_app_css reads file data from disk. It returns an error on failure.
func static_css_app_css() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/css/app.css"
name := "static/css/app.css"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_css_bootstrap_css reads file data from disk. It returns an error on failure.
func static_css_bootstrap_css() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/css/bootstrap.css"
name := "static/css/bootstrap.css"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_css_font_awesome_css reads file data from disk. It returns an error on failure.
func static_css_font_awesome_css() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/css/font-awesome.css"
name := "static/css/font-awesome.css"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_fonts_fontawesome_otf reads file data from disk. It returns an error on failure.
func static_fonts_fontawesome_otf() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/fonts/FontAwesome.otf"
name := "static/fonts/FontAwesome.otf"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_fonts_fontawesome_webfont_eot reads file data from disk. It returns an error on failure.
func static_fonts_fontawesome_webfont_eot() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/fonts/fontawesome-webfont.eot"
name := "static/fonts/fontawesome-webfont.eot"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_fonts_fontawesome_webfont_svg reads file data from disk. It returns an error on failure.
func static_fonts_fontawesome_webfont_svg() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/fonts/fontawesome-webfont.svg"
name := "static/fonts/fontawesome-webfont.svg"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_fonts_fontawesome_webfont_ttf reads file data from disk. It returns an error on failure.
func static_fonts_fontawesome_webfont_ttf() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/fonts/fontawesome-webfont.ttf"
name := "static/fonts/fontawesome-webfont.ttf"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_fonts_fontawesome_webfont_woff reads file data from disk. It returns an error on failure.
func static_fonts_fontawesome_webfont_woff() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/fonts/fontawesome-webfont.woff"
name := "static/fonts/fontawesome-webfont.woff"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_img_icon_ico reads file data from disk. It returns an error on failure.
func static_img_icon_ico() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/img/icon.ico"
name := "static/img/icon.ico"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_img_icon_png reads file data from disk. It returns an error on failure.
func static_img_icon_png() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/img/icon.png"
name := "static/img/icon.png"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_index_html reads file data from disk. It returns an error on failure.
func static_index_html() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/index.html"
name := "static/index.html"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_js_ace_pgsql_js reads file data from disk. It returns an error on failure.
func static_js_ace_pgsql_js() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/js/ace-pgsql.js"
name := "static/js/ace-pgsql.js"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_js_ace_js reads file data from disk. It returns an error on failure.
func static_js_ace_js() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/js/ace.js"
name := "static/js/ace.js"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_js_app_js reads file data from disk. It returns an error on failure.
func static_js_app_js() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/js/app.js"
name := "static/js/app.js"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// static_js_jquery_js reads file data from disk. It returns an error on failure.
func static_js_jquery_js() (*asset, error) {
path := "/Users/sosedoff/go/src/github.com/sosedoff/pgweb/static/js/jquery.js"
name := "static/js/jquery.js"
bytes, err := bindata_read(path, name)
if err != nil {
return nil, err
}
fi, err := os.Stat(path)
if err != nil {
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
}
a := &asset{bytes: bytes, info: fi}
return a, err
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if (err != nil) {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"static/css/app.css": static_css_app_css,
"static/css/bootstrap.css": static_css_bootstrap_css,
"static/css/font-awesome.css": static_css_font_awesome_css,
"static/fonts/FontAwesome.otf": static_fonts_fontawesome_otf,
"static/fonts/fontawesome-webfont.eot": static_fonts_fontawesome_webfont_eot,
"static/fonts/fontawesome-webfont.svg": static_fonts_fontawesome_webfont_svg,
"static/fonts/fontawesome-webfont.ttf": static_fonts_fontawesome_webfont_ttf,
"static/fonts/fontawesome-webfont.woff": static_fonts_fontawesome_webfont_woff,
"static/img/icon.ico": static_img_icon_ico,
"static/img/icon.png": static_img_icon_png,
"static/index.html": static_index_html,
"static/js/ace-pgsql.js": static_js_ace_pgsql_js,
"static/js/ace.js": static_js_ace_js,
"static/js/app.js": static_js_app_js,
"static/js/jquery.js": static_js_jquery_js,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
}
return rv, nil
}
type _bintree_t struct {
Func func() (*asset, error)
Children map[string]*_bintree_t
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"static": &_bintree_t{nil, map[string]*_bintree_t{
"css": &_bintree_t{nil, map[string]*_bintree_t{
"app.css": &_bintree_t{static_css_app_css, map[string]*_bintree_t{
}},
"bootstrap.css": &_bintree_t{static_css_bootstrap_css, map[string]*_bintree_t{
}},
"font-awesome.css": &_bintree_t{static_css_font_awesome_css, map[string]*_bintree_t{
}},
}},
"fonts": &_bintree_t{nil, map[string]*_bintree_t{
"FontAwesome.otf": &_bintree_t{static_fonts_fontawesome_otf, map[string]*_bintree_t{
}},
"fontawesome-webfont.eot": &_bintree_t{static_fonts_fontawesome_webfont_eot, map[string]*_bintree_t{
}},
"fontawesome-webfont.svg": &_bintree_t{static_fonts_fontawesome_webfont_svg, map[string]*_bintree_t{
}},
"fontawesome-webfont.ttf": &_bintree_t{static_fonts_fontawesome_webfont_ttf, map[string]*_bintree_t{
}},
"fontawesome-webfont.woff": &_bintree_t{static_fonts_fontawesome_webfont_woff, map[string]*_bintree_t{
}},
}},
"img": &_bintree_t{nil, map[string]*_bintree_t{
"icon.ico": &_bintree_t{static_img_icon_ico, map[string]*_bintree_t{
}},
"icon.png": &_bintree_t{static_img_icon_png, map[string]*_bintree_t{
}},
}},
"index.html": &_bintree_t{static_index_html, map[string]*_bintree_t{
}},
"js": &_bintree_t{nil, map[string]*_bintree_t{
"ace-pgsql.js": &_bintree_t{static_js_ace_pgsql_js, map[string]*_bintree_t{
}},
"ace.js": &_bintree_t{static_js_ace_js, map[string]*_bintree_t{
}},
"app.js": &_bintree_t{static_js_app_js, map[string]*_bintree_t{
}},
"jquery.js": &_bintree_t{static_js_jquery_js, map[string]*_bintree_t{
}},
}},
}},
}}
// Restore an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, path.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// Restore assets under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
if err != nil { // File
return RestoreAsset(dir, name)
} else { // Dir
for _, child := range children {
err = RestoreAssets(dir, path.Join(name, child))
if err != nil {
return err
}
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

21
pkg/history/history.go Normal file
View File

@ -0,0 +1,21 @@
package history
import (
"time"
)
type Record struct {
Query string `json:"query"`
Timestamp string `json:"timestamp"`
}
func New() []Record {
return make([]Record, 0)
}
func NewRecord(query string) Record {
return Record{
Query: query,
Timestamp: time.Now().String(),
}
}

View File

@ -1,4 +1,4 @@
package main package statements
const ( const (
PG_DATABASES = `SELECT datname FROM pg_database WHERE NOT datistemplate ORDER BY datname ASC` PG_DATABASES = `SELECT datname FROM pg_database WHERE NOT datistemplate ORDER BY datname ASC`

32
pkg/util/profiler.go Normal file
View File

@ -0,0 +1,32 @@
package util
import (
"log"
"os"
"runtime"
"time"
)
const MEGABYTE = 1024 * 1024
func runProfiler() {
logger := log.New(os.Stdout, "", 0)
m := &runtime.MemStats{}
for {
runtime.ReadMemStats(m)
logger.Printf(
"[DEBUG] Goroutines: %v, Mem used: %v (%v mb), Mem acquired: %v (%v mb)\n",
runtime.NumGoroutine(),
m.Alloc, m.Alloc/MEGABYTE,
m.Sys, m.Sys/MEGABYTE,
)
time.Sleep(time.Second * 30)
}
}
func StartProfiler() {
go runProfiler()
}

View File

@ -1,30 +0,0 @@
package main
import (
"log"
"os"
"runtime"
"time"
)
const MEGABYTE = 1024 * 1024
func startRuntimeProfiler() {
go func() {
logger := log.New(os.Stdout, "", 0)
m := &runtime.MemStats{}
for {
runtime.ReadMemStats(m)
logger.Printf(
"[DEBUG] Goroutines: %v, Mem used: %v (%v mb), Mem acquired: %v (%v mb)\n",
runtime.NumGoroutine(),
m.Alloc, m.Alloc/MEGABYTE,
m.Sys, m.Sys/MEGABYTE,
)
time.Sleep(time.Second * 30)
}
}()
}