FIX: Close DB connection and htmx spinner

This commit is contained in:
Azpect3120 2024-08-07 15:20:00 -07:00
parent 6dc9c19749
commit 0da4821724
2 changed files with 217 additions and 208 deletions

View File

@ -30,6 +30,7 @@ func queryConnection(query, url string) ([]string, []map[string]interface{}, err
if err != nil { if err != nil {
return []string{}, []map[string]interface{}{}, err return []string{}, []map[string]interface{}{}, err
} }
defer db.Close()
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {

View File

@ -1,24 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Database Query Tool</title> <title>Database Query Tool</title>
<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">
</head> </head>
<body class="bg-gray-100"> <body class="bg-gray-100">
<div class="flex flex-col h-screen"> <div class="flex flex-col h-screen">
<!-- Top Bar --> <!-- Top Bar -->
<div class="bg-white shadow-md p-4 flex items-center justify-between border-b"> <div class="bg-white shadow-md p-4 flex items-center justify-between border-b">
<div> <div>
<h1 class="text-2xl font-bold">Database Query Tool</h1> <h1 class="text-2xl font-bold">Database Query Tool</h1>
<p class="text-sm text-gray-600">Connect and query your databases effortlessly.</p> <p class="text-sm text-gray-600">Connect and query your databases effortlessly.</p>
</div> </div>
<div class="flex items-center justify-end space-x-4 flex-wrap"> <div class="flex items-center justify-end space-x-4 flex-wrap">
<form hx-post="/v1/api/connections/connect" hx-trigger="change" hx-swap="outerHTML" hx-target="#connected-database" hx-encoding="multipart/form-data" class="flex items-center justify-end space-x-2 flex-wrap"> <form hx-post="/v1/api/connections/connect" hx-trigger="change" hx-swap="outerHTML" hx-target="#connected-database" hx-encoding="multipart/form-data" class="flex items-center justify-end space-x-2 flex-wrap">
<label for="connected-database" class="block text-sm font-medium text-gray-700">Connected Database:</label> <label for="connected-database" class="block text-sm font-medium text-gray-700">Connected Database:</label>
@ -30,207 +30,215 @@
</div> </div>
</div> </div>
<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"> <div class="p-4 border-b">
<h2 class="text-lg font-bold"><em>Database 1</em> Tables</h2> <h2 class="text-lg font-bold"><em>Database 1</em> Tables</h2>
</div>
<div class="p-4 max-h-full">
<ul class="space-y-2">
<li>
<button class="w-full text-left text-gray-700 font-medium hover:bg-gray-100 p-2 rounded flex items-center"
title="Select this table">
<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="M6 9l6 6 6-6"></path>
</svg>
Table 1
</button>
<ul class="ml-6 mt-1 space-y-1 text-gray-600">
<li>
<button class="flex items-center" title="Select this field">
<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>Column 1</span>
<span class="h-1.5 w-1.5 bg-yellow-500 rounded-full mx-2" title="Primary Key"></span>
</button>
</li>
<li>
<button class="flex items-center" title="Select this field">
<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>Column 2</span>
</button>
</li>
</ul>
</li>
<li>
<button class="w-full text-left text-gray-700 font-medium hover:bg-gray-100 p-2 rounded flex items-center"
title="Select this table">
<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="M6 9l6 6 6-6"></path>
</svg>
Table 2
</button>
<ul class="ml-6 mt-1 space-y-1 text-gray-600">
<li>
<button class="flex items-center" title="Select this field">
<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>Column A</span>
<span class="h-1.5 w-1.5 bg-yellow-500 rounded-full mx-2" title="Primary Key"></span>
</button>
</li>
<li>
<button class="flex items-center" title="Select this field">
<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>Column B</span>
</button>
</li>
</ul>
</li>
</ul>
</div>
</div>
<!-- Main Content -->
<div class="w-full md:w-3/4 p-4">
<main>
<!-- Query Input -->
<form class="mb-4" hx-post="/v1/api/query" hx-trigger="input delay:1000ms" hx-swap="outerHTML" hx-target="#query-result">
<label for="sql" class="block text-sm font-medium text-gray-700">SQL Query</label>
<textarea id="sql" name="sql" rows="4" 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"></textarea>
<p id="query-error" hx-swap-oob="outerHTML" class="text-red-500 py-2 text-sm hidden"></p>
</form>
<!-- Query Results -->
<div class="overflow-x-auto overflow-y-hidden bg-white shadow-md rounded-lg">
<table id="query-result" class="min-w-full divide-y divide-gray-200"></table>
</div> </div>
</main> <div class="p-4 max-h-full">
</div> <ul class="space-y-2">
</div> <li>
</div> <button class="w-full text-left text-gray-700 font-medium hover:bg-gray-100 p-2 rounded flex items-center"
title="Select this table">
<!-- Create Connection Modal --> <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"
<div id="connection-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden opacity-0"> xmlns="http://www.w3.org/2000/svg">
<div class="flex items-center justify-center min-h-screen"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 9l6 6 6-6"></path>
<div class="bg-white p-6 rounded-lg shadow-lg w-2/3">
<div class="flex justify-between items-center border-b pb-2">
<h2 class="text-xl font-bold">Add New Connection</h2>
<button onclick="HideModal();" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" 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="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="mt-4">
<form id="connection-form" class="grid grid-cols-2 gap-4">
<div>
<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" value="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>
<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" value="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>
<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" value="azpect"
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>
<label for="db-password" class="block text-sm font-medium text-gray-700">Password</label>
<div class="relative mt-1">
<input id="db-password" name="db-password" type="password" placeholder="●●●●●●●●●"
value="Panther4487!!!!"
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"
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>
<circle cx="12" cy="12" r="3"></circle>
</svg> </svg>
Table 1
</button> </button>
<ul class="ml-6 mt-1 space-y-1 text-gray-600">
<li>
<button class="flex items-center" title="Select this field">
<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>Column 1</span>
<span class="h-1.5 w-1.5 bg-yellow-500 rounded-full mx-2" title="Primary Key"></span>
</button>
</li>
<li>
<button class="flex items-center" title="Select this field">
<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>Column 2</span>
</button>
</li>
</ul>
</li>
<li>
<button class="w-full text-left text-gray-700 font-medium hover:bg-gray-100 p-2 rounded flex items-center"
title="Select this table">
<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="M6 9l6 6 6-6"></path>
</svg>
Table 2
</button>
<ul class="ml-6 mt-1 space-y-1 text-gray-600">
<li>
<button class="flex items-center" title="Select this field">
<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>Column A</span>
<span class="h-1.5 w-1.5 bg-yellow-500 rounded-full mx-2" title="Primary Key"></span>
</button>
</li>
<li>
<button class="flex items-center" title="Select this field">
<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>Column B</span>
</button>
</li>
</ul>
</li>
</ul>
</div>
</div>
<!-- Main Content -->
<div class="w-full md:w-3/4 p-4">
<main>
<!-- Query Input -->
<form
class="mb-4"
hx-post="/v1/api/query"
hx-trigger="input delay:500ms"
hx-swap="outerHTML"
hx-target="#query-result"
hx-indicator="#spinner"
>
<label for="sql" class="block text-sm font-medium text-gray-700">SQL Query</label>
<textarea id="sql" name="sql" rows="4" 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"></textarea>
<p id="spinner" class="text-gray-700 text-sm px-1 py-2 htmx-indicator"> Query running... </p>
<p id="query-error" hx-swap-oob="outerHTML" class="text-red-500 py-2 text-sm hidden"></p>
</form>
<!-- Query Results -->
<div class="overflow-x-auto overflow-y-hidden bg-white shadow-md rounded-lg">
<table id="query-result" class="min-w-full divide-y divide-gray-200"></table>
</div>
</main>
</div>
</div>
</div>
<!-- Create Connection Modal -->
<div id="connection-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden opacity-0">
<div class="flex items-center justify-center min-h-screen">
<div class="bg-white p-6 rounded-lg shadow-lg w-2/3">
<div class="flex justify-between items-center border-b pb-2">
<h2 class="text-xl font-bold">Add New Connection</h2>
<button onclick="HideModal();" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" 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="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="mt-4">
<form id="connection-form" class="grid grid-cols-2 gap-4">
<div>
<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" value="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>
<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" value="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>
<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" value="azpect"
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>
<label for="db-password" class="block text-sm font-medium text-gray-700">Password</label>
<div class="relative mt-1">
<input id="db-password" name="db-password" type="password" placeholder="●●●●●●●●●"
value="Panther4487!!!!"
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"
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>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
</div>
</div>
<div>
<label for="db-driver" class="block text-sm font-medium text-gray-700">Driver/Type of Database</label>
<select id="db-driver" name="db-driver"
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">
<option value="postgresql">PostgreSQL</option>
<option value="mysql">MySQL</option>
<option value="sqlite">SQLite</option>
<option value="sqlserver">SQL Server</option>
<option value="oracle">Oracle</option>
<option value="mariadb">MariaDB</option>
<option value="db2">DB2</option>
</select>
</div>
<div>
<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" value="azpect"
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 class="col-span-2">
<label for="db-url" class="block text-sm font-medium text-gray-700">
Connection URL
<br>
<span class="text-xs font-light">
The connection URL will be automatically generated based on the above fields. To view the URL
generated,
push the "display secret details" button in the password section.
</span>
</label>
<input id="db-url" name="db-url" type="password"
placeholder="postgresql://admin:password@127.0.0.1:5432/master_database"
value="postgresql://azpect:Panther4487!!!!@127.0.0.1:5432/azpect?sslmode=disable"
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">
<span id="db-url-invalid" class="text-xs text-red-500 hidden">Connection URL is incomplete or
invalid.</span>
</div>
</form>
<div class="flex items-center space-x-4 mt-4">
<button hx-post="/v1/api/connections" hx-trigger="click" hx-target="#connected-database" hx-swap="outerHTML"
hx-include="#connection-form" onclick="HideModal();" class="bg-blue-500 text-white px-4 py-2 rounded-md">
Create Connection
</button>
<button hx-post="/v1/api/connections/test" hx-trigger="click" hx-swap="outerHTML"
hx-target="#connection-status" hx-include="#connection-form" hx-encoding="multipart/form-data"
class="bg-gray-200 text-gray-700 px-4 py-2 rounded-md flex items-center space-x-2">
<span>Test Connection</span>
<span id="connection-status" class="w-3 h-3 rounded-full bg-gray-400"></span>
</button>
<span id="connection-message"></span>
</div> </div>
<div>
<label for="db-driver" class="block text-sm font-medium text-gray-700">Driver/Type of Database</label>
<select id="db-driver" name="db-driver"
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">
<option value="postgresql">PostgreSQL</option>
<option value="mysql">MySQL</option>
<option value="sqlite">SQLite</option>
<option value="sqlserver">SQL Server</option>
<option value="oracle">Oracle</option>
<option value="mariadb">MariaDB</option>
<option value="db2">DB2</option>
</select>
</div>
<div>
<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" value="azpect"
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 class="col-span-2">
<label for="db-url" class="block text-sm font-medium text-gray-700">
Connection URL
<br>
<span class="text-xs font-light">
The connection URL will be automatically generated based on the above fields. To view the URL
generated,
push the "display secret details" button in the password section.
</span>
</label>
<input id="db-url" name="db-url" type="password"
placeholder="postgresql://admin:password@127.0.0.1:5432/master_database"
value="postgresql://azpect:Panther4487!!!!@127.0.0.1:5432/azpect?sslmode=disable"
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">
<span id="db-url-invalid" class="text-xs text-red-500 hidden">Connection URL is incomplete or
invalid.</span>
</div>
</form>
<div class="flex items-center space-x-4 mt-4">
<button hx-post="/v1/api/connections" hx-trigger="click" hx-target="#connected-database" hx-swap="outerHTML"
hx-include="#connection-form" onclick="HideModal();" class="bg-blue-500 text-white px-4 py-2 rounded-md">
Create Connection
</button>
<button hx-post="/v1/api/connections/test" hx-trigger="click" hx-swap="outerHTML"
hx-target="#connection-status" hx-include="#connection-form" hx-encoding="multipart/form-data"
class="bg-gray-200 text-gray-700 px-4 py-2 rounded-md flex items-center space-x-2">
<span>Test Connection</span>
<span id="connection-status" class="w-3 h-3 rounded-full bg-gray-400"></span>
</button>
<span id="connection-message"></span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Scripts --> <!-- Scripts -->
<script src="/v1/web/static/scripts/password.js"></script> <script src="/v1/web/static/scripts/password.js"></script>
<script src="/v1/web/static/scripts/modal.js"></script> <script src="/v1/web/static/scripts/modal.js"></script>
</body> </body>
</html> </html>