@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,252 @@
|
|
|
1
|
+
import { LioranClient } from '@liorandb/driver';
|
|
2
|
+
import { Database, Collection, Document } from '@/types';
|
|
3
|
+
|
|
4
|
+
let clientInstance: LioranClient | null = null;
|
|
5
|
+
|
|
6
|
+
export class LioranDBService {
|
|
7
|
+
private static client: LioranClient | null = null;
|
|
8
|
+
|
|
9
|
+
static async initialize(uri: string): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
this.client = new LioranClient(uri);
|
|
12
|
+
await this.client.connect();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
throw new Error(`Failed to connect to LioranDB: ${error}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static isConnected(): boolean {
|
|
19
|
+
return this.client !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static getClient(): LioranClient | null {
|
|
23
|
+
return this.client;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static disconnect(): void {
|
|
27
|
+
this.client = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Database Operations
|
|
31
|
+
static async listDatabases(): Promise<Database[]> {
|
|
32
|
+
if (!this.client) throw new Error('Client not connected');
|
|
33
|
+
try {
|
|
34
|
+
const dbs = await this.client.listDatabases();
|
|
35
|
+
return ((dbs as unknown) as string[]).map(name => ({ name }));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`Failed to list databases: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static async createDatabase(name: string): Promise<void> {
|
|
42
|
+
if (!this.client) throw new Error('Client not connected');
|
|
43
|
+
try {
|
|
44
|
+
await this.client.createDatabase(name);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`Failed to create database: ${error}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static async dropDatabase(name: string): Promise<void> {
|
|
51
|
+
if (!this.client) throw new Error('Client not connected');
|
|
52
|
+
try {
|
|
53
|
+
await this.client.dropDatabase(name);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`Failed to drop database: ${error}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async renameDatabase(oldName: string, newName: string): Promise<void> {
|
|
60
|
+
if (!this.client) throw new Error('Client not connected');
|
|
61
|
+
try {
|
|
62
|
+
await this.client.renameDatabase(oldName, newName);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
throw new Error(`Failed to rename database: ${error}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Collection Operations
|
|
69
|
+
static async listCollections(dbName: string): Promise<Collection[]> {
|
|
70
|
+
if (!this.client) throw new Error('Client not connected');
|
|
71
|
+
try {
|
|
72
|
+
const db = this.client.db(dbName);
|
|
73
|
+
const collections = await db.listCollections();
|
|
74
|
+
return ((collections as unknown) as string[]).map(name => ({ name }));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Failed to list collections: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static async createCollection(dbName: string, name: string): Promise<void> {
|
|
81
|
+
if (!this.client) throw new Error('Client not connected');
|
|
82
|
+
try {
|
|
83
|
+
const db = this.client.db(dbName);
|
|
84
|
+
await db.createCollection(name);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Failed to create collection: ${error}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static async dropCollection(dbName: string, name: string): Promise<void> {
|
|
91
|
+
if (!this.client) throw new Error('Client not connected');
|
|
92
|
+
try {
|
|
93
|
+
const db = this.client.db(dbName);
|
|
94
|
+
await db.dropCollection(name);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(`Failed to drop collection: ${error}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static async renameCollection(
|
|
101
|
+
dbName: string,
|
|
102
|
+
oldName: string,
|
|
103
|
+
newName: string
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
if (!this.client) throw new Error('Client not connected');
|
|
106
|
+
try {
|
|
107
|
+
const db = this.client.db(dbName);
|
|
108
|
+
await db.renameCollection(oldName, newName);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Failed to rename collection: ${error}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Document Operations
|
|
115
|
+
static async find(
|
|
116
|
+
dbName: string,
|
|
117
|
+
collectionName: string,
|
|
118
|
+
filter?: Record<string, any>,
|
|
119
|
+
limit: number = 100
|
|
120
|
+
): Promise<{ documents: Document[]; count: number }> {
|
|
121
|
+
if (!this.client) throw new Error('Client not connected');
|
|
122
|
+
try {
|
|
123
|
+
const db = this.client.db(dbName);
|
|
124
|
+
const collection = db.collection(collectionName);
|
|
125
|
+
const documents = await collection.find(filter || {});
|
|
126
|
+
return {
|
|
127
|
+
documents: ((documents as unknown) as Document[]).slice(0, limit),
|
|
128
|
+
count: documents.length,
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
throw new Error(`Failed to find documents: ${error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static async findOne(
|
|
136
|
+
dbName: string,
|
|
137
|
+
collectionName: string,
|
|
138
|
+
filter: Record<string, any>
|
|
139
|
+
): Promise<Document | null> {
|
|
140
|
+
if (!this.client) throw new Error('Client not connected');
|
|
141
|
+
try {
|
|
142
|
+
const db = this.client.db(dbName);
|
|
143
|
+
const collection = db.collection(collectionName);
|
|
144
|
+
const doc = await collection.findOne(filter);
|
|
145
|
+
return ((doc as unknown) as Document) || null;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw new Error(`Failed to find document: ${error}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static async insertOne(
|
|
152
|
+
dbName: string,
|
|
153
|
+
collectionName: string,
|
|
154
|
+
doc: Document
|
|
155
|
+
): Promise<string> {
|
|
156
|
+
if (!this.client) throw new Error('Client not connected');
|
|
157
|
+
try {
|
|
158
|
+
const db = this.client.db(dbName);
|
|
159
|
+
const collection = db.collection(collectionName);
|
|
160
|
+
const { _id, ...docData } = doc;
|
|
161
|
+
const docToInsert = { ...docData, ...(typeof _id === 'string' || _id === undefined ? { _id } : {}) };
|
|
162
|
+
const result = await collection.insertOne(docToInsert as any);
|
|
163
|
+
return result as unknown as string;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error(`Failed to insert document: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static async insertMany(
|
|
170
|
+
dbName: string,
|
|
171
|
+
collectionName: string,
|
|
172
|
+
docs: Document[]
|
|
173
|
+
): Promise<string[]> {
|
|
174
|
+
if (!this.client) throw new Error('Client not connected');
|
|
175
|
+
try {
|
|
176
|
+
const db = this.client.db(dbName);
|
|
177
|
+
const collection = db.collection(collectionName);
|
|
178
|
+
const cleanedDocs = docs.map(({ _id, ...docData }) => ({
|
|
179
|
+
...docData,
|
|
180
|
+
...(typeof _id === 'string' || _id === undefined ? { _id } : {})
|
|
181
|
+
}));
|
|
182
|
+
const result = await collection.insertMany(cleanedDocs as any);
|
|
183
|
+
return result as unknown as string[];
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw new Error(`Failed to insert documents: ${error}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static async updateMany(
|
|
190
|
+
dbName: string,
|
|
191
|
+
collectionName: string,
|
|
192
|
+
filter: Record<string, any>,
|
|
193
|
+
update: Record<string, any>
|
|
194
|
+
): Promise<number> {
|
|
195
|
+
if (!this.client) throw new Error('Client not connected');
|
|
196
|
+
try {
|
|
197
|
+
const db = this.client.db(dbName);
|
|
198
|
+
const collection = db.collection(collectionName);
|
|
199
|
+
const result = await collection.updateMany(filter, update);
|
|
200
|
+
return result as unknown as number;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new Error(`Failed to update documents: ${error}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static async deleteMany(
|
|
207
|
+
dbName: string,
|
|
208
|
+
collectionName: string,
|
|
209
|
+
filter: Record<string, any>
|
|
210
|
+
): Promise<number> {
|
|
211
|
+
if (!this.client) throw new Error('Client not connected');
|
|
212
|
+
try {
|
|
213
|
+
const db = this.client.db(dbName);
|
|
214
|
+
const collection = db.collection(collectionName);
|
|
215
|
+
const result = await collection.deleteMany(filter);
|
|
216
|
+
return result as unknown as number;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw new Error(`Failed to delete documents: ${error}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static async count(
|
|
223
|
+
dbName: string,
|
|
224
|
+
collectionName: string,
|
|
225
|
+
filter?: Record<string, any>
|
|
226
|
+
): Promise<number> {
|
|
227
|
+
if (!this.client) throw new Error('Client not connected');
|
|
228
|
+
try {
|
|
229
|
+
const db = this.client.db(dbName);
|
|
230
|
+
const collection = db.collection(collectionName);
|
|
231
|
+
const count = await collection.count(filter || {});
|
|
232
|
+
return count as unknown as number;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw new Error(`Failed to count documents: ${error}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static async stats(
|
|
239
|
+
dbName: string,
|
|
240
|
+
collectionName: string
|
|
241
|
+
): Promise<Record<string, any>> {
|
|
242
|
+
if (!this.client) throw new Error('Client not connected');
|
|
243
|
+
try {
|
|
244
|
+
const db = this.client.db(dbName);
|
|
245
|
+
const collection = db.collection(collectionName);
|
|
246
|
+
const stats = await collection.stats();
|
|
247
|
+
return stats as Record<string, any>;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new Error(`Failed to get collection stats: ${error}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export function parseConnectionUri(uri: string): {
|
|
2
|
+
username: string;
|
|
3
|
+
password: string;
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
} {
|
|
7
|
+
const regex = /^lioran:\/\/([^:]+):([^@]+)@([^:]+):(\d+)$/;
|
|
8
|
+
const match = uri.match(regex);
|
|
9
|
+
|
|
10
|
+
if (!match) {
|
|
11
|
+
throw new Error('Invalid connection URI format. Expected: lioran://username:password@host:port');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
username: match[1],
|
|
16
|
+
password: match[2],
|
|
17
|
+
host: match[3],
|
|
18
|
+
port: parseInt(match[4], 10),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatConnectionUri(
|
|
23
|
+
username: string,
|
|
24
|
+
password: string,
|
|
25
|
+
host: string,
|
|
26
|
+
port: number
|
|
27
|
+
): string {
|
|
28
|
+
return `lioran://${username}:${password}@${host}:${port}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatDate(date: Date | string): string {
|
|
32
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
33
|
+
return d.toLocaleString();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function formatBytes(bytes: number): string {
|
|
37
|
+
if (bytes === 0) return '0 Bytes';
|
|
38
|
+
const k = 1024;
|
|
39
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
40
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
41
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatJSON(obj: any, indent: number = 2): string {
|
|
45
|
+
return JSON.stringify(obj, null, indent);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function copyToClipboard(text: string): Promise<void> {
|
|
49
|
+
if (navigator?.clipboard) {
|
|
50
|
+
return navigator.clipboard.writeText(text);
|
|
51
|
+
} else {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const textarea = document.createElement('textarea');
|
|
54
|
+
textarea.value = text;
|
|
55
|
+
document.body.appendChild(textarea);
|
|
56
|
+
textarea.select();
|
|
57
|
+
try {
|
|
58
|
+
document.execCommand('copy');
|
|
59
|
+
resolve();
|
|
60
|
+
} catch (err) {
|
|
61
|
+
reject(err);
|
|
62
|
+
}
|
|
63
|
+
document.body.removeChild(textarea);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { AuthState } from '@/types';
|
|
3
|
+
|
|
4
|
+
interface AuthStore extends AuthState {
|
|
5
|
+
setLoggedIn: (loggedIn: boolean, token?: string, uri?: string) => void;
|
|
6
|
+
setToken: (token: string | null) => void;
|
|
7
|
+
setConnectionUri: (uri: string | null) => void;
|
|
8
|
+
setError: (error: string | null) => void;
|
|
9
|
+
setLoading: (loading: boolean) => void;
|
|
10
|
+
logout: () => void;
|
|
11
|
+
loadFromStorage: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useAuthStore = create<AuthStore>((set) => ({
|
|
15
|
+
isLoggedIn: false,
|
|
16
|
+
token: null,
|
|
17
|
+
connectionUri: null,
|
|
18
|
+
error: null,
|
|
19
|
+
isLoading: false,
|
|
20
|
+
|
|
21
|
+
setLoggedIn: (loggedIn, token, uri) => {
|
|
22
|
+
set({ isLoggedIn: loggedIn, token, connectionUri: uri });
|
|
23
|
+
if (loggedIn && token && uri) {
|
|
24
|
+
if (typeof window !== 'undefined') {
|
|
25
|
+
localStorage.setItem('liorandb_token', token);
|
|
26
|
+
localStorage.setItem('liorandb_uri', uri);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
setToken: (token) => set({ token }),
|
|
32
|
+
|
|
33
|
+
setConnectionUri: (uri) => set({ connectionUri: uri }),
|
|
34
|
+
|
|
35
|
+
setError: (error) => set({ error }),
|
|
36
|
+
|
|
37
|
+
setLoading: (loading) => set({ isLoading: loading }),
|
|
38
|
+
|
|
39
|
+
logout: () => {
|
|
40
|
+
set({
|
|
41
|
+
isLoggedIn: false,
|
|
42
|
+
token: null,
|
|
43
|
+
connectionUri: null,
|
|
44
|
+
error: null,
|
|
45
|
+
});
|
|
46
|
+
if (typeof window !== 'undefined') {
|
|
47
|
+
localStorage.removeItem('liorandb_token');
|
|
48
|
+
localStorage.removeItem('liorandb_uri');
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
loadFromStorage: () => {
|
|
53
|
+
if (typeof window === 'undefined') return;
|
|
54
|
+
|
|
55
|
+
const token = localStorage.getItem('liorandb_token');
|
|
56
|
+
const uri = localStorage.getItem('liorandb_uri');
|
|
57
|
+
|
|
58
|
+
if (token && uri) {
|
|
59
|
+
set({
|
|
60
|
+
isLoggedIn: true,
|
|
61
|
+
token,
|
|
62
|
+
connectionUri: uri,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { Database, Collection, Document, QueryResult, StoreState } from '@/types';
|
|
3
|
+
|
|
4
|
+
interface AppStore extends StoreState {
|
|
5
|
+
// Auth actions
|
|
6
|
+
setLoggedIn: (loggedIn: boolean, token?: string, uri?: string) => void;
|
|
7
|
+
setToken: (token: string | null) => void;
|
|
8
|
+
setConnectionUri: (uri: string | null) => void;
|
|
9
|
+
logout: () => void;
|
|
10
|
+
|
|
11
|
+
// Navigation actions
|
|
12
|
+
setCurrentDatabase: (db: string | null) => void;
|
|
13
|
+
setSelectedCollection: (col: string | null) => void;
|
|
14
|
+
|
|
15
|
+
// Data actions
|
|
16
|
+
setDatabases: (databases: Database[]) => void;
|
|
17
|
+
setCollections: (dbName: string, collections: Collection[]) => void;
|
|
18
|
+
setDocuments: (documents: Document[]) => void;
|
|
19
|
+
setQueryResults: (results: QueryResult | null) => void;
|
|
20
|
+
|
|
21
|
+
// UI actions
|
|
22
|
+
setLoading: (loading: boolean) => void;
|
|
23
|
+
setError: (error: string | null) => void;
|
|
24
|
+
setSuccessMessage: (message: string | null) => void;
|
|
25
|
+
|
|
26
|
+
// Storage
|
|
27
|
+
loadFromStorage: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const useAppStore = create<AppStore>((set) => ({
|
|
31
|
+
// Auth
|
|
32
|
+
isLoggedIn: false,
|
|
33
|
+
token: null,
|
|
34
|
+
connectionUri: null,
|
|
35
|
+
|
|
36
|
+
// Navigation
|
|
37
|
+
currentDatabase: null,
|
|
38
|
+
selectedCollection: null,
|
|
39
|
+
|
|
40
|
+
// Data
|
|
41
|
+
databases: [],
|
|
42
|
+
collections: {},
|
|
43
|
+
documents: [],
|
|
44
|
+
queryResults: null,
|
|
45
|
+
|
|
46
|
+
// UI State
|
|
47
|
+
isLoading: false,
|
|
48
|
+
error: null,
|
|
49
|
+
successMessage: null,
|
|
50
|
+
|
|
51
|
+
// Auth actions
|
|
52
|
+
setLoggedIn: (loggedIn, token, uri) => {
|
|
53
|
+
set({ isLoggedIn: loggedIn, token, connectionUri: uri });
|
|
54
|
+
if (loggedIn && token && uri && typeof window !== 'undefined') {
|
|
55
|
+
localStorage.setItem('liorandb_token', token);
|
|
56
|
+
localStorage.setItem('liorandb_uri', uri);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
setToken: (token) => set({ token }),
|
|
61
|
+
|
|
62
|
+
setConnectionUri: (uri) => set({ connectionUri: uri }),
|
|
63
|
+
|
|
64
|
+
logout: () => {
|
|
65
|
+
set({
|
|
66
|
+
isLoggedIn: false,
|
|
67
|
+
token: null,
|
|
68
|
+
connectionUri: null,
|
|
69
|
+
databases: [],
|
|
70
|
+
collections: {},
|
|
71
|
+
documents: [],
|
|
72
|
+
queryResults: null,
|
|
73
|
+
currentDatabase: null,
|
|
74
|
+
selectedCollection: null,
|
|
75
|
+
});
|
|
76
|
+
if (typeof window !== 'undefined') {
|
|
77
|
+
localStorage.removeItem('liorandb_token');
|
|
78
|
+
localStorage.removeItem('liorandb_uri');
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Navigation actions
|
|
83
|
+
setCurrentDatabase: (db) => set({ currentDatabase: db, selectedCollection: null }),
|
|
84
|
+
|
|
85
|
+
setSelectedCollection: (col) => set({ selectedCollection: col }),
|
|
86
|
+
|
|
87
|
+
// Data actions
|
|
88
|
+
setDatabases: (databases) => set({ databases }),
|
|
89
|
+
|
|
90
|
+
setCollections: (dbName, collections) => {
|
|
91
|
+
set((state) => ({
|
|
92
|
+
collections: {
|
|
93
|
+
...state.collections,
|
|
94
|
+
[dbName]: collections,
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
setDocuments: (documents) => set({ documents }),
|
|
100
|
+
|
|
101
|
+
setQueryResults: (results) => set({ queryResults: results }),
|
|
102
|
+
|
|
103
|
+
// UI actions
|
|
104
|
+
setLoading: (loading) => set({ isLoading: loading }),
|
|
105
|
+
|
|
106
|
+
setError: (error) => set({ error }),
|
|
107
|
+
|
|
108
|
+
setSuccessMessage: (message) => set({ successMessage: message }),
|
|
109
|
+
|
|
110
|
+
// Storage
|
|
111
|
+
loadFromStorage: () => {
|
|
112
|
+
if (typeof window === 'undefined') return;
|
|
113
|
+
|
|
114
|
+
const token = localStorage.getItem('liorandb_token');
|
|
115
|
+
const uri = localStorage.getItem('liorandb_uri');
|
|
116
|
+
|
|
117
|
+
if (token && uri) {
|
|
118
|
+
set({
|
|
119
|
+
isLoggedIn: true,
|
|
120
|
+
token,
|
|
121
|
+
connectionUri: uri,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface ConnectionConfig {
|
|
2
|
+
uri: string;
|
|
3
|
+
username: string;
|
|
4
|
+
host: string;
|
|
5
|
+
port: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AuthState {
|
|
9
|
+
isLoggedIn: boolean;
|
|
10
|
+
token: string | null;
|
|
11
|
+
connectionUri: string | null;
|
|
12
|
+
error: string | null;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Database {
|
|
17
|
+
name: string;
|
|
18
|
+
sizeOnDisk?: number;
|
|
19
|
+
empty?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Collection {
|
|
23
|
+
name: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
count?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Document {
|
|
29
|
+
_id?: string | Record<string, any>;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StoredDocument extends Document {
|
|
34
|
+
_id: string | Record<string, any>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface QueryResult {
|
|
38
|
+
data: Document[];
|
|
39
|
+
count: number;
|
|
40
|
+
executionTime: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface StoreState {
|
|
44
|
+
// Auth
|
|
45
|
+
isLoggedIn: boolean;
|
|
46
|
+
token: string | null;
|
|
47
|
+
connectionUri: string | null;
|
|
48
|
+
|
|
49
|
+
// Navigation
|
|
50
|
+
currentDatabase: string | null;
|
|
51
|
+
selectedCollection: string | null;
|
|
52
|
+
|
|
53
|
+
// Data
|
|
54
|
+
databases: Database[];
|
|
55
|
+
collections: Record<string, Collection[]>;
|
|
56
|
+
documents: Document[];
|
|
57
|
+
queryResults: QueryResult | null;
|
|
58
|
+
|
|
59
|
+
// UI State
|
|
60
|
+
isLoading: boolean;
|
|
61
|
+
error: string | null;
|
|
62
|
+
successMessage: string | null;
|
|
63
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|