@liorandb/studio 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +19 -0
- package/package.json +10 -0
- package/template/README.md +36 -0
- package/template/app/dashboard/page.tsx +240 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/globals.css +73 -0
- package/template/app/layout.tsx +37 -0
- package/template/app/login/page.tsx +233 -0
- package/template/app/page.tsx +32 -0
- package/template/eslint.config.mjs +18 -0
- package/template/next.config.ts +7 -0
- package/template/package-lock.json +6765 -0
- package/template/package.json +31 -0
- package/template/postcss.config.mjs +7 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/next.svg +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/dashboard/page.tsx +240 -0
- package/template/src/app/login/page.tsx +233 -0
- package/template/src/components/DocumentViewer.tsx +313 -0
- package/template/src/components/JsonViewer.tsx +93 -0
- package/template/src/components/Modal.tsx +192 -0
- package/template/src/components/Navbar.tsx +76 -0
- package/template/src/components/QueryEditor.tsx +189 -0
- package/template/src/components/Sidebar.tsx +196 -0
- package/template/src/components/Toast.tsx +91 -0
- package/template/src/lib/lioran.ts +252 -0
- package/template/src/lib/utils.ts +66 -0
- package/template/src/store/auth.ts +66 -0
- package/template/src/store/index.ts +125 -0
- package/template/src/types/index.ts +63 -0
- package/template/tsconfig.json +34 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "liorandb-studio",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@liorandb/driver": "^1.0.4",
|
|
13
|
+
"@monaco-editor/react": "^4.7.0",
|
|
14
|
+
"clsx": "^2.1.1",
|
|
15
|
+
"lucide-react": "^0.563.0",
|
|
16
|
+
"next": "16.1.6",
|
|
17
|
+
"react": "19.2.3",
|
|
18
|
+
"react-dom": "19.2.3",
|
|
19
|
+
"zustand": "^5.0.11"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@tailwindcss/postcss": "^4",
|
|
23
|
+
"@types/node": "^20",
|
|
24
|
+
"@types/react": "^19",
|
|
25
|
+
"@types/react-dom": "^19",
|
|
26
|
+
"eslint": "^9",
|
|
27
|
+
"eslint-config-next": "16.1.6",
|
|
28
|
+
"tailwindcss": "^4",
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useAppStore } from '@/store';
|
|
6
|
+
import { LioranDBService } from '@/lib/lioran';
|
|
7
|
+
import { Sidebar } from '@/components/Sidebar';
|
|
8
|
+
import { Navbar } from '@/components/Navbar';
|
|
9
|
+
import { DocumentViewer } from '@/components/DocumentViewer';
|
|
10
|
+
import { QueryEditor } from '@/components/QueryEditor';
|
|
11
|
+
import { InputModal, JsonInputModal } from '@/components/Modal';
|
|
12
|
+
import { useToast } from '@/components/Toast';
|
|
13
|
+
import { Document } from '@/types';
|
|
14
|
+
|
|
15
|
+
export default function DashboardPage() {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const { addToast } = useToast();
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
isLoggedIn,
|
|
21
|
+
currentDatabase,
|
|
22
|
+
selectedCollection,
|
|
23
|
+
logout,
|
|
24
|
+
setCurrentDatabase,
|
|
25
|
+
setSelectedCollection,
|
|
26
|
+
setDatabases,
|
|
27
|
+
setCollections,
|
|
28
|
+
} = useAppStore();
|
|
29
|
+
|
|
30
|
+
// Modal states
|
|
31
|
+
const [createDbModal, setCreateDbModal] = useState(false);
|
|
32
|
+
const [createColModal, setCreateColModal] = useState(false);
|
|
33
|
+
const [addDocModal, setAddDocModal] = useState(false);
|
|
34
|
+
const [editDocModal, setEditDocModal] = useState(false);
|
|
35
|
+
const [editingDoc, setEditingDoc] = useState<Document | null>(null);
|
|
36
|
+
|
|
37
|
+
// Check authentication
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!isLoggedIn) {
|
|
40
|
+
router.push('/login');
|
|
41
|
+
}
|
|
42
|
+
}, [isLoggedIn, router]);
|
|
43
|
+
|
|
44
|
+
// Load databases on mount
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (isLoggedIn) {
|
|
47
|
+
loadDatabases();
|
|
48
|
+
}
|
|
49
|
+
}, [isLoggedIn]);
|
|
50
|
+
|
|
51
|
+
// Load collections when database changes
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (currentDatabase) {
|
|
54
|
+
loadCollections(currentDatabase);
|
|
55
|
+
}
|
|
56
|
+
}, [currentDatabase]);
|
|
57
|
+
|
|
58
|
+
async function loadDatabases() {
|
|
59
|
+
try {
|
|
60
|
+
const databases = await LioranDBService.listDatabases();
|
|
61
|
+
setDatabases(databases);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
addToast(`Error loading databases: ${error}`, 'error');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function loadCollections(dbName: string) {
|
|
68
|
+
try {
|
|
69
|
+
const collections = await LioranDBService.listCollections(dbName);
|
|
70
|
+
setCollections(dbName, collections);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
addToast(`Error loading collections: ${error}`, 'error');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function handleCreateDatabase(name: string) {
|
|
77
|
+
try {
|
|
78
|
+
await LioranDBService.createDatabase(name);
|
|
79
|
+
await loadDatabases();
|
|
80
|
+
setCurrentDatabase(name);
|
|
81
|
+
addToast(`Database "${name}" created`, 'success');
|
|
82
|
+
} catch (error) {
|
|
83
|
+
addToast(`Error creating database: ${error}`, 'error');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function handleCreateCollection(name: string) {
|
|
88
|
+
if (!currentDatabase) return;
|
|
89
|
+
try {
|
|
90
|
+
await LioranDBService.createCollection(currentDatabase, name);
|
|
91
|
+
await loadCollections(currentDatabase);
|
|
92
|
+
setSelectedCollection(name);
|
|
93
|
+
addToast(`Collection "${name}" created`, 'success');
|
|
94
|
+
} catch (error) {
|
|
95
|
+
addToast(`Error creating collection: ${error}`, 'error');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function handleAddDocument(doc: Record<string, any>) {
|
|
100
|
+
if (!currentDatabase || !selectedCollection) return;
|
|
101
|
+
try {
|
|
102
|
+
await LioranDBService.insertOne(currentDatabase, selectedCollection, doc);
|
|
103
|
+
addToast('Document added', 'success');
|
|
104
|
+
setAddDocModal(false);
|
|
105
|
+
// Reload documents
|
|
106
|
+
const docViewer = document.querySelector('[data-reload-documents]');
|
|
107
|
+
if (docViewer) {
|
|
108
|
+
const event = new CustomEvent('reload');
|
|
109
|
+
docViewer.dispatchEvent(event);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
addToast(`Error adding document: ${error}`, 'error');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function handleLogout() {
|
|
117
|
+
if (confirm('Are you sure you want to logout?')) {
|
|
118
|
+
logout();
|
|
119
|
+
LioranDBService.disconnect();
|
|
120
|
+
router.push('/login');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="h-screen flex flex-col bg-slate-950">
|
|
126
|
+
{/* Navbar */}
|
|
127
|
+
<Navbar onLogout={handleLogout} />
|
|
128
|
+
|
|
129
|
+
{/* Main Content */}
|
|
130
|
+
<div className="flex-1 flex overflow-hidden">
|
|
131
|
+
{/* Sidebar */}
|
|
132
|
+
<Sidebar
|
|
133
|
+
onDatabaseSelect={setCurrentDatabase}
|
|
134
|
+
onCollectionSelect={(db, col) => {
|
|
135
|
+
setCurrentDatabase(db);
|
|
136
|
+
setSelectedCollection(col);
|
|
137
|
+
}}
|
|
138
|
+
onCreateDatabase={() => setCreateDbModal(true)}
|
|
139
|
+
onCreateCollection={() => setCreateColModal(true)}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
{/* Workspace */}
|
|
143
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
144
|
+
{!currentDatabase ? (
|
|
145
|
+
// Empty State
|
|
146
|
+
<div className="flex-1 flex items-center justify-center text-slate-400">
|
|
147
|
+
<div className="text-center">
|
|
148
|
+
<div className="text-4xl mb-4">📦</div>
|
|
149
|
+
<p className="text-lg">No database selected</p>
|
|
150
|
+
<p className="text-sm text-slate-500">Create or select a database to get started</p>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
) : !selectedCollection ? (
|
|
154
|
+
// Collection Selection State
|
|
155
|
+
<div className="flex-1 flex items-center justify-center text-slate-400">
|
|
156
|
+
<div className="text-center">
|
|
157
|
+
<div className="text-4xl mb-4">📚</div>
|
|
158
|
+
<p className="text-lg">No collection selected</p>
|
|
159
|
+
<p className="text-sm text-slate-500">Select or create a collection to view documents</p>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
) : (
|
|
163
|
+
// Two Panel Layout
|
|
164
|
+
<div className="flex-1 flex overflow-hidden gap-4 p-4 bg-slate-900">
|
|
165
|
+
{/* Left: Document Viewer */}
|
|
166
|
+
<div className="flex-1 bg-slate-950 rounded-lg border border-slate-800 overflow-hidden flex flex-col">
|
|
167
|
+
<DocumentViewer
|
|
168
|
+
onAddDocument={() => setAddDocModal(true)}
|
|
169
|
+
onEditDocument={(doc) => {
|
|
170
|
+
setEditingDoc(doc);
|
|
171
|
+
setEditDocModal(true);
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{/* Right: Query Editor */}
|
|
177
|
+
<div className="w-96 bg-slate-950 rounded-lg border border-slate-800 overflow-hidden">
|
|
178
|
+
<QueryEditor />
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Modals */}
|
|
186
|
+
<InputModal
|
|
187
|
+
isOpen={createDbModal}
|
|
188
|
+
title="Create Database"
|
|
189
|
+
label="Database Name"
|
|
190
|
+
placeholder="mydb"
|
|
191
|
+
onClose={() => setCreateDbModal(false)}
|
|
192
|
+
onConfirm={handleCreateDatabase}
|
|
193
|
+
/>
|
|
194
|
+
|
|
195
|
+
<InputModal
|
|
196
|
+
isOpen={createColModal}
|
|
197
|
+
title="Create Collection"
|
|
198
|
+
label="Collection Name"
|
|
199
|
+
placeholder="users"
|
|
200
|
+
onClose={() => setCreateColModal(false)}
|
|
201
|
+
onConfirm={handleCreateCollection}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
<JsonInputModal
|
|
205
|
+
isOpen={addDocModal}
|
|
206
|
+
title="Add Document"
|
|
207
|
+
defaultValue='{}'
|
|
208
|
+
onClose={() => setAddDocModal(false)}
|
|
209
|
+
onConfirm={handleAddDocument}
|
|
210
|
+
/>
|
|
211
|
+
|
|
212
|
+
<JsonInputModal
|
|
213
|
+
isOpen={editDocModal}
|
|
214
|
+
title="Edit Document"
|
|
215
|
+
defaultValue={editingDoc ? JSON.stringify(editingDoc, null, 2) : '{}'}
|
|
216
|
+
onClose={() => {
|
|
217
|
+
setEditDocModal(false);
|
|
218
|
+
setEditingDoc(null);
|
|
219
|
+
}}
|
|
220
|
+
onConfirm={async (doc) => {
|
|
221
|
+
if (!currentDatabase || !selectedCollection || !editingDoc) return;
|
|
222
|
+
try {
|
|
223
|
+
const _id = editingDoc._id;
|
|
224
|
+
await LioranDBService.updateMany(
|
|
225
|
+
currentDatabase,
|
|
226
|
+
selectedCollection,
|
|
227
|
+
{ _id },
|
|
228
|
+
{ $set: doc }
|
|
229
|
+
);
|
|
230
|
+
addToast('Document updated', 'success');
|
|
231
|
+
setEditDocModal(false);
|
|
232
|
+
setEditingDoc(null);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
addToast(`Error updating document: ${error}`, 'error');
|
|
235
|
+
}
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useAppStore } from '@/store';
|
|
6
|
+
import { LioranDBService } from '@/lib/lioran';
|
|
7
|
+
import { parseConnectionUri } from '@/lib/utils';
|
|
8
|
+
import { useToast } from '@/components/Toast';
|
|
9
|
+
|
|
10
|
+
export default function LoginPage() {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const { addToast } = useToast();
|
|
13
|
+
const { setLoggedIn } = useAppStore();
|
|
14
|
+
|
|
15
|
+
const [uri, setUri] = useState('');
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
18
|
+
|
|
19
|
+
const [username, setUsername] = useState('admin');
|
|
20
|
+
const [password, setPassword] = useState('');
|
|
21
|
+
const [host, setHost] = useState('localhost');
|
|
22
|
+
const [port, setPort] = useState('4000');
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// Load saved URI from localStorage
|
|
26
|
+
const savedUri = localStorage.getItem('liorandb_uri');
|
|
27
|
+
const savedToken = localStorage.getItem('liorandb_token');
|
|
28
|
+
|
|
29
|
+
if (savedUri && savedToken) {
|
|
30
|
+
// Try to auto-login
|
|
31
|
+
attemptAutoLogin(savedUri, savedToken);
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
async function attemptAutoLogin(uri: string, token: string) {
|
|
36
|
+
try {
|
|
37
|
+
setIsLoading(true);
|
|
38
|
+
LioranDBService.initialize(uri);
|
|
39
|
+
|
|
40
|
+
// Test connection
|
|
41
|
+
const databases = await LioranDBService.listDatabases();
|
|
42
|
+
|
|
43
|
+
setLoggedIn(true, token, uri);
|
|
44
|
+
useAppStore.setState({ databases });
|
|
45
|
+
addToast('Connected successfully', 'success');
|
|
46
|
+
router.push('/dashboard');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
localStorage.removeItem('liorandb_token');
|
|
49
|
+
localStorage.removeItem('liorandb_uri');
|
|
50
|
+
addToast(`Connection failed: ${error}`, 'error');
|
|
51
|
+
} finally {
|
|
52
|
+
setIsLoading(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleLogin(e: React.FormEvent) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
|
|
59
|
+
const loginUri = showAdvanced
|
|
60
|
+
? `lioran://${username}:${password}@${host}:${port}`
|
|
61
|
+
: uri;
|
|
62
|
+
|
|
63
|
+
if (!loginUri) {
|
|
64
|
+
addToast('Please enter a connection URI', 'warning');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
setIsLoading(true);
|
|
70
|
+
|
|
71
|
+
// Validate URI format
|
|
72
|
+
try {
|
|
73
|
+
parseConnectionUri(loginUri);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
addToast(String(err), 'error');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Initialize client
|
|
80
|
+
await LioranDBService.initialize(loginUri);
|
|
81
|
+
|
|
82
|
+
// List databases to verify connection
|
|
83
|
+
const databases = await LioranDBService.listDatabases();
|
|
84
|
+
|
|
85
|
+
// Store session (in a real app, you'd get a token from the server)
|
|
86
|
+
const token = `token_${Date.now()}`;
|
|
87
|
+
|
|
88
|
+
setLoggedIn(true, token, loginUri);
|
|
89
|
+
useAppStore.setState({ databases });
|
|
90
|
+
|
|
91
|
+
addToast('Connected successfully', 'success');
|
|
92
|
+
router.push('/dashboard');
|
|
93
|
+
} catch (error) {
|
|
94
|
+
addToast(`Login failed: ${error}`, 'error');
|
|
95
|
+
} finally {
|
|
96
|
+
setIsLoading(false);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-800 flex items-center justify-center p-4">
|
|
102
|
+
{/* Background Effect */}
|
|
103
|
+
<div className="absolute inset-0 overflow-hidden">
|
|
104
|
+
<div className="absolute -top-40 -right-40 w-80 h-80 bg-emerald-500/5 rounded-full blur-3xl" />
|
|
105
|
+
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-cyan-500/5 rounded-full blur-3xl" />
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Login Card */}
|
|
109
|
+
<div className="relative w-full max-w-md">
|
|
110
|
+
<div className="bg-slate-900/80 backdrop-blur border border-slate-800 rounded-xl shadow-2xl overflow-hidden">
|
|
111
|
+
{/* Header */}
|
|
112
|
+
<div className="px-8 pt-8 pb-6 border-b border-slate-800">
|
|
113
|
+
<div className="flex items-center gap-3 mb-2">
|
|
114
|
+
<div className="w-10 h-10 bg-gradient-to-br from-emerald-400 to-cyan-400 rounded-lg flex items-center justify-center">
|
|
115
|
+
<span className="text-slate-900 font-bold text-lg">⚡</span>
|
|
116
|
+
</div>
|
|
117
|
+
<h1 className="text-2xl font-bold text-slate-100">LioranDB</h1>
|
|
118
|
+
</div>
|
|
119
|
+
<p className="text-slate-400 text-sm">Database Studio</p>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Form */}
|
|
123
|
+
<form onSubmit={handleLogin} className="px-8 py-8 space-y-6">
|
|
124
|
+
{!showAdvanced ? (
|
|
125
|
+
// Quick Connect
|
|
126
|
+
<div>
|
|
127
|
+
<label className="block text-sm font-medium text-slate-300 mb-2">
|
|
128
|
+
Connection URI
|
|
129
|
+
</label>
|
|
130
|
+
<input
|
|
131
|
+
type="text"
|
|
132
|
+
value={uri}
|
|
133
|
+
onChange={(e) => setUri(e.target.value)}
|
|
134
|
+
placeholder="lioran://admin:password@localhost:4000"
|
|
135
|
+
className="w-full bg-slate-800/50 border border-slate-700 rounded-lg px-4 py-3 text-slate-100 placeholder-slate-500 focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 transition"
|
|
136
|
+
disabled={isLoading}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
) : (
|
|
140
|
+
// Advanced Connect
|
|
141
|
+
<div className="space-y-4">
|
|
142
|
+
<div>
|
|
143
|
+
<label className="block text-sm font-medium text-slate-300 mb-2">
|
|
144
|
+
Username
|
|
145
|
+
</label>
|
|
146
|
+
<input
|
|
147
|
+
type="text"
|
|
148
|
+
value={username}
|
|
149
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
150
|
+
className="w-full bg-slate-800/50 border border-slate-700 rounded-lg px-4 py-3 text-slate-100 focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 transition"
|
|
151
|
+
disabled={isLoading}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div>
|
|
156
|
+
<label className="block text-sm font-medium text-slate-300 mb-2">
|
|
157
|
+
Password
|
|
158
|
+
</label>
|
|
159
|
+
<input
|
|
160
|
+
type="password"
|
|
161
|
+
value={password}
|
|
162
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
163
|
+
className="w-full bg-slate-800/50 border border-slate-700 rounded-lg px-4 py-3 text-slate-100 focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 transition"
|
|
164
|
+
disabled={isLoading}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div className="grid grid-cols-2 gap-4">
|
|
169
|
+
<div>
|
|
170
|
+
<label className="block text-sm font-medium text-slate-300 mb-2">
|
|
171
|
+
Host
|
|
172
|
+
</label>
|
|
173
|
+
<input
|
|
174
|
+
type="text"
|
|
175
|
+
value={host}
|
|
176
|
+
onChange={(e) => setHost(e.target.value)}
|
|
177
|
+
className="w-full bg-slate-800/50 border border-slate-700 rounded-lg px-4 py-3 text-slate-100 focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 transition"
|
|
178
|
+
disabled={isLoading}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div>
|
|
183
|
+
<label className="block text-sm font-medium text-slate-300 mb-2">
|
|
184
|
+
Port
|
|
185
|
+
</label>
|
|
186
|
+
<input
|
|
187
|
+
type="text"
|
|
188
|
+
value={port}
|
|
189
|
+
onChange={(e) => setPort(e.target.value)}
|
|
190
|
+
className="w-full bg-slate-800/50 border border-slate-700 rounded-lg px-4 py-3 text-slate-100 focus:outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-500/20 transition"
|
|
191
|
+
disabled={isLoading}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{/* Toggle Advanced */}
|
|
199
|
+
<button
|
|
200
|
+
type="button"
|
|
201
|
+
onClick={() => setShowAdvanced(!showAdvanced)}
|
|
202
|
+
className="text-sm text-emerald-400 hover:text-emerald-300 transition"
|
|
203
|
+
disabled={isLoading}
|
|
204
|
+
>
|
|
205
|
+
{showAdvanced ? '← Back to Quick Connect' : 'Advanced Options →'}
|
|
206
|
+
</button>
|
|
207
|
+
|
|
208
|
+
{/* Submit Button */}
|
|
209
|
+
<button
|
|
210
|
+
type="submit"
|
|
211
|
+
disabled={isLoading || (!showAdvanced && !uri) || (showAdvanced && !password)}
|
|
212
|
+
className="w-full bg-gradient-to-r from-emerald-600 to-emerald-500 hover:from-emerald-700 hover:to-emerald-600 text-white font-semibold py-3 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed"
|
|
213
|
+
>
|
|
214
|
+
{isLoading ? 'Connecting...' : 'Connect to LioranDB'}
|
|
215
|
+
</button>
|
|
216
|
+
</form>
|
|
217
|
+
|
|
218
|
+
{/* Footer */}
|
|
219
|
+
<div className="px-8 py-6 bg-slate-800/30 border-t border-slate-800">
|
|
220
|
+
<p className="text-xs text-slate-400 text-center">
|
|
221
|
+
Default: <code className="bg-slate-900 px-2 py-1 rounded">lioran://admin:password@localhost:4000</code>
|
|
222
|
+
</p>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Beta Badge */}
|
|
227
|
+
<div className="absolute -top-3 right-4 bg-amber-500 text-slate-900 px-3 py-1 rounded-full text-xs font-semibold">
|
|
228
|
+
BETA
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|