feat(dev_tools): enhance SQL Query Tool — clear, copy, history, edit, hardening
All checks were successful
All checks were successful
UI: add Clear and Copy-to-clipboard (TSV) buttons, an in-page Recent Queries pane (localStorage, capped at 20, de-duped) and a pencil-edit flow for saved queries with a dedicated SQL field in the modal. Bind Ctrl/Cmd+S to open the save modal (or edit the active saved query). Backend: harden validate_query with a multi-statement guard that respects string literals + comments. Stop swallowing record_query_run errors silently — log via logger.exception so failures show up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,11 +79,47 @@
|
||||
Run <span x-text="q.run_count"></span> time<span x-show="q.run_count !== 1">s</span>
|
||||
</div>
|
||||
</div>
|
||||
<button @click.stop="deleteSavedQuery(q.id)"
|
||||
class="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-500 transition-opacity"
|
||||
title="Delete">
|
||||
<span x-html="$icon('trash', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button @click.stop="openEditModal(q)"
|
||||
class="p-1 text-gray-400 hover:text-indigo-500"
|
||||
title="Edit">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button @click.stop="deleteSavedQuery(q.id)"
|
||||
class="p-1 text-gray-400 hover:text-red-500"
|
||||
title="Delete">
|
||||
<span x-html="$icon('trash', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Recent Queries (history) -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider flex items-center gap-1.5">
|
||||
<span x-html="$icon('clock', 'w-4 h-4')"></span>
|
||||
Recent
|
||||
</h3>
|
||||
<button @click="clearHistory()"
|
||||
x-show="history.length > 0"
|
||||
class="text-xs text-gray-400 hover:text-red-500"
|
||||
title="Clear history">Clear</button>
|
||||
</div>
|
||||
<div x-show="history.length === 0" class="text-sm text-gray-400">No recent queries.</div>
|
||||
<ul class="space-y-1">
|
||||
<template x-for="(h, idx) in history" :key="h.ts">
|
||||
<li @click="loadHistory(h)"
|
||||
class="rounded-md px-2 py-1.5 text-xs cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-gray-600 dark:text-gray-400"
|
||||
:title="h.sql">
|
||||
<div class="truncate font-mono" x-text="h.preview"></div>
|
||||
<div class="text-[10px] text-gray-400 mt-0.5">
|
||||
<span x-text="formatHistoryTime(h.ts)"></span>
|
||||
<span x-show="h.row_count !== undefined"> · <span x-text="h.row_count"></span> row<span x-show="h.row_count !== 1">s</span></span>
|
||||
<span x-show="h.elapsed !== undefined"> · <span x-text="h.elapsed"></span>ms</span>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
@@ -128,6 +164,22 @@
|
||||
<span x-html="$icon('download', 'w-4 h-4 mr-1.5')"></span>
|
||||
Export CSV
|
||||
</button>
|
||||
|
||||
<button @click="copyResults()"
|
||||
x-show="rows.length > 0"
|
||||
class="inline-flex items-center px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 text-sm font-medium rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
||||
title="Copy results as TSV (paste into spreadsheet)">
|
||||
<span x-html="$icon('clipboard', 'w-4 h-4 mr-1.5')"></span>
|
||||
Copy Results
|
||||
</button>
|
||||
|
||||
<button @click="clearQuery()"
|
||||
:disabled="!sql && columns.length === 0 && !error"
|
||||
class="ml-auto inline-flex items-center px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 text-sm font-medium rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
title="Clear editor and results">
|
||||
<span x-html="$icon('x-circle', 'w-4 h-4 mr-1.5')"></span>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,9 +234,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Query Modal -->
|
||||
<!-- Save / Edit Query Modal -->
|
||||
{% call modal('saveQueryModal', 'Save Query', show_var='showSaveModal', size='sm', show_footer=false) %}
|
||||
<div class="space-y-4">
|
||||
<div class="text-xs uppercase tracking-wider text-gray-400" x-text="editingSavedId ? 'Edit saved query' : 'New saved query'"></div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
|
||||
<input type="text" x-model="saveName"
|
||||
@@ -198,6 +251,13 @@
|
||||
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 text-sm focus:ring-indigo-500 focus:border-indigo-500"
|
||||
placeholder="Brief description of what this query does">
|
||||
</div>
|
||||
<div x-show="editingSavedId">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">SQL</label>
|
||||
<textarea x-model="saveSql"
|
||||
rows="6"
|
||||
spellcheck="false"
|
||||
class="w-full bg-gray-900 text-green-400 font-mono text-xs rounded-lg p-3 border border-gray-700 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 resize-y"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<button @click="showSaveModal = false"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
|
||||
@@ -206,7 +266,7 @@
|
||||
<button @click="saveQuery()"
|
||||
:disabled="!saveName.trim() || saving"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 disabled:opacity-50 transition-colors">
|
||||
<span x-text="saving ? 'Saving...' : 'Save'"></span>
|
||||
<span x-text="saving ? 'Saving...' : (editingSavedId ? 'Save changes' : 'Save')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user