FEAT: Enum support all over!
Enum table is generated below the Tables tree. Enums cannot be queried since I don't know what I would want them to query so right now its just for viewing. However, enum types are now displayed properly in the table tree. I still don't know how strong the support will be for other kinds of DBs that aren't PSQL.
This commit is contained in:
parent
8ab00b20d4
commit
83dafb8a08
@ -38,5 +38,5 @@ func ChangeConnection(c *gin.Context) {
|
|||||||
session.Set("current", name)
|
session.Set("current", name)
|
||||||
session.Save()
|
session.Save()
|
||||||
|
|
||||||
c.String(200, templates.ConnectionsList(connections, name)+TableTree(c))
|
c.String(200, templates.ConnectionsList(connections, name)+TableTree(c)+EnumTree(c))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ func TableTree(c *gin.Context) string {
|
|||||||
|
|
||||||
url := connections[current]
|
url := connections[current]
|
||||||
|
|
||||||
tree, err := generateTree(url)
|
tree, err := generateTableTree(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return ""
|
return ""
|
||||||
@ -39,7 +39,7 @@ func TableTree(c *gin.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate the tree of the database tables
|
// Generate the tree of the database tables
|
||||||
func generateTree(url string) (map[string][]model.Column, error) {
|
func generateTableTree(url string) (map[string][]model.Column, error) {
|
||||||
conn, err := sql.Open("postgres", url)
|
conn, err := sql.Open("postgres", url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return map[string][]model.Column{}, err
|
return map[string][]model.Column{}, err
|
||||||
@ -118,17 +118,23 @@ func fillColumns(conn *sql.DB, tree map[string][]model.Column) error {
|
|||||||
fkeys = append(fkeys, fkey)
|
fkeys = append(fkeys, fkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := conn.Query(fmt.Sprintf("SELECT column_name, is_nullable, data_type, character_maximum_length FROM information_schema.columns WHERE table_name = '%s';", table))
|
rows, err := conn.Query(fmt.Sprintf("SELECT c.column_name, c.is_nullable, c.data_type, c.character_maximum_length, t.typname AS enum_type FROM information_schema.columns c JOIN pg_type t ON c.udt_name = t.typname WHERE c.table_name = '%s';", table))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var column model.Column
|
var (
|
||||||
if err := rows.Scan(&column.Name, &column.Nullable, &column.Type, &column.MaxLength); err != nil {
|
column model.Column
|
||||||
|
enumType string
|
||||||
|
)
|
||||||
|
if err := rows.Scan(&column.Name, &column.Nullable, &column.Type, &column.MaxLength, &enumType); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if column.Type == "USER-DEFINED" {
|
||||||
|
column.Type = enumType
|
||||||
|
}
|
||||||
if column.Name == pkey {
|
if column.Name == pkey {
|
||||||
column.PrimaryKey = true
|
column.PrimaryKey = true
|
||||||
}
|
}
|
||||||
@ -172,3 +178,69 @@ func getUniqueColumns(conn *sql.DB, table string) ([]string, error) {
|
|||||||
|
|
||||||
return cols, nil
|
return cols, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate the tree of the database enums and their values
|
||||||
|
func EnumTree(c *gin.Context) string {
|
||||||
|
session := sessions.Default(c)
|
||||||
|
connections_bytes, ok := session.Get("connections").([]byte)
|
||||||
|
current, ok := session.Get("current").(string)
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("No connections found")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections map[string]string
|
||||||
|
if err := json.Unmarshal(connections_bytes, &connections); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
url := connections[current]
|
||||||
|
|
||||||
|
enums, err := genereteEnumTree(url)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates.EnumTree(enums)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the tree of the database enums and their values from a
|
||||||
|
// provided connection URL.
|
||||||
|
func genereteEnumTree(url string) (map[string][]string, error) {
|
||||||
|
conn, err := sql.Open("postgres", url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
enums, err := enumList(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enums, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list/map of all the enums in the database.
|
||||||
|
// The key is the name of the enum and the value is a slice of the enum values.
|
||||||
|
func enumList(conn *sql.DB) (map[string][]string, error) {
|
||||||
|
rows, err := conn.Query("SELECT t.typname AS enum_name, e.enumlabel AS enum_value FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid JOIN pg_namespace n ON n.oid = t.typnamespace WHERE t.typcategory = 'E' AND n.nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY t.typname, e.enumsortorder;")
|
||||||
|
if err != nil {
|
||||||
|
return map[string][]string{}, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
enums := make(map[string][]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var enum, value string
|
||||||
|
if err := rows.Scan(&enum, &value); err != nil {
|
||||||
|
return map[string][]string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
enums[enum] = append(enums[enum], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enums, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -70,9 +70,15 @@ func populate(web, api *gin.RouterGroup) {
|
|||||||
})
|
})
|
||||||
api.POST("/connections/connect", database.ChangeConnection)
|
api.POST("/connections/connect", database.ChangeConnection)
|
||||||
|
|
||||||
web.GET("/connections/tree", func(c *gin.Context) {
|
web.GET("/connections/tree/table", func(c *gin.Context) {
|
||||||
c.String(200, database.TableTree(c))
|
c.String(200, database.TableTree(c))
|
||||||
})
|
})
|
||||||
|
web.GET("/connections/tree/enum", func(c *gin.Context) {
|
||||||
|
c.String(200, database.EnumTree(c))
|
||||||
|
})
|
||||||
|
web.GET("/connections/tree", func(c *gin.Context) {
|
||||||
|
c.String(200, database.TableTree(c)+database.EnumTree(c))
|
||||||
|
})
|
||||||
|
|
||||||
web.GET("/query/auto", templates.ToggleQueryType)
|
web.GET("/query/auto", templates.ToggleQueryType)
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import (
|
|||||||
"github.com/Azpect3120/Web-Database-Viewer/internal/model"
|
"github.com/Azpect3120/Web-Database-Viewer/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tree definition
|
// Table tree definition
|
||||||
const TREE_OPEN string = `<ul hx-swap-oob="outerHTML" id="database-table-tree" class="space-y-2">`
|
const TABLE_TREE_OPEN string = `<ul hx-swap-oob="outerHTML" id="database-table-tree" class="space-y-2">`
|
||||||
const TREE_CLOSE string = `</ul>`
|
const TABLE_TREE_CLOSE string = `</ul>`
|
||||||
const TREE_BODY_TEMPLATE string = `<li>%s</li>`
|
const TABLE_TREE_BODY_TEMPLATE string = `<li>%s</li>`
|
||||||
|
|
||||||
// Table definition
|
// Table definition
|
||||||
const TABLE_TEMPLATE string = `
|
const TABLE_TEMPLATE string = `
|
||||||
@ -26,9 +26,9 @@ const TABLE_TEMPLATE string = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
// Fields definition
|
// Fields definition
|
||||||
const FIELDS_LIST_OPEN string = `<ul id="fields-%s" class="hidden ml-6 mt-1 space-y-1 text-gray-600">`
|
const TABLE_FIELDS_LIST_OPEN string = `<ul id="fields-%s" class="hidden ml-6 mt-1 space-y-1 text-gray-600">`
|
||||||
const FIELDS_LIST_CLOSE string = `</ul>`
|
const TABLE_FIELDS_LIST_CLOSE string = `</ul>`
|
||||||
const FIELD_TEMPLATE string = `
|
const TABLE_FIELD_TEMPLATE string = `
|
||||||
<li>
|
<li>
|
||||||
<button onclick="LoadTableQueryWithFields('%s', '%s')" class="flex items-center w-full" title="Select this field">
|
<button onclick="LoadTableQueryWithFields('%s', '%s')" class="flex items-center w-full" title="Select this field">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
@ -42,35 +42,63 @@ const FIELD_TEMPLATE string = `
|
|||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
|
|
||||||
// This is not implemented yet
|
// Enum tree definition
|
||||||
const PRIMARY_KEY string = `<span class="h-1.5 w-1.5 bg-yellow-500 rounded-full mx-2" title="Primary Key"></span>`
|
const ENUM_TREE_OPEN string = `<ul hx-swap-oob="outerHTML" id="database-enum-tree" class="space-y-2">`
|
||||||
|
const ENUM_TREE_CLOSE string = `</ul>`
|
||||||
|
const ENUM_TREE_BODY_TEMPLATE string = `<li>%s</li>`
|
||||||
|
|
||||||
|
// Enum definition
|
||||||
|
const ENUM_TEMPLATE string = `
|
||||||
|
<button class="w-full text-left text-gray-700 font-medium hover:bg-gray-100 p-2 rounded flex items-center">
|
||||||
|
<svg onclick="ToggleEnumValues('%s');" id="icon-enum-squeeze" class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" transform="rotate(-90)">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 9l6 6 6-6"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="hover:underline py-1" onclick="ToggleEnumValues('%s');">%s</span>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Enum values definition
|
||||||
|
const ENUM_VALUES_LIST_OPEN string = `<ul id="enum-values-%s" class="hidden ml-6 mt-1 space-y-1 text-gray-600">`
|
||||||
|
const ENUM_VALUES_LIST_CLOSE string = `</ul>`
|
||||||
|
const ENUM_VALUE_TEMPLATE string = `
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center w-full py-2">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
<span>%s</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
|
||||||
// Generate the tree based on the database tables and columns
|
// Generate the tree based on the database tables and columns
|
||||||
func TableTree(tree map[string][]model.Column) string {
|
func TableTree(tree map[string][]model.Column) string {
|
||||||
html := TREE_OPEN
|
html := TABLE_TREE_OPEN
|
||||||
|
|
||||||
var body string
|
var body string
|
||||||
for _, table := range getSortedKeys(tree) {
|
for _, table := range getSortedKeys(tree) {
|
||||||
body += fmt.Sprintf(TABLE_TEMPLATE, table, table, table, table, table)
|
body += fmt.Sprintf(TABLE_TEMPLATE, table, table, table, table, table)
|
||||||
fields := fmt.Sprintf(FIELDS_LIST_OPEN, table)
|
fields := fmt.Sprintf(TABLE_FIELDS_LIST_OPEN, table)
|
||||||
body += fields + generateFields(table, tree[table]) + FIELDS_LIST_CLOSE
|
body += fields + generateFields(table, tree[table]) + TABLE_FIELDS_LIST_CLOSE
|
||||||
}
|
}
|
||||||
|
|
||||||
html += fmt.Sprintf(TREE_BODY_TEMPLATE, body)
|
html += fmt.Sprintf(TABLE_TREE_BODY_TEMPLATE, body)
|
||||||
return html + TREE_CLOSE
|
return html + TABLE_TREE_CLOSE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using a list of fields, generate the HTML for the fields
|
// Using a list of fields, generate the HTML for the fields
|
||||||
func generateFields(table string, fields []model.Column) string {
|
func generateFields(table string, fields []model.Column) string {
|
||||||
var html string
|
var html string
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
html += fmt.Sprintf(FIELD_TEMPLATE, table, field.Name, field.Name, generateType(field))
|
html += fmt.Sprintf(TABLE_FIELD_TEMPLATE, table, field.Name, field.Name, generateType(field))
|
||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a list of the keys in a map, sorted alphabetically
|
// Return a list of the keys in a map, sorted alphabetically
|
||||||
func getSortedKeys(m map[string][]model.Column) []string {
|
func getSortedKeys[T model.Column | string](m map[string][]T) []string {
|
||||||
keys := make([]string, 0, len(m))
|
keys := make([]string, 0, len(m))
|
||||||
for k := range m {
|
for k := range m {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
@ -106,3 +134,28 @@ func generateType(col model.Column) string {
|
|||||||
|
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate the HTML string for the enum tree
|
||||||
|
func EnumTree(enums map[string][]string) string {
|
||||||
|
html := ENUM_TREE_OPEN
|
||||||
|
var body string
|
||||||
|
|
||||||
|
for _, enum := range getSortedKeys(enums) {
|
||||||
|
body += fmt.Sprintf(ENUM_TEMPLATE, enum, enum, enum)
|
||||||
|
valuesList := fmt.Sprintf(ENUM_VALUES_LIST_OPEN, enum)
|
||||||
|
body += valuesList + generateEnumValues(enums[enum]) + ENUM_VALUES_LIST_CLOSE
|
||||||
|
}
|
||||||
|
|
||||||
|
html += fmt.Sprintf(ENUM_TREE_BODY_TEMPLATE, body)
|
||||||
|
|
||||||
|
return html + ENUM_TREE_CLOSE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a list of values into a list of HTML elements
|
||||||
|
func generateEnumValues(values []string) string {
|
||||||
|
var html string
|
||||||
|
for _, value := range values {
|
||||||
|
html += fmt.Sprintf(ENUM_VALUE_TEMPLATE, value)
|
||||||
|
}
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
*
|
*
|
||||||
* This file also contains the functions that are used to generate quick queries for the
|
* This file also contains the functions that are used to generate quick queries for the
|
||||||
* tables.
|
* tables.
|
||||||
|
*
|
||||||
|
* This file also contains the functions that are used to toggle the visibility of the
|
||||||
|
* enum values in the tree view of the tables.
|
||||||
*/
|
*/
|
||||||
function ToggleFields(id) {
|
function ToggleFields(id) {
|
||||||
const fields = document.getElementById(`fields-${id}`);
|
const fields = document.getElementById(`fields-${id}`);
|
||||||
@ -29,3 +32,15 @@ function LoadTableQueryWithFields(table, fields) {
|
|||||||
sql.dispatchEvent(new Event("input", { bubbles: true }));
|
sql.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ToggleEnumValues(id) {
|
||||||
|
const enum_values = document.getElementById(`enum-values-${id}`);
|
||||||
|
const button_svg = document.getElementById(`icon-enum-${id}`);
|
||||||
|
|
||||||
|
if (enum_values.classList.contains("hidden")) {
|
||||||
|
enum_values.classList.remove("hidden");
|
||||||
|
button_svg.setAttribute("transform", "rotate(0)");
|
||||||
|
} else {
|
||||||
|
enum_values.classList.add("hidden");
|
||||||
|
button_svg.setAttribute("transform", "rotate(-90)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -8,11 +8,6 @@
|
|||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.1"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.1"></script>
|
||||||
<link rel="icon" type="image/png" href="/v1/web/assets/favicon.ico">
|
<link rel="icon" type="image/png" href="/v1/web/assets/favicon.ico">
|
||||||
<style>
|
|
||||||
.htmx-request {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-100">
|
<body class="bg-gray-100">
|
||||||
@ -45,29 +40,37 @@
|
|||||||
|
|
||||||
<div class="flex flex-col md:flex-row flex-grow">
|
<div class="flex flex-col md:flex-row flex-grow">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="w-full md:w-1/4 bg-white shadow-md">
|
<div class="w-full md:w-1/4 bg-white shadow-md">
|
||||||
<div class="p-4 border-b flex justify-between items-center">
|
<div class="p-4 border-b flex justify-between items-center">
|
||||||
<h2 class="text-lg font-bold">
|
<h2 class="text-lg font-bold">
|
||||||
<span id="database-name-tree">database</span>
|
<span id="database-name-tree">database</span>
|
||||||
<span id="table-loading" class="text-sm font-normal mx-1 htmx-indicator duration-300">loading
|
</h2>
|
||||||
tables...</span>
|
<button hx-get="/v1/web/connections/tree" hx-trigger="click" hx-swap="none" class="hover:bg-gray-100 p-2 rounded-md" hx-indicator="#table-loading">
|
||||||
</h2>
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon h-4 w-4" viewBox="0 0 512 512">
|
||||||
<button hx-get="/v1/web/connections/tree" hx-trigger="click" hx-target="#database-table-tree"
|
<path
|
||||||
class="hover:bg-gray-100 p-2 rounded-md" hx-indicator="#table-loading">
|
d="M400 148l-21.12-24.57A191.43 191.43 0 00240 64C134 64 48 150 48 256s86 192 192 192a192.09 192.09 0 00181.07-128"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon h-4 w-4" viewBox="0 0 512 512">
|
fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" />
|
||||||
<path
|
<path
|
||||||
d="M400 148l-21.12-24.57A191.43 191.43 0 00240 64C134 64 48 150 48 256s86 192 192 192a192.09 192.09 0 00181.07-128"
|
d="M464 97.42V208a16 16 0 01-16 16H337.42c-14.26 0-21.4-17.23-11.32-27.31L436.69 86.1C446.77 76 464 83.16 464 97.42z" />
|
||||||
fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" />
|
</svg>
|
||||||
<path
|
</button>
|
||||||
d="M464 97.42V208a16 16 0 01-16 16H337.42c-14.26 0-21.4-17.23-11.32-27.31L436.69 86.1C446.77 76 464 83.16 464 97.42z" />
|
</div>
|
||||||
</svg>
|
<div class="p-4 max-h-full" hx-get="/v1/web/connections/tree/table" hx-trigger="load" hx-params="none" hx-indicator="#table-loading" hx-target="#database-table-tree">
|
||||||
</button>
|
<div class="w-full flex items-center justify-between border-b pb-4 pt-2">
|
||||||
|
<h2 class="text-lg text-gray-700">Tables</h2>
|
||||||
|
<p id="table-loading" class="text-xs font-light htmx-indicator">Loading...</p>
|
||||||
|
</div>
|
||||||
|
<ul hx-swap-oob="outerHTML" id="database-table-tree" class="space-y-2"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 max-h-full" hx-get="/v1/web/connections/tree/enum" hx-trigger="load" hx-params="none" hx-indicator="#enum-loading" hx-target="#database-enum-tree">
|
||||||
|
<div class="w-full flex items-center justify-between border-b pb-4 pt-2">
|
||||||
|
<h2 class="text-lg text-gray-700">Enums</h2>
|
||||||
|
<p id="enum-loading" class="text-xs font-light htmx-indicator">Loading...</p>
|
||||||
|
</div>
|
||||||
|
<ul hx-swap-oob="outerHTML" id="database-enum-tree" class="space-y-2"></ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 max-h-full" hx-get="/v1/web/connections/tree" hx-trigger="load" hx-params="none"
|
|
||||||
hx-indicator="#table-loading" hx-target="#database-table-tree">
|
|
||||||
<ul hx-swap-oob="outerHTML" id="database-table-tree" class="space-y-2"></ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="w-full md:w-3/4 p-4">
|
<div class="w-full md:w-3/4 p-4">
|
||||||
@ -131,21 +134,27 @@
|
|||||||
<form id="connection-form" class="grid grid-cols-2 gap-4">
|
<form id="connection-form" class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="db-host" class="block text-sm font-medium text-gray-700">Host</label>
|
<label for="db-host" class="block text-sm font-medium text-gray-700">Host</label>
|
||||||
<input id="db-host" name="db-host" type="text" placeholder="127.0.0.1" class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input id="db-host" name="db-host" type="text" placeholder="127.0.0.1"
|
||||||
|
class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="db-port" class="block text-sm font-medium text-gray-700">Port</label>
|
<label for="db-port" class="block text-sm font-medium text-gray-700">Port</label>
|
||||||
<input id="db-port" name="db-port" type="text" placeholder="5432" class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input id="db-port" name="db-port" type="text" placeholder="5432"
|
||||||
|
class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="db-username" class="block text-sm font-medium text-gray-700">Username</label>
|
<label for="db-username" class="block text-sm font-medium text-gray-700">Username</label>
|
||||||
<input id="db-username" name="db-username" type="text" placeholder="admin" class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input id="db-username" name="db-username" type="text" placeholder="admin"
|
||||||
|
class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="db-password" class="block text-sm font-medium text-gray-700">Password</label>
|
<label for="db-password" class="block text-sm font-medium text-gray-700">Password</label>
|
||||||
<div class="relative mt-1">
|
<div class="relative mt-1">
|
||||||
<input id="db-password" name="db-password" type="password" placeholder="●●●●●●●●●" class="block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input id="db-password" name="db-password" type="password" placeholder="●●●●●●●●●"
|
||||||
<button type="button" id="togglePassword" class="absolute inset-y-0 right-0 px-3 py-2 text-gray-500 bg-gray-200 rounded-r-md border border-gray-300" title="Display secret details">
|
class="block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
|
<button type="button" id="togglePassword"
|
||||||
|
class="absolute inset-y-0 right-0 px-3 py-2 text-gray-500 bg-gray-200 rounded-r-md border border-gray-300"
|
||||||
|
title="Display secret details">
|
||||||
<svg id="eyeIcon" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none"
|
<svg id="eyeIcon" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M1 12s3.5-7 11-7 11 7 11 7-3.5 7-11 7S1 12 1 12z"></path>
|
<path d="M1 12s3.5-7 11-7 11 7 11 7-3.5 7-11 7S1 12 1 12z"></path>
|
||||||
@ -169,7 +178,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="db-database" class="block text-sm font-medium text-gray-700">Database Name</label>
|
<label for="db-database" class="block text-sm font-medium text-gray-700">Database Name</label>
|
||||||
<input id="db-database" name="db-database" type="text" placeholder="master_database" class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input id="db-database" name="db-database" type="text" placeholder="master_database"
|
||||||
|
class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<label for="db-url" class="block text-sm font-medium text-gray-700">
|
<label for="db-url" class="block text-sm font-medium text-gray-700">
|
||||||
@ -180,7 +190,8 @@
|
|||||||
will match the database name.
|
will match the database name.
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<input name="db-conn-name" id="db-conn-name" placeholder="master_database" class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
<input name="db-conn-name" id="db-conn-name" placeholder="master_database"
|
||||||
|
class="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<label for="db-url" class="block text-sm font-medium text-gray-700">
|
<label for="db-url" class="block text-sm font-medium text-gray-700">
|
||||||
@ -201,15 +212,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="flex items-center space-x-4 mt-4">
|
<div class="flex items-center space-x-4 mt-4">
|
||||||
<button
|
<button hx-post="/v1/api/connections" hx-trigger="click" hx-target="#connected-database" hx-swap="outerHTML"
|
||||||
hx-post="/v1/api/connections"
|
hx-include="#connection-form" hx-on::after-request="HideModal();"
|
||||||
hx-trigger="click"
|
class="bg-blue-500 text-white px-4 py-2 rounded-md">
|
||||||
hx-target="#connected-database"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-include="#connection-form"
|
|
||||||
hx-on::after-request="HideModal();"
|
|
||||||
class="bg-blue-500 text-white px-4 py-2 rounded-md"
|
|
||||||
>
|
|
||||||
Create Connection
|
Create Connection
|
||||||
</button>
|
</button>
|
||||||
<button hx-post="/v1/api/connections/test" hx-trigger="click" hx-swap="outerHTML"
|
<button hx-post="/v1/api/connections/test" hx-trigger="click" hx-swap="outerHTML"
|
||||||
@ -234,4 +239,5 @@
|
|||||||
<script src="/v1/web/static/scripts/modal.js"></script>
|
<script src="/v1/web/static/scripts/modal.js"></script>
|
||||||
<script src="/v1/web/static/scripts/tree.js"></script>
|
<script src="/v1/web/static/scripts/tree.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user