diff --git a/pkg/api/api.go b/pkg/api/api.go
index 0849bed..ecda2ee 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -142,27 +142,16 @@ func Connect(c *gin.Context) {
return
}
- url := c.Request.FormValue("url")
- if url == "" {
- badRequest(c, errURLRequired)
- return
- }
+ var (
+ cl *client.Client
+ err error
+ )
- url, err := connection.FormatURL(command.Options{
- URL: url,
- Passfile: command.Opts.Passfile,
- })
- if err != nil {
- badRequest(c, err)
- return
+ if bookmarkID := c.Request.FormValue("bookmark_id"); bookmarkID != "" {
+ cl, err = ConnectWithBookmark(bookmarkID)
+ } else {
+ cl, err = ConnectWithURL(c)
}
-
- var sshInfo *shared.SSHInfo
- if c.Request.FormValue("ssh") != "" {
- sshInfo = parseSshInfo(c)
- }
-
- cl, err := client.NewFromUrl(url, sshInfo)
if err != nil {
badRequest(c, err)
return
@@ -187,6 +176,39 @@ func Connect(c *gin.Context) {
successResponse(c, info.Format()[0])
}
+func ConnectWithURL(c *gin.Context) (*client.Client, error) {
+ url := c.Request.FormValue("url")
+ if url == "" {
+ return nil, errURLRequired
+ }
+
+ url, err := connection.FormatURL(command.Options{
+ URL: url,
+ Passfile: command.Opts.Passfile,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ var sshInfo *shared.SSHInfo
+ if c.Request.FormValue("ssh") != "" {
+ sshInfo = parseSshInfo(c)
+ }
+
+ return client.NewFromUrl(url, sshInfo)
+}
+
+func ConnectWithBookmark(id string) (*client.Client, error) {
+ manager := bookmarks.NewManager(command.Opts.BookmarksDir)
+
+ bookmark, err := manager.Get(id)
+ if err != nil {
+ return nil, err
+ }
+
+ return client.NewFromBookmark(bookmark)
+}
+
// SwitchDb perform database switch for the client connection
func SwitchDb(c *gin.Context) {
if command.Opts.LockSession {
@@ -500,8 +522,9 @@ func HandleQuery(query string, c *gin.Context) {
// GetBookmarks renders the list of available bookmarks
func GetBookmarks(c *gin.Context) {
- bookmarks, err := bookmarks.ReadAll(bookmarks.Path(command.Opts.BookmarksDir))
- serveResult(c, bookmarks, err)
+ manager := bookmarks.NewManager(command.Opts.BookmarksDir)
+ ids, err := manager.ListIDs()
+ serveResult(c, ids, err)
}
// GetInfo renders the pgweb system information
diff --git a/pkg/bookmarks/bookmarks.go b/pkg/bookmarks/bookmarks.go
index f89efb8..d4d45fb 100644
--- a/pkg/bookmarks/bookmarks.go
+++ b/pkg/bookmarks/bookmarks.go
@@ -1,28 +1,21 @@
package bookmarks
import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/BurntSushi/toml"
- "github.com/mitchellh/go-homedir"
-
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/shared"
)
// Bookmark contains information about bookmarked database connection
type Bookmark struct {
- URL string `json:"url"` // Postgres connection URL
- Host string `json:"host"` // Server hostname
- Port int `json:"port"` // Server port
- User string `json:"user"` // Database user
- Password string `json:"password"` // User password
- Database string `json:"database"` // Database name
- SSLMode string `json:"ssl"` // Connection SSL mode
- SSH *shared.SSHInfo `json:"ssh"` // SSH tunnel config
+ ID string // ID generated from the filename
+ URL string // Postgres connection URL
+ Host string // Server hostname
+ Port int // Server port
+ User string // Database user
+ Password string // User password
+ Database string // Database name
+ SSLMode string // Connection SSL mode
+ SSH *shared.SSHInfo // SSH tunnel config
}
// SSHInfoIsEmpty returns true if ssh configuration is not provided
@@ -42,100 +35,3 @@ func (b Bookmark) ConvertToOptions() command.Options {
SSLMode: b.SSLMode,
}
}
-
-func readServerConfig(path string) (Bookmark, error) {
- bookmark := Bookmark{}
-
- buff, err := os.ReadFile(path)
- if err != nil {
- return bookmark, err
- }
-
- _, err = toml.Decode(string(buff), &bookmark)
-
- if bookmark.Port == 0 {
- bookmark.Port = 5432
- }
-
- // List of all supported postgres modes
- modes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
- valid := false
-
- for _, mode := range modes {
- if bookmark.SSLMode == mode {
- valid = true
- break
- }
- }
-
- // Fall back to a default mode if mode is not set or invalid
- // Typical typo: ssl mode set to "disabled"
- if bookmark.SSLMode == "" || !valid {
- bookmark.SSLMode = "disable"
- }
-
- // Set default SSH port if it's not provided by user
- if bookmark.SSH != nil && bookmark.SSH.Port == "" {
- bookmark.SSH.Port = "22"
- }
-
- return bookmark, err
-}
-
-func fileBasename(path string) string {
- filename := filepath.Base(path)
- return strings.Replace(filename, filepath.Ext(path), "", 1)
-}
-
-// Path returns bookmarks storage path
-func Path(overrideDir string) string {
- if overrideDir == "" {
- path, _ := homedir.Dir()
- return fmt.Sprintf("%s/.pgweb/bookmarks", path)
- }
- return overrideDir
-}
-
-// ReadAll returns all available bookmarks
-func ReadAll(path string) (map[string]Bookmark, error) {
- results := map[string]Bookmark{}
-
- files, err := os.ReadDir(path)
- if err != nil {
- return results, err
- }
-
- for _, file := range files {
- if filepath.Ext(file.Name()) != ".toml" {
- continue
- }
-
- fullPath := filepath.Join(path, file.Name())
- key := fileBasename(file.Name())
- config, err := readServerConfig(fullPath)
-
- if err != nil {
- fmt.Printf("%s parse error: %s\n", fullPath, err)
- continue
- }
-
- results[key] = config
- }
-
- return results, nil
-}
-
-// GetBookmark reads an existing bookmark
-func GetBookmark(bookmarkPath string, bookmarkName string) (Bookmark, error) {
- bookmarks, err := ReadAll(bookmarkPath)
- if err != nil {
- return Bookmark{}, err
- }
-
- bookmark, ok := bookmarks[bookmarkName]
- if !ok {
- return Bookmark{}, fmt.Errorf("couldn't find a bookmark with name %s", bookmarkName)
- }
-
- return bookmark, nil
-}
diff --git a/pkg/bookmarks/bookmarks_test.go b/pkg/bookmarks/bookmarks_test.go
index d3d59db..66c730d 100644
--- a/pkg/bookmarks/bookmarks_test.go
+++ b/pkg/bookmarks/bookmarks_test.go
@@ -8,115 +8,34 @@ import (
"github.com/stretchr/testify/assert"
)
-func Test_Invalid_Bookmark_Files(t *testing.T) {
- _, err := readServerConfig("foobar")
- assert.Error(t, err)
+func TestBookmarkSSHInfoIsEmpty(t *testing.T) {
+ t.Run("empty", func(t *testing.T) {
+ info := &shared.SSHInfo{
+ Host: "",
+ Port: "",
+ User: "",
+ }
- _, err = readServerConfig("../../data/invalid.toml")
- assert.Error(t, err)
- assert.Equal(t, "toml: line 1: expected '.' or '=', but got 'e' instead", err.Error())
+ b := Bookmark{SSH: nil}
+ assert.True(t, b.SSHInfoIsEmpty())
+
+ b = Bookmark{SSH: info}
+ assert.True(t, b.SSHInfoIsEmpty())
+ })
+
+ t.Run("populated", func(t *testing.T) {
+ info := &shared.SSHInfo{
+ Host: "localhost",
+ Port: "8080",
+ User: "postgres",
+ }
+
+ b := Bookmark{SSH: info}
+ assert.False(t, b.SSHInfoIsEmpty())
+ })
}
-func Test_Bookmark(t *testing.T) {
- bookmark, err := readServerConfig("../../data/bookmark.toml")
- assert.Equal(t, nil, err)
- assert.Equal(t, "localhost", bookmark.Host)
- assert.Equal(t, 5432, bookmark.Port)
- assert.Equal(t, "postgres", bookmark.User)
- assert.Equal(t, "mydatabase", bookmark.Database)
- assert.Equal(t, "disable", bookmark.SSLMode)
- assert.Equal(t, "", bookmark.Password)
- assert.Equal(t, "", bookmark.URL)
-
- bookmark, err = readServerConfig("../../data/bookmark_invalid_ssl.toml")
- assert.Equal(t, nil, err)
- assert.Equal(t, "disable", bookmark.SSLMode)
-}
-
-func Test_Bookmark_URL(t *testing.T) {
- bookmark, err := readServerConfig("../../data/bookmark_url.toml")
-
- assert.Equal(t, nil, err)
- assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", bookmark.URL)
- assert.Equal(t, "", bookmark.Host)
- assert.Equal(t, 5432, bookmark.Port)
- assert.Equal(t, "", bookmark.User)
- assert.Equal(t, "", bookmark.Database)
- assert.Equal(t, "disable", bookmark.SSLMode)
- assert.Equal(t, "", bookmark.Password)
-}
-
-func Test_Bookmarks_Path(t *testing.T) {
- assert.NotEqual(t, "/.pgweb/bookmarks", Path(""))
-}
-
-func Test_Basename(t *testing.T) {
- assert.Equal(t, "filename", fileBasename("filename.toml"))
- assert.Equal(t, "filename", fileBasename("path/filename.toml"))
- assert.Equal(t, "filename", fileBasename("~/long/path/filename.toml"))
- assert.Equal(t, "filename", fileBasename("filename"))
-}
-
-func Test_ReadBookmarks_Invalid(t *testing.T) {
- bookmarks, err := ReadAll("foobar")
-
- assert.Error(t, err)
- assert.Equal(t, 0, len(bookmarks))
-}
-
-func Test_ReadBookmarks(t *testing.T) {
- bookmarks, err := ReadAll("../../data")
-
- assert.Equal(t, nil, err)
- assert.Equal(t, 3, len(bookmarks))
-}
-
-func Test_GetBookmark(t *testing.T) {
- expBookmark := Bookmark{
-
- Host: "localhost",
- Port: 5432,
- User: "postgres",
- Password: "",
- Database: "mydatabase",
- SSLMode: "disable",
- }
- b, err := GetBookmark("../../data", "bookmark")
- if assert.NoError(t, err) {
- assert.Equal(t, expBookmark, b)
- }
-
- _, err = GetBookmark("../../data", "bar")
- expErrStr := "couldn't find a bookmark with name bar"
- assert.Equal(t, expErrStr, err.Error())
-
- _, err = GetBookmark("foo", "bookmark")
- assert.Error(t, err)
-}
-
-func Test_Bookmark_SSHInfoIsEmpty(t *testing.T) {
- emptySSH := &shared.SSHInfo{
- Host: "",
- Port: "",
- User: "",
- }
- populatedSSH := &shared.SSHInfo{
- Host: "localhost",
- Port: "8080",
- User: "postgres",
- }
-
- b := Bookmark{SSH: nil}
- assert.True(t, b.SSHInfoIsEmpty())
-
- b = Bookmark{SSH: emptySSH}
- assert.True(t, b.SSHInfoIsEmpty())
-
- b.SSH = populatedSSH
- assert.False(t, b.SSHInfoIsEmpty())
-}
-
-func Test_ConvertToOptions(t *testing.T) {
+func TestBookmarkConvertToOptions(t *testing.T) {
b := Bookmark{
URL: "postgres://username:password@host:port/database?sslmode=disable",
Host: "localhost",
@@ -136,6 +55,7 @@ func Test_ConvertToOptions(t *testing.T) {
DbName: "mydatabase",
SSLMode: "disable",
}
+
opt := b.ConvertToOptions()
assert.Equal(t, expOpt, opt)
}
diff --git a/pkg/bookmarks/manager.go b/pkg/bookmarks/manager.go
new file mode 100644
index 0000000..6b9af2d
--- /dev/null
+++ b/pkg/bookmarks/manager.go
@@ -0,0 +1,152 @@
+package bookmarks
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/BurntSushi/toml"
+)
+
+type Manager struct {
+ dir string
+}
+
+func NewManager(dir string) Manager {
+ return Manager{
+ dir: dir,
+ }
+}
+
+func (m Manager) Get(id string) (*Bookmark, error) {
+ bookmarks, err := m.list()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, b := range bookmarks {
+ if b.ID == id {
+ return &b, nil
+ }
+ }
+
+ return nil, fmt.Errorf("bookmark %v not found", id)
+}
+
+func (m Manager) List() ([]Bookmark, error) {
+ return m.list()
+}
+
+func (m Manager) ListIDs() ([]string, error) {
+ bookmarks, err := m.list()
+ if err != nil {
+ return nil, err
+ }
+
+ ids := make([]string, len(bookmarks))
+ for i, bookmark := range bookmarks {
+ ids[i] = bookmark.ID
+ }
+
+ return ids, nil
+}
+
+func (m Manager) list() ([]Bookmark, error) {
+ result := []Bookmark{}
+
+ if m.dir == "" {
+ return result, nil
+ }
+
+ info, err := os.Stat(m.dir)
+ if err != nil {
+ // Do not fail if base dir does not exists: it's not created by default
+ if errors.Is(err, os.ErrNotExist) {
+ fmt.Fprintf(os.Stderr, "[WARN] bookmarks dir %s does not exist\n", m.dir)
+ return result, nil
+ }
+ return nil, err
+ }
+ if !info.IsDir() {
+ return nil, fmt.Errorf("path %s is not a directory", m.dir)
+ }
+
+ dirEntries, err := os.ReadDir(m.dir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, entry := range dirEntries {
+ name := entry.Name()
+ if filepath.Ext(name) != ".toml" {
+ continue
+ }
+
+ bookmark, err := readBookmark(filepath.Join(m.dir, name))
+ if err != nil {
+ // Do not fail if one of the bookmarks is invalid
+ fmt.Fprintf(os.Stderr, "[WARN] bookmark file %s is invalid: %s\n", name, err)
+ continue
+ }
+
+ result = append(result, bookmark)
+ }
+
+ return result, nil
+}
+
+func readBookmark(path string) (Bookmark, error) {
+ bookmark := Bookmark{
+ ID: fileBasename(path),
+ }
+
+ _, err := os.Stat(path)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ err = fmt.Errorf("bookmark file %s does not exist", path)
+ }
+ return bookmark, err
+ }
+
+ buff, err := os.ReadFile(path)
+ if err != nil {
+ return bookmark, err
+ }
+
+ _, err = toml.Decode(string(buff), &bookmark)
+
+ if bookmark.Port == 0 {
+ bookmark.Port = 5432
+ }
+
+ // List of all supported postgres modes
+ modes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
+ valid := false
+
+ for _, mode := range modes {
+ if bookmark.SSLMode == mode {
+ valid = true
+ break
+ }
+ }
+
+ // Fall back to a default mode if mode is not set or invalid
+ // Typical typo: ssl mode set to "disabled"
+ if bookmark.SSLMode == "" || !valid {
+ bookmark.SSLMode = "disable"
+ }
+
+ // Set default SSH port if it's not provided by user
+ if bookmark.SSH != nil && bookmark.SSH.Port == "" {
+ bookmark.SSH.Port = "22"
+ }
+
+ return bookmark, err
+}
+
+func fileBasename(path string) string {
+ filename := filepath.Base(path)
+ return strings.Replace(filename, filepath.Ext(path), "", 1)
+}
diff --git a/pkg/bookmarks/manager_test.go b/pkg/bookmarks/manager_test.go
new file mode 100644
index 0000000..9b72b98
--- /dev/null
+++ b/pkg/bookmarks/manager_test.go
@@ -0,0 +1,98 @@
+package bookmarks
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestManagerList(t *testing.T) {
+ examples := []struct {
+ dir string
+ num int
+ err string
+ }{
+ {"../../data", 3, ""},
+ {"../../data/bookmark.toml", 0, "is not a directory"},
+ {"../../data2", 0, ""},
+ {"", 0, ""},
+ }
+
+ for _, ex := range examples {
+ t.Run(ex.dir, func(t *testing.T) {
+ bookmarks, err := NewManager(ex.dir).List()
+ if ex.err != "" {
+ assert.Contains(t, err.Error(), ex.err)
+ }
+ assert.Len(t, bookmarks, ex.num)
+ })
+ }
+}
+
+func TestManagerListIDs(t *testing.T) {
+ ids, err := NewManager("../../data").ListIDs()
+ assert.NoError(t, err)
+ assert.Equal(t, []string{"bookmark", "bookmark_invalid_ssl", "bookmark_url"}, ids)
+}
+
+func TestManagerGet(t *testing.T) {
+ manager := NewManager("../../data")
+
+ b, err := manager.Get("bookmark")
+ assert.NoError(t, err)
+ assert.Equal(t, "bookmark", b.ID)
+
+ b, err = manager.Get("foo")
+ assert.Equal(t, "bookmark foo not found", err.Error())
+ assert.Nil(t, b)
+}
+
+func Test_fileBasename(t *testing.T) {
+ assert.Equal(t, "filename", fileBasename("filename.toml"))
+ assert.Equal(t, "filename", fileBasename("path/filename.toml"))
+ assert.Equal(t, "filename", fileBasename("~/long/path/filename.toml"))
+ assert.Equal(t, "filename", fileBasename("filename"))
+}
+
+func Test_readBookmark(t *testing.T) {
+ t.Run("good", func(t *testing.T) {
+ b, err := readBookmark("../../data/bookmark.toml")
+ assert.NoError(t, err)
+ assert.Equal(t, "bookmark", b.ID)
+ assert.Equal(t, "localhost", b.Host)
+ assert.Equal(t, 5432, b.Port)
+ assert.Equal(t, "postgres", b.User)
+ assert.Equal(t, "mydatabase", b.Database)
+ assert.Equal(t, "disable", b.SSLMode)
+ assert.Equal(t, "", b.Password)
+ assert.Equal(t, "", b.URL)
+ })
+
+ t.Run("with url", func(t *testing.T) {
+ b, err := readBookmark("../../data/bookmark_url.toml")
+ assert.NoError(t, err)
+ assert.Equal(t, "postgres://username:password@host:port/database?sslmode=disable", b.URL)
+ assert.Equal(t, "", b.Host)
+ assert.Equal(t, 5432, b.Port)
+ assert.Equal(t, "", b.User)
+ assert.Equal(t, "", b.Database)
+ assert.Equal(t, "disable", b.SSLMode)
+ assert.Equal(t, "", b.Password)
+ })
+
+ t.Run("invalid ssl", func(t *testing.T) {
+ b, err := readBookmark("../../data/bookmark_invalid_ssl.toml")
+ assert.NoError(t, err)
+ assert.Equal(t, "disable", b.SSLMode)
+ })
+
+ t.Run("invalid file", func(t *testing.T) {
+ _, err := readBookmark("foobar")
+ assert.Equal(t, "bookmark file foobar does not exist", err.Error())
+ })
+
+ t.Run("invalid syntax", func(t *testing.T) {
+ _, err := readBookmark("../../data/invalid.toml")
+ assert.Equal(t, "toml: line 1: expected '.' or '=', but got 'e' instead", err.Error())
+ })
+}
diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go
index 2a939e9..7b92bbc 100644
--- a/pkg/cli/cli.go
+++ b/pkg/cli/cli.go
@@ -19,7 +19,6 @@ import (
"github.com/sosedoff/pgweb/pkg/client"
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/connection"
- "github.com/sosedoff/pgweb/pkg/shared"
"github.com/sosedoff/pgweb/pkg/util"
)
@@ -47,30 +46,14 @@ func exitWithMessage(message string) {
os.Exit(1)
}
-func initClientUsingBookmark(bookmarkPath, bookmarkName string) (*client.Client, error) {
- bookmark, err := bookmarks.GetBookmark(bookmarkPath, bookmarkName)
+func initClientUsingBookmark(baseDir, bookmarkName string) (*client.Client, error) {
+ manager := bookmarks.NewManager(baseDir)
+ bookmark, err := manager.Get(bookmarkName)
if err != nil {
return nil, err
}
- opt := bookmark.ConvertToOptions()
- var connStr string
-
- if opt.URL != "" { // if the bookmark has url set, use it
- connStr = opt.URL
- } else {
- connStr, err = connection.BuildStringFromOptions(opt)
- if err != nil {
- return nil, fmt.Errorf("error building connection string: %v", err)
- }
- }
-
- var ssh *shared.SSHInfo
- if !bookmark.SSHInfoIsEmpty() {
- ssh = bookmark.SSH
- }
-
- return client.NewFromUrl(connStr, ssh)
+ return client.NewFromBookmark(bookmark)
}
func initClient() {
@@ -82,7 +65,7 @@ func initClient() {
var err error
if options.Bookmark != "" {
- cl, err = initClientUsingBookmark(bookmarks.Path(options.BookmarksDir), options.Bookmark)
+ cl, err = initClientUsingBookmark(options.BookmarksDir, options.Bookmark)
} else {
cl, err = client.New()
}
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 22216ca..4fce389 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -13,6 +13,7 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
+ "github.com/sosedoff/pgweb/pkg/bookmarks"
"github.com/sosedoff/pgweb/pkg/command"
"github.com/sosedoff/pgweb/pkg/connection"
"github.com/sosedoff/pgweb/pkg/history"
@@ -137,6 +138,30 @@ func NewFromUrl(url string, sshInfo *shared.SSHInfo) (*Client, error) {
return &client, nil
}
+func NewFromBookmark(bookmark *bookmarks.Bookmark) (*Client, error) {
+ var (
+ connStr string
+ err error
+ )
+
+ options := bookmark.ConvertToOptions()
+ if options.URL != "" {
+ connStr = options.URL
+ } else {
+ connStr, err = connection.BuildStringFromOptions(options)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var sshInfo *shared.SSHInfo
+ if !bookmark.SSHInfoIsEmpty() {
+ sshInfo = bookmark.SSH
+ }
+
+ return NewFromUrl(connStr, sshInfo)
+}
+
func (client *Client) init() {
if command.Opts.QueryTimeout > 0 {
client.queryTimeout = time.Second * time.Duration(command.Opts.QueryTimeout)
diff --git a/pkg/client/tunnel.go b/pkg/client/tunnel.go
index b64e515..b539b5c 100644
--- a/pkg/client/tunnel.go
+++ b/pkg/client/tunnel.go
@@ -59,14 +59,13 @@ func parsePrivateKey(keyPath string, keyPass string) (ssh.Signer, error) {
}
signer, err := ssh.ParsePrivateKey(buff)
- if err != nil {
- if strings.Contains(err.Error(), "cannot decode encrypted private keys") {
- if keyPass == "" {
- return nil, errors.New("SSH key password is not provided")
- }
- return sshkeys.ParseEncryptedPrivateKey(buff, []byte(keyPass))
+ if _, ok := err.(*ssh.PassphraseMissingError); ok {
+ if keyPass == "" {
+ return nil, errors.New("SSH key password is not provided")
}
+ return sshkeys.ParseEncryptedPrivateKey(buff, []byte(keyPass))
}
+
return signer, err
}
diff --git a/pkg/command/options.go b/pkg/command/options.go
index 0bdf19c..0269e1e 100644
--- a/pkg/command/options.go
+++ b/pkg/command/options.go
@@ -10,6 +10,7 @@ import (
"github.com/jackc/pgpassfile"
"github.com/jessevdk/go-flags"
+ "github.com/mitchellh/go-homedir"
"github.com/sirupsen/logrus"
)
@@ -154,6 +155,13 @@ func ParseOptions(args []string) (Options, error) {
}
}
+ if opts.BookmarksDir == "" {
+ path, err := homedir.Dir()
+ if err == nil {
+ opts.BookmarksDir = filepath.Join(path, ".pgweb/bookmarks")
+ }
+ }
+
return opts, nil
}
diff --git a/pkg/command/options_test.go b/pkg/command/options_test.go
index 75eed5f..565002d 100644
--- a/pkg/command/options_test.go
+++ b/pkg/command/options_test.go
@@ -2,12 +2,19 @@ package command
import (
"os"
+ "path/filepath"
"testing"
+ "github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
)
func TestParseOptions(t *testing.T) {
+ var hdir string
+ if d, err := homedir.Dir(); err == nil {
+ hdir = d
+ }
+
t.Run("defaults", func(t *testing.T) {
opts, err := ParseOptions([]string{})
assert.NoError(t, err)
@@ -22,6 +29,7 @@ func TestParseOptions(t *testing.T) {
assert.Equal(t, false, opts.Cors)
assert.Equal(t, "*", opts.CorsOrigin)
assert.Equal(t, "", opts.Passfile)
+ assert.Equal(t, filepath.Join(hdir, ".pgweb/bookmarks"), opts.BookmarksDir)
})
t.Run("sessions", func(t *testing.T) {
diff --git a/static/js/app.js b/static/js/app.js
index 2323b4c..1b853b8 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -1000,18 +1000,18 @@ function getLatestReleaseInfo(current) {
function showConnectionSettings() {
// Show the current postgres version
$(".connection-settings .version").text("v" + appInfo.version).show();
+ $("#connection_window").show();
// Check github release page for updates
getLatestReleaseInfo(appInfo);
getBookmarks(function(data) {
- // Do not add any bookmarks if we've got an error
if (data.error) {
- console.log("Cant get bookmarks:", data.error);
+ console.log("Error while fetching bookmarks:", data.error);
return;
}
- if (Object.keys(data).length > 0) {
+ if (data.length > 0) {
// Set bookmarks in global var
bookmarks = data;
@@ -1019,10 +1019,10 @@ function showConnectionSettings() {
$("#connection_bookmarks").html("");
// Add blank option
- $("").appendTo("#connection_bookmarks");
+ $("").appendTo("#connection_bookmarks");
// Add all available bookmarks
- for (key in data) {
+ for (key of data) {
$("").appendTo("#connection_bookmarks");
}
@@ -1032,8 +1032,6 @@ function showConnectionSettings() {
$(".bookmarks").hide();
}
});
-
- $("#connection_window").show();
}
function getConnectionString() {
@@ -1624,67 +1622,43 @@ $(document).ready(function() {
});
$("#connection_bookmarks").on("change", function(e) {
- var name = $.trim($(this).val());
- if (name == "") return;
+ var selection = $(this).val();
- var item = bookmarks[name];
+ var inputs = [
+ $("#connection_form input[type='text']"),
+ $("#connection_form input[type='password']"),
+ $("#connection_ssl")
+ ];
- // Check if bookmark only has url set
- if (item.url && item.url != "") {
- $("#connection_url").val(item.url);
- $("#connection_scheme").click();
- return;
- }
-
- // Fill in bookmarked connection settings
- $("#pg_host").val(item.host);
- $("#pg_port").val(item.port);
- $("#pg_user").val(item.user);
- $("#pg_password").val(item.password);
- $("#pg_db").val(item.database);
- $("#connection_ssl").val(item.ssl);
-
- if (item.ssh && Object.keys(item.ssh).length > 0) {
- $("#ssh_host").val(item.ssh.host);
- $("#ssh_port").val(item.ssh.port);
- $("#ssh_user").val(item.ssh.user);
- $("#ssh_password").val(item.ssh.password);
- $("#ssh_key").val(item.ssh.key);
- $("#ssh_key_password").val(item.ssh.keypassword);
- $("#connection_ssh").click();
- }
- else {
- $("#ssh_host").val("");
- $("#ssh_port").val("");
- $("#ssh_user").val("");
- $("#ssh_password").val("");
- $("#ssh_key").val("");
- $("#ssh_key_password").val("");
- $(".connection-ssh-group").hide();
- $("#connection_standard").click();
- }
+ inputs.forEach(function(selector) {
+ selector.val("").prop("disabled", selection == "" ? "" : "disabled");
+ });
});
$("#connection_form").on("submit", function(e) {
e.preventDefault();
var button = $(this).find("button.open-connection");
- var params = {
- url: getConnectionString()
- };
+ var params = {};
- if (params.url.length == 0) {
- return;
+ if ($("#connection_bookmarks").val() != "") {
+ params["bookmark_id"] = $("#connection_bookmarks").val();
}
+ else {
+ params.url = getConnectionString();
+ if (params.url.length == 0) {
+ return;
+ }
- if ($(".connection-group-switch button.active").attr("data") == "ssh") {
- params["ssh"] = 1
- params["ssh_host"] = $("#ssh_host").val();
- params["ssh_port"] = $("#ssh_port").val();
- params["ssh_user"] = $("#ssh_user").val();
- params["ssh_password"] = $("#ssh_password").val();
- params["ssh_key"] = $("#ssh_key").val();
- params["ssh_key_password"] = $("#ssh_key_password").val()
+ if ($(".connection-group-switch button.active").attr("data") == "ssh") {
+ params["ssh"] = 1
+ params["ssh_host"] = $("#ssh_host").val();
+ params["ssh_port"] = $("#ssh_port").val();
+ params["ssh_user"] = $("#ssh_user").val();
+ params["ssh_password"] = $("#ssh_password").val();
+ params["ssh_key"] = $("#ssh_key").val();
+ params["ssh_key_password"] = $("#ssh_key_password").val()
+ }
}
$("#connection_error").hide();