datapeek 0.1.5 → 0.1.7
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/dist/cli/chunk-5X2YZYYM.js +137 -0
- package/dist/cli/dev.js +43 -11
- package/dist/cli/index.js +43 -11
- package/dist/cli/mssql-D7NDIUG7.js +20 -0
- package/dist/client/assets/index-Bz4cddDl.css +1 -0
- package/dist/client/assets/index-DDmcmxkb.js +234 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/index.html +3 -3
- package/dist/server/chunk-XFS5KTMI.js +134 -0
- package/dist/server/dev.js +43 -11
- package/dist/server/index.js +43 -11
- package/dist/server/mssql-UUT2IKOP.js +18 -0
- package/package.json +1 -1
- package/dist/client/assets/index-7OAGxTMP.css +0 -1
- package/dist/client/assets/index-Bm4Xi6LT.js +0 -192
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/server/db/mssql.ts
|
|
5
|
+
import sql from "mssql";
|
|
6
|
+
var pool = null;
|
|
7
|
+
function parseConnectionString(connectionString) {
|
|
8
|
+
const config = {
|
|
9
|
+
server: "",
|
|
10
|
+
database: "",
|
|
11
|
+
options: {
|
|
12
|
+
encrypt: true,
|
|
13
|
+
trustServerCertificate: false,
|
|
14
|
+
enableArithAbort: true
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const parts = connectionString.split(";");
|
|
18
|
+
for (const part of parts) {
|
|
19
|
+
const [key, ...valueParts] = part.split("=");
|
|
20
|
+
const value = valueParts.join("=").trim();
|
|
21
|
+
const keyLower = key.trim().toLowerCase();
|
|
22
|
+
switch (keyLower) {
|
|
23
|
+
case "server":
|
|
24
|
+
case "data source":
|
|
25
|
+
let serverValue = value;
|
|
26
|
+
if (serverValue.startsWith("tcp:")) {
|
|
27
|
+
serverValue = serverValue.substring(4);
|
|
28
|
+
}
|
|
29
|
+
const [serverHost, serverPort] = serverValue.split(",");
|
|
30
|
+
config.server = serverHost.trim();
|
|
31
|
+
if (serverPort) {
|
|
32
|
+
config.port = parseInt(serverPort.trim(), 10);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case "database":
|
|
36
|
+
case "initial catalog":
|
|
37
|
+
config.database = value;
|
|
38
|
+
break;
|
|
39
|
+
case "user id":
|
|
40
|
+
case "userid":
|
|
41
|
+
case "uid":
|
|
42
|
+
config.user = value;
|
|
43
|
+
break;
|
|
44
|
+
case "password":
|
|
45
|
+
case "pwd":
|
|
46
|
+
config.password = value;
|
|
47
|
+
break;
|
|
48
|
+
case "port":
|
|
49
|
+
config.port = parseInt(value, 10);
|
|
50
|
+
break;
|
|
51
|
+
case "encrypt":
|
|
52
|
+
config.options.encrypt = value.toLowerCase() === "true";
|
|
53
|
+
break;
|
|
54
|
+
case "trustservercertificate":
|
|
55
|
+
case "trust server certificate":
|
|
56
|
+
config.options.trustServerCertificate = value.toLowerCase() === "true";
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
async function testConnection(config) {
|
|
63
|
+
const testPool = typeof config === "string" ? new sql.ConnectionPool(parseConnectionString(config)) : new sql.ConnectionPool(config);
|
|
64
|
+
try {
|
|
65
|
+
await testPool.connect();
|
|
66
|
+
await testPool.close();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function connect(config) {
|
|
72
|
+
await disconnect();
|
|
73
|
+
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
74
|
+
pool = new sql.ConnectionPool(connectionConfig);
|
|
75
|
+
try {
|
|
76
|
+
await pool.connect();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
pool = null;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function disconnect() {
|
|
83
|
+
if (pool) {
|
|
84
|
+
try {
|
|
85
|
+
await pool.close();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
}
|
|
88
|
+
pool = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function getConnection() {
|
|
92
|
+
return pool;
|
|
93
|
+
}
|
|
94
|
+
async function executeQuery(query, parameters) {
|
|
95
|
+
if (!pool || !pool.connected) {
|
|
96
|
+
throw new Error("Not connected to database");
|
|
97
|
+
}
|
|
98
|
+
const request = pool.request();
|
|
99
|
+
if (parameters) {
|
|
100
|
+
for (const param of parameters) {
|
|
101
|
+
if (param.type) {
|
|
102
|
+
request.input(param.name, param.type, param.value);
|
|
103
|
+
} else {
|
|
104
|
+
request.input(param.name, param.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = await request.query(query);
|
|
109
|
+
return result.recordset || [];
|
|
110
|
+
}
|
|
111
|
+
async function executeQueryMultiple(query, parameters) {
|
|
112
|
+
if (!pool || !pool.connected) {
|
|
113
|
+
throw new Error("Not connected to database");
|
|
114
|
+
}
|
|
115
|
+
const request = pool.request();
|
|
116
|
+
if (parameters) {
|
|
117
|
+
for (const param of parameters) {
|
|
118
|
+
if (param.type) {
|
|
119
|
+
request.input(param.name, param.type, param.value);
|
|
120
|
+
} else {
|
|
121
|
+
request.input(param.name, param.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const result = await request.query(query);
|
|
126
|
+
return result.recordsets || [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
parseConnectionString,
|
|
131
|
+
testConnection,
|
|
132
|
+
connect,
|
|
133
|
+
disconnect,
|
|
134
|
+
getConnection,
|
|
135
|
+
executeQuery,
|
|
136
|
+
executeQueryMultiple
|
|
137
|
+
};
|
package/dist/cli/dev.js
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
connect,
|
|
5
5
|
disconnect,
|
|
6
6
|
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
7
8
|
getConnection,
|
|
8
9
|
testConnection
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5X2YZYYM.js";
|
|
10
11
|
|
|
11
12
|
// src/server/index.ts
|
|
12
13
|
import express from "express";
|
|
@@ -87,15 +88,20 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
87
88
|
});
|
|
88
89
|
connectionRoutes.get("/status", async (req, res) => {
|
|
89
90
|
try {
|
|
90
|
-
const { getConnection: getConnection2, executeQuery:
|
|
91
|
+
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
|
|
91
92
|
const pool = getConnection2();
|
|
92
93
|
if (pool && pool.connected) {
|
|
93
94
|
try {
|
|
94
|
-
const result = await
|
|
95
|
+
const result = await executeQuery3("SELECT DB_NAME() as databaseName");
|
|
95
96
|
const databaseName = result[0]?.databaseName || null;
|
|
96
97
|
res.json({ connected: true, databaseName });
|
|
97
|
-
} catch {
|
|
98
|
-
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const errorMessage = error.message || "";
|
|
100
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
101
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
102
|
+
await disconnect2();
|
|
103
|
+
}
|
|
104
|
+
res.json({ connected: false });
|
|
99
105
|
}
|
|
100
106
|
} else {
|
|
101
107
|
res.json({ connected: false });
|
|
@@ -126,6 +132,11 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
126
132
|
const result = await executeQuery(query);
|
|
127
133
|
res.json(result);
|
|
128
134
|
} catch (error) {
|
|
135
|
+
const errorMessage = error.message || "";
|
|
136
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
137
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
138
|
+
await disconnect2();
|
|
139
|
+
}
|
|
129
140
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
130
141
|
}
|
|
131
142
|
});
|
|
@@ -164,6 +175,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
164
175
|
]);
|
|
165
176
|
res.json(result);
|
|
166
177
|
} catch (error) {
|
|
178
|
+
const errorMessage = error.message || "";
|
|
179
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
180
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
181
|
+
await disconnect2();
|
|
182
|
+
}
|
|
167
183
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
168
184
|
}
|
|
169
185
|
});
|
|
@@ -274,7 +290,11 @@ ORDER BY rn`;
|
|
|
274
290
|
});
|
|
275
291
|
} catch (error) {
|
|
276
292
|
console.error("Error fetching table data:", error);
|
|
277
|
-
const errorMessage = error.message || "
|
|
293
|
+
const errorMessage = error.message || "";
|
|
294
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
295
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
296
|
+
await disconnect2();
|
|
297
|
+
}
|
|
278
298
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
279
299
|
res.status(500).json({
|
|
280
300
|
error: errorMessage,
|
|
@@ -299,17 +319,29 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
299
319
|
});
|
|
300
320
|
}
|
|
301
321
|
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
302
|
-
const hasDangerousKeyword = dangerousKeywords.some(
|
|
303
|
-
|
|
304
|
-
|
|
322
|
+
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
323
|
+
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
324
|
+
return regex.test(sqlQuery);
|
|
325
|
+
});
|
|
305
326
|
if (hasDangerousKeyword) {
|
|
306
327
|
return res.status(400).json({
|
|
307
328
|
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
308
329
|
});
|
|
309
330
|
}
|
|
310
|
-
const
|
|
311
|
-
|
|
331
|
+
const startTime = Date.now();
|
|
332
|
+
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
333
|
+
const executionTime = Date.now() - startTime;
|
|
334
|
+
res.json({
|
|
335
|
+
data: resultSets[0] || [],
|
|
336
|
+
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
337
|
+
executionTime
|
|
338
|
+
});
|
|
312
339
|
} catch (error) {
|
|
340
|
+
const errorMessage = error.message || "";
|
|
341
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
342
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
343
|
+
await disconnect2();
|
|
344
|
+
}
|
|
313
345
|
res.status(500).json({
|
|
314
346
|
error: error.message || "Query execution failed",
|
|
315
347
|
details: error.originalError?.message
|
package/dist/cli/index.js
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
connect,
|
|
5
5
|
disconnect,
|
|
6
6
|
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
7
8
|
getConnection,
|
|
8
9
|
testConnection
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5X2YZYYM.js";
|
|
10
11
|
|
|
11
12
|
// src/cli/index.ts
|
|
12
13
|
import { Command } from "commander";
|
|
@@ -90,15 +91,20 @@ connectionRoutes.delete("/", async (req, res) => {
|
|
|
90
91
|
});
|
|
91
92
|
connectionRoutes.get("/status", async (req, res) => {
|
|
92
93
|
try {
|
|
93
|
-
const { getConnection: getConnection2, executeQuery:
|
|
94
|
+
const { getConnection: getConnection2, executeQuery: executeQuery3 } = await import("./mssql-D7NDIUG7.js");
|
|
94
95
|
const pool = getConnection2();
|
|
95
96
|
if (pool && pool.connected) {
|
|
96
97
|
try {
|
|
97
|
-
const result = await
|
|
98
|
+
const result = await executeQuery3("SELECT DB_NAME() as databaseName");
|
|
98
99
|
const databaseName = result[0]?.databaseName || null;
|
|
99
100
|
res.json({ connected: true, databaseName });
|
|
100
|
-
} catch {
|
|
101
|
-
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorMessage = error.message || "";
|
|
103
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
104
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
105
|
+
await disconnect2();
|
|
106
|
+
}
|
|
107
|
+
res.json({ connected: false });
|
|
102
108
|
}
|
|
103
109
|
} else {
|
|
104
110
|
res.json({ connected: false });
|
|
@@ -129,6 +135,11 @@ tableRoutes.get("/", async (req, res) => {
|
|
|
129
135
|
const result = await executeQuery(query);
|
|
130
136
|
res.json(result);
|
|
131
137
|
} catch (error) {
|
|
138
|
+
const errorMessage = error.message || "";
|
|
139
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
140
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
141
|
+
await disconnect2();
|
|
142
|
+
}
|
|
132
143
|
res.status(500).json({ error: error.message || "Failed to fetch tables" });
|
|
133
144
|
}
|
|
134
145
|
});
|
|
@@ -167,6 +178,11 @@ tableRoutes.get("/:schema/:table", async (req, res) => {
|
|
|
167
178
|
]);
|
|
168
179
|
res.json(result);
|
|
169
180
|
} catch (error) {
|
|
181
|
+
const errorMessage = error.message || "";
|
|
182
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
183
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
184
|
+
await disconnect2();
|
|
185
|
+
}
|
|
170
186
|
res.status(500).json({ error: error.message || "Failed to fetch table structure" });
|
|
171
187
|
}
|
|
172
188
|
});
|
|
@@ -277,7 +293,11 @@ ORDER BY rn`;
|
|
|
277
293
|
});
|
|
278
294
|
} catch (error) {
|
|
279
295
|
console.error("Error fetching table data:", error);
|
|
280
|
-
const errorMessage = error.message || "
|
|
296
|
+
const errorMessage = error.message || "";
|
|
297
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
298
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
299
|
+
await disconnect2();
|
|
300
|
+
}
|
|
281
301
|
const errorDetails = error.originalError?.message || error.originalError?.info?.message || "";
|
|
282
302
|
res.status(500).json({
|
|
283
303
|
error: errorMessage,
|
|
@@ -302,17 +322,29 @@ queryRoutes.post("/", async (req, res) => {
|
|
|
302
322
|
});
|
|
303
323
|
}
|
|
304
324
|
const dangerousKeywords = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE", "EXEC", "EXECUTE"];
|
|
305
|
-
const hasDangerousKeyword = dangerousKeywords.some(
|
|
306
|
-
|
|
307
|
-
|
|
325
|
+
const hasDangerousKeyword = dangerousKeywords.some((keyword) => {
|
|
326
|
+
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
327
|
+
return regex.test(sqlQuery);
|
|
328
|
+
});
|
|
308
329
|
if (hasDangerousKeyword) {
|
|
309
330
|
return res.status(400).json({
|
|
310
331
|
error: "Query contains prohibited keywords. Only SELECT queries are allowed."
|
|
311
332
|
});
|
|
312
333
|
}
|
|
313
|
-
const
|
|
314
|
-
|
|
334
|
+
const startTime = Date.now();
|
|
335
|
+
const resultSets = await executeQueryMultiple(sqlQuery);
|
|
336
|
+
const executionTime = Date.now() - startTime;
|
|
337
|
+
res.json({
|
|
338
|
+
data: resultSets[0] || [],
|
|
339
|
+
resultSets: resultSets.length > 0 ? resultSets : [],
|
|
340
|
+
executionTime
|
|
341
|
+
});
|
|
315
342
|
} catch (error) {
|
|
343
|
+
const errorMessage = error.message || "";
|
|
344
|
+
if (errorMessage.includes("Login failed") || errorMessage.includes("authentication")) {
|
|
345
|
+
const { disconnect: disconnect2 } = await import("./mssql-D7NDIUG7.js");
|
|
346
|
+
await disconnect2();
|
|
347
|
+
}
|
|
316
348
|
res.status(500).json({
|
|
317
349
|
error: error.message || "Query execution failed",
|
|
318
350
|
details: error.originalError?.message
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
connect,
|
|
5
|
+
disconnect,
|
|
6
|
+
executeQuery,
|
|
7
|
+
executeQueryMultiple,
|
|
8
|
+
getConnection,
|
|
9
|
+
parseConnectionString,
|
|
10
|
+
testConnection
|
|
11
|
+
} from "./chunk-5X2YZYYM.js";
|
|
12
|
+
export {
|
|
13
|
+
connect,
|
|
14
|
+
disconnect,
|
|
15
|
+
executeQuery,
|
|
16
|
+
executeQueryMultiple,
|
|
17
|
+
getConnection,
|
|
18
|
+
parseConnectionString,
|
|
19
|
+
testConnection
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 222.2 84% 4.9%;--card: 0 0% 100%;--card-foreground: 222.2 84% 4.9%;--popover: 0 0% 100%;--popover-foreground: 222.2 84% 4.9%;--primary: 222.2 47.4% 11.2%;--primary-foreground: 210 40% 98%;--secondary: 210 40% 96.1%;--secondary-foreground: 222.2 47.4% 11.2%;--muted: 210 40% 96.1%;--muted-foreground: 215.4 16.3% 46.9%;--accent: 210 40% 96.1%;--accent-foreground: 222.2 47.4% 11.2%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 210 40% 98%;--border: 214.3 31.8% 91.4%;--input: 214.3 31.8% 91.4%;--ring: 222.2 84% 4.9%;--radius: .5rem;--chart-1: 12 76% 61%;--chart-2: 173 58% 39%;--chart-3: 197 37% 24%;--chart-4: 43 74% 66%;--chart-5: 27 87% 67%;--header-bg: 0 0% 100%;--sidebar-bg: 0 0% 100%;--content-bg: 0 0% 100%;--tabs-bg: 0 0% 100%;--grid-bg: 0 0% 100%}.dark{--background: 222.2 84% 4.9%;--foreground: 210 40% 98%;--card: 222.2 84% 4.9%;--card-foreground: 210 40% 98%;--popover: 222.2 84% 4.9%;--popover-foreground: 210 40% 98%;--primary: 210 40% 98%;--primary-foreground: 222.2 47.4% 11.2%;--secondary: 217.2 32.6% 17.5%;--secondary-foreground: 210 40% 98%;--muted: 217.2 32.6% 17.5%;--muted-foreground: 215 20.2% 65.1%;--accent: 217.2 32.6% 17.5%;--accent-foreground: 210 40% 98%;--destructive: 0 70% 55%;--destructive-foreground: 210 40% 98%;--border: 217.2 32.6% 17.5%;--input: 217.2 32.6% 17.5%;--ring: 212.7 26.8% 83.9%;--chart-1: 220 70% 50%;--chart-2: 160 60% 45%;--chart-3: 30 80% 55%;--chart-4: 280 65% 60%;--chart-5: 340 75% 55%;--header-bg: 222.2 84% 6.5%;--sidebar-bg: 222.2 84% 5.5%;--content-bg: 222.2 84% 4.9%;--tabs-bg: 222.2 84% 5.2%;--grid-bg: 222.2 84% 5%}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground));font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}code,pre{font-family:JetBrains Mono,Menlo,Monaco,Courier New,monospace}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-2{left:.5rem}.right-0{right:0}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-4{top:1rem}.top-full{top:100%}.z-10{z-index:10}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-4{margin-left:1rem}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.flex{display:flex}.inline-flex{display:inline-flex}.\!table{display:table!important}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.w-12{width:3rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.min-w-\[8rem\]{min-width:8rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-cell{cursor:cell}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-input{border-color:hsl(var(--input))}.border-primary{border-color:hsl(var(--primary))}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-black\/50{background-color:#00000080}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-content-bg{background-color:hsl(var(--content-bg))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-destructive\/90{background-color:hsl(var(--destructive) / .9)}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-grid-bg{background-color:hsl(var(--grid-bg))}.bg-header-bg{background-color:hsl(var(--header-bg))}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/20{background-color:hsl(var(--primary) / .2)}.bg-primary\/90{background-color:hsl(var(--primary) / .9)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-secondary\/80{background-color:hsl(var(--secondary) / .8)}.bg-sidebar-bg{background-color:hsl(var(--sidebar-bg))}.bg-tabs-bg{background-color:hsl(var(--tabs-bg))}.bg-transparent{background-color:transparent}.fill-yellow-500{fill:#eab308}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pl-8{padding-left:2rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.last\:border-b-0:last-child{border-bottom-width:0px}.last\:border-r-0:last-child{border-right-width:0px}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-green-500\/20:hover{background-color:#22c55e33}.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted) / .3)}.hover\:bg-muted\/70:hover{background-color:hsl(var(--muted) / .7)}.hover\:bg-primary\/50:hover{background-color:hsl(var(--primary) / .5)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:bg-transparent:hover{background-color:transparent}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-destructive\/10:focus{background-color:hsl(var(--destructive) / .1)}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-destructive:focus{color:hsl(var(--destructive))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.dark\:bg-content-bg:is(.dark *){background-color:hsl(var(--content-bg))}.dark\:bg-grid-bg:is(.dark *){background-color:hsl(var(--grid-bg))}.dark\:bg-header-bg:is(.dark *){background-color:hsl(var(--header-bg))}.dark\:bg-sidebar-bg:is(.dark *){background-color:hsl(var(--sidebar-bg))}.dark\:bg-tabs-bg:is(.dark *){background-color:hsl(var(--tabs-bg))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}@media(min-width:640px){.sm\:max-w-\[500px\]{max-width:500px}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:text-left{text-align:left}}
|