1- import { useState , useRef } from 'react'
1+ import { useState } from 'react'
22
33interface QueryResult {
44 columns : string [ ]
55 rows : ( string | number | boolean | null ) [ ] [ ]
66 row_count : number
77}
88
9- interface ExecuteResult {
10- message : string
11- affected_rows : number
12- }
13-
149interface ErrorResult {
1510 error : string
1611}
1712
1813export default function AdminDatabaseIndex ( { tables } : { tables : string [ ] } ) {
1914 const [ sql , setSql ] = useState ( '' )
2015 const [ result , setResult ] = useState < QueryResult | null > ( null )
21- const [ executeMsg , setExecuteMsg ] = useState < string | null > ( null )
2216 const [ error , setError ] = useState < string | null > ( null )
2317 const [ loading , setLoading ] = useState ( false )
2418 const [ selectedTable , setSelectedTable ] = useState < string | null > ( null )
25- const textareaRef = useRef < HTMLTextAreaElement > ( null )
2619
2720 const csrfToken = document . querySelector < HTMLMetaElement > ( 'meta[name="csrf-token"]' ) ?. content || ''
2821
@@ -32,7 +25,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
3225
3326 setLoading ( true )
3427 setError ( null )
35- setExecuteMsg ( null )
3628 setResult ( null )
3729
3830 try {
@@ -54,57 +46,20 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
5446 }
5547 }
5648
57- async function runExecute ( ) {
58- if ( ! sql . trim ( ) ) return
59- if ( ! confirm ( 'This will execute a write operation. Continue?' ) ) return
60-
61- setLoading ( true )
62- setError ( null )
63- setExecuteMsg ( null )
64- setResult ( null )
65-
66- try {
67- const res = await fetch ( '/admin/database/execute' , {
68- method : 'POST' ,
69- headers : { 'Content-Type' : 'application/json' , 'X-CSRF-Token' : csrfToken } ,
70- body : JSON . stringify ( { sql } ) ,
71- } )
72- const data : ExecuteResult | ErrorResult = await res . json ( )
73- if ( 'error' in data ) {
74- setError ( data . error )
75- } else {
76- setExecuteMsg ( `${ data . message } (${ data . affected_rows } rows affected)` )
77- }
78- } catch ( e ) {
79- setError ( 'Request failed' )
80- } finally {
81- setLoading ( false )
82- }
83- }
84-
8549 function selectTable ( table : string ) {
8650 setSelectedTable ( table )
8751 const query = `SELECT * FROM ${ table } LIMIT 50`
8852 setSql ( query )
8953 runQuery ( query )
9054 }
9155
92- function deleteRow ( table : string , primaryKey : string , id : string | number | boolean | null ) {
93- if ( ! confirm ( `Delete row with ${ primaryKey } = ${ id } from ${ table } ?` ) ) return
94- const query = `DELETE FROM ${ table } WHERE ${ primaryKey } = ${ typeof id === 'string' ? `'${ id . replace ( / ' / g, "''" ) } '` : id } `
95- setSql ( query )
96- runExecute ( )
97- }
98-
9956 function handleKeyDown ( e : React . KeyboardEvent ) {
10057 if ( ( e . metaKey || e . ctrlKey ) && e . key === 'Enter' ) {
10158 e . preventDefault ( )
10259 runQuery ( )
10360 }
10461 }
10562
106- const idColumnIndex = result ?. columns . findIndex ( c => c === 'id' ) ?? - 1
107-
10863 return (
10964 < div className = "p-12 max-w-[1600px] mx-auto" >
11065 < div className = "flex items-center justify-between mb-8" >
@@ -134,7 +89,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
13489 < div className = "flex-1 min-w-0 space-y-4" >
13590 < div className = "ghost-border bg-[#1c1b1b] p-4" >
13691 < textarea
137- ref = { textareaRef }
13892 value = { sql }
13993 onChange = { ( e ) => setSql ( e . target . value ) }
14094 onKeyDown = { handleKeyDown }
@@ -150,13 +104,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
150104 >
151105 { loading ? 'Running...' : 'Run Query' }
152106 </ button >
153- < button
154- onClick = { runExecute }
155- disabled = { loading }
156- className = "ghost-border bg-[#1c1b1b] text-stone-400 hover:bg-[#2a2a2a] px-6 py-3 font-bold uppercase tracking-wider text-xs cursor-pointer disabled:opacity-50"
157- >
158- Execute (Write)
159- </ button >
160107 < span className = "text-stone-600 text-xs self-center ml-2" > ⌘+Enter to run</ span >
161108 </ div >
162109 </ div >
@@ -167,12 +114,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
167114 </ div >
168115 ) }
169116
170- { executeMsg && (
171- < div className = "border border-emerald-500/20 bg-emerald-500/5 px-5 py-4 text-emerald-400 text-sm" >
172- { executeMsg }
173- </ div >
174- ) }
175-
176117 { result && (
177118 < div >
178119 < div className = "flex items-center justify-between mb-2" >
@@ -187,9 +128,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
187128 { col }
188129 </ th >
189130 ) ) }
190- { selectedTable && idColumnIndex >= 0 && (
191- < th className = "text-right px-4 py-3 text-[10px] uppercase tracking-[0.2em] font-bold text-stone-600" > Actions</ th >
192- ) }
193131 </ tr >
194132 </ thead >
195133 < tbody >
@@ -200,16 +138,6 @@ export default function AdminDatabaseIndex({ tables }: { tables: string[] }) {
200138 { cell === null ? < span className = "text-stone-600 italic" > NULL</ span > : String ( cell ) }
201139 </ td >
202140 ) ) }
203- { selectedTable && idColumnIndex >= 0 && (
204- < td className = "px-4 py-3 text-right" >
205- < button
206- onClick = { ( ) => deleteRow ( selectedTable , 'id' , row [ idColumnIndex ] ) }
207- className = "text-red-400/50 hover:text-red-400 transition-colors cursor-pointer"
208- >
209- < span className = "material-symbols-outlined text-lg" > delete</ span >
210- </ button >
211- </ td >
212- ) }
213141 </ tr >
214142 ) ) }
215143 </ tbody >
0 commit comments