@hienlh/ppm 0.11.16 → 0.11.18

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/web/assets/{ai-settings-section-C6FDY8qE.js → ai-settings-section-L6XAmZEP.js} +1 -1
  3. package/dist/web/assets/{api-client-Bn-Pi9k5.js → api-client-CwbMRXYl.js} +1 -1
  4. package/dist/web/assets/{api-settings-C__hxGX2.js → api-settings-ByUGHhTB.js} +1 -1
  5. package/dist/web/assets/{audio-preview-DjLLuzUD.js → audio-preview-CZ7uVdJ1.js} +1 -1
  6. package/dist/web/assets/{chat-tab-CL9_hlGE.js → chat-tab-D0edjxcO.js} +4 -4
  7. package/dist/web/assets/{code-editor-YaQFb5Db.js → code-editor-CHWEBvuT.js} +2 -2
  8. package/dist/web/assets/{conflict-editor-Dk6MS9pE.js → conflict-editor-BsNC4CIp.js} +1 -1
  9. package/dist/web/assets/database-viewer-dYaf9tXG.js +2 -0
  10. package/dist/web/assets/{diff-viewer-DPgNFO4j.js → diff-viewer-BlQsizFF.js} +1 -1
  11. package/dist/web/assets/{extension-webview-B8361xT-.js → extension-webview-DXjdd0U6.js} +1 -1
  12. package/dist/web/assets/{image-preview-B8oAGvHG.js → image-preview-eH8PNO1D.js} +1 -1
  13. package/dist/web/assets/{index-Bl3W1FYm.js → index-BVQikaUv.js} +6 -6
  14. package/dist/web/assets/index-C99i-AFP.css +2 -0
  15. package/dist/web/assets/keybindings-store-BIQHClUy.js +1 -0
  16. package/dist/web/assets/{keybindings-store-C9KsBH7z.js → keybindings-store-CThBg3hS.js} +1 -1
  17. package/dist/web/assets/{markdown-renderer-B8Gp0FKa.js → markdown-renderer-CyDOVFYo.js} +1 -1
  18. package/dist/web/assets/{pdf-preview-D7JrByDM.js → pdf-preview-BOJHsWt2.js} +1 -1
  19. package/dist/web/assets/{port-forwarding-tab-D2w0zW8n.js → port-forwarding-tab-BxuO01Zb.js} +1 -1
  20. package/dist/web/assets/{postgres-viewer-ei8XhyiT.js → postgres-viewer-cLp_WazA.js} +1 -1
  21. package/dist/web/assets/{project-store-BYmQ0fDC.js → project-store-IB6pAGQh.js} +1 -1
  22. package/dist/web/assets/{settings-store-B9axDbuA.js → settings-store-fDOEursg.js} +2 -2
  23. package/dist/web/assets/settings-tab-BQpEkd-V.js +1 -0
  24. package/dist/web/assets/{sql-query-editor-BnpKNGG0.js → sql-query-editor-JwymAmuK.js} +1 -1
  25. package/dist/web/assets/{sqlite-viewer-BNkPwKNL.js → sqlite-viewer-BgEY7Fml.js} +1 -1
  26. package/dist/web/assets/{terminal-tab-DwYueV8K.js → terminal-tab-B6meTG0M.js} +1 -1
  27. package/dist/web/assets/{use-blob-url-BSltfg79.js → use-blob-url-BU9hYOj9.js} +1 -1
  28. package/dist/web/assets/{use-monaco-theme-BERjR8IA.js → use-monaco-theme-o7Ip-BDL.js} +1 -1
  29. package/dist/web/assets/{video-preview-BnZt6xtr.js → video-preview-D2gRujKO.js} +1 -1
  30. package/dist/web/index.html +8 -8
  31. package/dist/web/sw.js +1 -1
  32. package/package.json +1 -1
  33. package/src/server/routes/database.ts +11 -3
  34. package/src/services/postgres.service.ts +1 -1
  35. package/src/services/supervisor.ts +7 -2
  36. package/src/web/components/database/connection-list.tsx +7 -2
  37. package/src/web/components/database/data-grid.tsx +1 -1
  38. package/src/web/components/database/database-sidebar.tsx +2 -1
  39. package/src/web/components/database/use-connections.ts +24 -11
  40. package/src/web/components/layout/upgrade-banner.tsx +47 -37
  41. package/src/web/lib/api-client.ts +6 -1
  42. package/dist/web/assets/database-viewer-BO58nGFL.js +0 -2
  43. package/dist/web/assets/index-B0V_IYbX.css +0 -2
  44. package/dist/web/assets/keybindings-store-BkZjvU9J.js +0 -1
  45. package/dist/web/assets/settings-tab-Dd4mGQHq.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.11.16",
3
+ "version": "0.11.18",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -9,6 +9,14 @@ import { syncTables, searchTables, getTablesFromCache } from "../../services/tab
9
9
  import { isReadOnlyQuery } from "../../services/database/readonly-check.ts";
10
10
  import { ok, err } from "../../types/api.ts";
11
11
 
12
+ /** Race a promise against a timeout — ensures routes always respond */
13
+ function withTimeout<T>(promise: Promise<T>, ms: number, message = "Connection timed out"): Promise<T> {
14
+ return Promise.race([
15
+ promise,
16
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error(message)), ms)),
17
+ ]);
18
+ }
19
+
12
20
  export const databaseRoutes = new Hono();
13
21
 
14
22
  /** Strip sensitive connection_config from connection responses */
@@ -190,7 +198,7 @@ databaseRoutes.post("/test", async (c) => {
190
198
  const body = await c.req.json<{ type: "sqlite" | "postgres"; connectionConfig: { type: string; path?: string; connectionString?: string } }>();
191
199
  if (!body.type || !body.connectionConfig) return c.json(err("type and connectionConfig required"), 400);
192
200
  const adapter = getAdapter(body.type);
193
- const result = await adapter.testConnection(body.connectionConfig as import("../../types/database.ts").DbConnectionConfig);
201
+ const result = await withTimeout(adapter.testConnection(body.connectionConfig as import("../../types/database.ts").DbConnectionConfig), 15_000);
194
202
  return c.json(ok(result));
195
203
  } catch (e) {
196
204
  return c.json(err((e as Error).message), 500);
@@ -204,7 +212,7 @@ databaseRoutes.post("/connections/:id/test", async (c) => {
204
212
  if (!conn) return c.json(err("Connection not found"), 404);
205
213
  const config = decryptConfig(conn.connection_config);
206
214
  const adapter = getAdapter(conn.type);
207
- const result = await adapter.testConnection(config);
215
+ const result = await withTimeout(adapter.testConnection(config), 15_000);
208
216
  return c.json(ok(result));
209
217
  } catch (e) {
210
218
  return c.json(err((e as Error).message), 500);
@@ -217,7 +225,7 @@ databaseRoutes.get("/connections/:id/tables", async (c) => {
217
225
  const conn = resolveConn(c.req.param("id"));
218
226
  if (!conn) return c.json(err("Connection not found"), 404);
219
227
  const useCached = c.req.query("cached") === "1";
220
- const result = useCached ? getTablesFromCache(conn.id) : await syncTables(conn.id);
228
+ const result = useCached ? getTablesFromCache(conn.id) : await withTimeout(syncTables(conn.id), 15_000);
221
229
  const tables = result.map((t) => ({ name: t.tableName, schema: t.schemaName, rowCount: t.rowCount }));
222
230
  return c.json(ok(tables));
223
231
  } catch (e) {
@@ -48,7 +48,7 @@ class PostgresService {
48
48
  const sslOpts = sslmode === "no-verify" || sslmode === "require"
49
49
  ? { rejectUnauthorized: false }
50
50
  : sslmode === "disable" ? false : undefined;
51
- const sql = postgres(connectionString, { max: 3, idle_timeout: 60, ssl: sslOpts as any });
51
+ const sql = postgres(connectionString, { max: 3, idle_timeout: 60, connect_timeout: 10, ssl: sslOpts as any });
52
52
  const timer = setTimeout(() => this.disconnect(connectionString), IDLE_TIMEOUT_MS);
53
53
  this.cache.set(connectionString, { sql, timer });
54
54
  return sql;
@@ -806,12 +806,17 @@ export async function runSupervisor(opts: {
806
806
  log("ERROR", `Unhandled rejection: ${reason}`);
807
807
  });
808
808
 
809
- // Full write to clear any stale data from previous runs (different port, dead PIDs, etc.)
809
+ // Full write to clear stale data but preserve tunnel info during self-replace upgrade
810
+ // so the new supervisor can adopt the existing tunnel and keep the domain.
810
811
  writeFileSync(PID_FILE(), String(process.pid));
812
+ const prevStatus = readStatus();
813
+ const isUpgrade = prevStatus.state === "upgrading";
811
814
  writeStatus({
812
815
  supervisorPid: process.pid, port: opts.port, host: opts.host, availableVersion: null,
813
816
  state: "running", pausedAt: null, pauseReason: null, lastCrashError: null,
814
- pid: null, tunnelPid: null, shareUrl: null,
817
+ pid: null,
818
+ tunnelPid: isUpgrade ? (prevStatus.tunnelPid ?? null) : null,
819
+ shareUrl: isUpgrade ? (prevStatus.shareUrl ?? null) : null,
815
820
  });
816
821
 
817
822
  // Build __serve__ args
@@ -14,6 +14,7 @@ interface ColumnInfo {
14
14
  interface ConnectionListProps {
15
15
  connections: Connection[];
16
16
  cachedTables: Map<number, CachedTable[]>;
17
+ refreshErrors?: Map<number, string>;
17
18
  onOpenTable: (conn: Connection, tableName: string, schemaName: string) => void;
18
19
  onRefreshTables: (id: number) => Promise<void>;
19
20
  onEdit: (conn: Connection) => void;
@@ -27,7 +28,7 @@ interface GroupMap {
27
28
  }
28
29
 
29
30
  export function ConnectionList({
30
- connections, cachedTables,
31
+ connections, cachedTables, refreshErrors,
31
32
  onOpenTable, onRefreshTables, onEdit, onDelete,
32
33
  onFetchColumns, columnCache,
33
34
  }: ConnectionListProps) {
@@ -186,7 +187,11 @@ export function ConnectionList({
186
187
  {isExpanded && (
187
188
  <div className="ml-[11px] border-l border-dashed border-border pl-1">
188
189
  {isRefreshing && tables.length === 0 && <p className="text-[10px] text-text-subtle px-2 py-1">Loading…</p>}
189
- {!isRefreshing && tables.length === 0 && <p className="text-[10px] text-text-subtle px-2 py-1">No tables cached</p>}
190
+ {!isRefreshing && tables.length === 0 && (
191
+ refreshErrors?.get(conn.id)
192
+ ? <p className="text-[10px] text-red-500 px-2 py-1 break-all">{refreshErrors.get(conn.id)}</p>
193
+ : <p className="text-[10px] text-text-subtle px-2 py-1">No tables cached</p>
194
+ )}
190
195
  {tables.length > 0 && (
191
196
  <>
192
197
  {tables.length > 5 && (
@@ -331,7 +331,7 @@ export function DataGrid({
331
331
  if (pc !== col) stickyWidth = Math.max(stickyWidth, offset + (colWidths.get(pc) ?? 0));
332
332
  }
333
333
  const targetLeft = th.offsetLeft - stickyWidth;
334
- container.scrollTo({ left: targetLeft, behavior: "smooth" });
334
+ container.scrollTo({ left: targetLeft, behavior: "instant" });
335
335
  setColSearchOpen(false);
336
336
  }, [pinnedColOffsets, colWidths]);
337
337
 
@@ -7,7 +7,7 @@ import { ConnectionImportExport } from "./connection-import-export";
7
7
  import { useConnections, type Connection, type CreateConnectionData, type UpdateConnectionData } from "./use-connections";
8
8
 
9
9
  export function DatabaseSidebar() {
10
- const { connections, loading, cachedTables, columnCache, createConnection, updateConnection, deleteConnection, testConnection, testRawConnection, refreshTables, fetchColumns, exportConnections, importConnections } = useConnections();
10
+ const { connections, loading, cachedTables, refreshErrors, columnCache, createConnection, updateConnection, deleteConnection, testConnection, testRawConnection, refreshTables, fetchColumns, exportConnections, importConnections } = useConnections();
11
11
  const openTab = useTabStore((s) => s.openTab);
12
12
  const [addOpen, setAddOpen] = useState(false);
13
13
  const [editConn, setEditConn] = useState<Connection | null>(null);
@@ -62,6 +62,7 @@ export function DatabaseSidebar() {
62
62
  <ConnectionList
63
63
  connections={connections}
64
64
  cachedTables={cachedTables}
65
+ refreshErrors={refreshErrors}
65
66
  onOpenTable={handleOpenTable}
66
67
  onRefreshTables={refreshTables}
67
68
  onEdit={setEditConn}
@@ -41,6 +41,7 @@ export function useConnections() {
41
41
  const [connections, setConnections] = useState<Connection[]>([]);
42
42
  const [loading, setLoading] = useState(true);
43
43
  const [cachedTables, setCachedTables] = useState<Map<number, CachedTable[]>>(new Map());
44
+ const [refreshErrors, setRefreshErrors] = useState<Map<number, string>>(new Map());
44
45
 
45
46
  const fetchConnections = useCallback(async () => {
46
47
  try {
@@ -84,15 +85,22 @@ export function useConnections() {
84
85
  }, []);
85
86
 
86
87
  const refreshTables = useCallback(async (id: number): Promise<void> => {
87
- const raw = await api.get<{ name: string; schema: string; rowCount: number }[]>(`/api/db/connections/${id}/tables`);
88
- const tables: CachedTable[] = raw.map((t) => ({
89
- connectionId: id,
90
- tableName: t.name,
91
- schemaName: t.schema,
92
- rowCount: t.rowCount,
93
- cachedAt: new Date().toISOString(),
94
- }));
95
- setCachedTables((prev) => new Map(prev).set(id, tables));
88
+ setRefreshErrors((prev) => { const m = new Map(prev); m.delete(id); return m; });
89
+ try {
90
+ const raw = await api.get<{ name: string; schema: string; rowCount: number }[]>(`/api/db/connections/${id}/tables`);
91
+ const tables: CachedTable[] = raw.map((t) => ({
92
+ connectionId: id,
93
+ tableName: t.name,
94
+ schemaName: t.schema,
95
+ rowCount: t.rowCount,
96
+ cachedAt: new Date().toISOString(),
97
+ }));
98
+ setCachedTables((prev) => new Map(prev).set(id, tables));
99
+ } catch (e) {
100
+ const msg = (e as Error).message || "Connection failed";
101
+ setRefreshErrors((prev) => new Map(prev).set(id, msg));
102
+ throw e; // re-throw so callers know it failed
103
+ }
96
104
  }, []);
97
105
 
98
106
  /** Fetch column metadata for a table (lazy loaded for schema tree) */
@@ -117,8 +125,13 @@ export function useConnections() {
117
125
  "/api/db/connections/import", data,
118
126
  );
119
127
  await fetchConnections();
128
+ // Auto-refresh table cache for newly imported connections (fire-and-forget)
129
+ const imported = result.connections ?? [];
130
+ if (imported.length > 0) {
131
+ Promise.all(imported.map((c) => refreshTables(c.id).catch(() => {})));
132
+ }
120
133
  return result;
121
- }, [fetchConnections]);
134
+ }, [fetchConnections, refreshTables]);
122
135
 
123
- return { connections, loading, cachedTables, columnCache, createConnection, updateConnection, deleteConnection, testConnection, testRawConnection, refreshTables, fetchColumns, exportConnections, importConnections };
136
+ return { connections, loading, cachedTables, refreshErrors, columnCache, createConnection, updateConnection, deleteConnection, testConnection, testRawConnection, refreshTables, fetchColumns, exportConnections, importConnections };
124
137
  }
@@ -1,12 +1,10 @@
1
1
  import { useState, useEffect, useCallback } from "react";
2
2
  import { api } from "@/lib/api-client";
3
3
  import { toast } from "sonner";
4
- import { Loader2, ArrowUpCircle, X } from "lucide-react";
4
+ import { Loader2, ArrowUpCircle, X, RefreshCw, CheckCircle2 } from "lucide-react";
5
5
 
6
6
  const POLL_INTERVAL_MS = 60_000;
7
7
  const DISMISS_KEY_PREFIX = "ppm-upgrade-dismissed-";
8
- const RESTART_POLL_MS = 1_500;
9
- const RESTART_TIMEOUT_MS = 60_000;
10
8
 
11
9
  interface UpgradeStatus {
12
10
  currentVersion: string;
@@ -21,28 +19,13 @@ interface UpgradeResult {
21
19
  message?: string;
22
20
  }
23
21
 
24
- /** Poll /api/health aggressively until server goes down then back up, then reload. */
25
- async function waitForServerRestart(): Promise<boolean> {
26
- let serverWentDown = false;
27
- const start = Date.now();
28
-
29
- while (Date.now() - start < RESTART_TIMEOUT_MS) {
30
- await new Promise((r) => setTimeout(r, RESTART_POLL_MS));
31
- try {
32
- const res = await fetch("/api/health", { cache: "no-store" });
33
- if (res.ok && serverWentDown) {
34
- if ("caches" in window) {
35
- const keys = await caches.keys();
36
- await Promise.all(keys.map((k) => caches.delete(k)));
37
- }
38
- window.location.reload();
39
- return true;
40
- }
41
- } catch {
42
- serverWentDown = true;
43
- }
22
+ /** Clear browser/SW caches and reload the page */
23
+ async function clearCachesAndReload() {
24
+ if ("caches" in window) {
25
+ const keys = await caches.keys();
26
+ await Promise.all(keys.map((k) => caches.delete(k)));
44
27
  }
45
- return false;
28
+ window.location.reload();
46
29
  }
47
30
 
48
31
  interface UpgradeBannerProps {
@@ -52,6 +35,7 @@ interface UpgradeBannerProps {
52
35
  export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
53
36
  const [availableVersion, setAvailableVersion] = useState<string | null>(null);
54
37
  const [upgrading, setUpgrading] = useState(false);
38
+ const [upgradeComplete, setUpgradeComplete] = useState(false);
55
39
  const [dismissed, setDismissed] = useState(false);
56
40
 
57
41
  // Poll for upgrade status
@@ -86,12 +70,9 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
86
70
  const data = await api.post<UpgradeResult>("/api/upgrade/apply");
87
71
 
88
72
  if (data.restart) {
89
- // Server will restart — poll aggressively until it comes back
90
- const restarted = await waitForServerRestart();
91
- if (!restarted) {
92
- toast.warning("Upgrade installed but server hasn't restarted. Try refreshing manually.");
93
- setUpgrading(false);
94
- }
73
+ // Upgrade installed, server will restart — ask user to reload
74
+ setUpgrading(false);
75
+ setUpgradeComplete(true);
95
76
  } else {
96
77
  // No supervisor — manual restart needed
97
78
  toast.info(data.message || "Upgrade installed. Restart PPM manually.");
@@ -102,10 +83,21 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
102
83
  setDismissed(true);
103
84
  }
104
85
  } catch (e) {
105
- toast.error(`Upgrade failed: ${(e as Error).message}`);
106
- setUpgrading(false);
86
+ // If fetch failed with a network error, the server likely died mid-response
87
+ // after the install succeeded (supervisor killed the server before response flushed).
88
+ // Show reload prompt instead of a confusing error.
89
+ const isNetworkError = e instanceof TypeError
90
+ || (e as Error).message?.includes("fetch")
91
+ || (e as Error).message?.includes("network");
92
+ if (isNetworkError) {
93
+ setUpgrading(false);
94
+ setUpgradeComplete(true);
95
+ } else {
96
+ toast.error(`Upgrade failed: ${(e as Error).message}`);
97
+ setUpgrading(false);
98
+ }
107
99
  }
108
- }, []);
100
+ }, [availableVersion]);
109
101
 
110
102
  const handleDismiss = useCallback(() => {
111
103
  if (availableVersion) {
@@ -114,7 +106,7 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
114
106
  setDismissed(true);
115
107
  }, [availableVersion]);
116
108
 
117
- const visible = !!availableVersion && !dismissed;
109
+ const visible = (!!availableVersion && !dismissed) || upgradeComplete;
118
110
 
119
111
  useEffect(() => {
120
112
  onVisibilityChange?.(visible);
@@ -123,12 +115,30 @@ export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
123
115
  if (!visible) return null;
124
116
 
125
117
  return (
126
- <div className="w-full bg-blue-600 dark:bg-blue-700 text-white px-3 py-1 flex items-center justify-between gap-2 z-50 text-sm shrink-0">
127
- {upgrading ? (
118
+ <div className={`w-full text-white px-3 py-1 flex items-center justify-between gap-2 z-50 text-sm shrink-0 ${
119
+ upgradeComplete ? "bg-green-600 dark:bg-green-700" : "bg-blue-600 dark:bg-blue-700"
120
+ }`}>
121
+ {upgradeComplete ? (
122
+ <>
123
+ <div className="flex items-center gap-2 flex-1 min-w-0">
124
+ <CheckCircle2 className="size-4 shrink-0" />
125
+ <span className="truncate">
126
+ Upgrade to v{availableVersion} installed! Reload to apply.
127
+ </span>
128
+ </div>
129
+ <button
130
+ onClick={clearCachesAndReload}
131
+ className="bg-white text-green-600 font-medium rounded-full px-3 py-0.5 text-xs min-h-[28px] min-w-[28px] flex items-center gap-1.5 justify-center hover:bg-green-50 active:bg-green-100 transition-colors shrink-0"
132
+ >
133
+ <RefreshCw className="size-3" />
134
+ Reload
135
+ </button>
136
+ </>
137
+ ) : upgrading ? (
128
138
  <div className="flex items-center gap-2 flex-1 min-w-0">
129
139
  <Loader2 className="size-4 animate-spin shrink-0" />
130
140
  <span className="truncate">
131
- Upgrading to v{availableVersion}... PPM will restart shortly
141
+ Upgrading to v{availableVersion}...
132
142
  </span>
133
143
  </div>
134
144
  ) : (
@@ -75,7 +75,12 @@ class ApiClient {
75
75
  throw new Error("Unauthorized");
76
76
  }
77
77
 
78
- const json = await res.json();
78
+ let json: any;
79
+ try {
80
+ json = await res.json();
81
+ } catch {
82
+ throw new Error(res.ok ? "Empty response from server" : `Server error (HTTP ${res.status})`);
83
+ }
79
84
 
80
85
  if (json.ok === false) {
81
86
  throw new Error(json.error ?? `HTTP ${res.status}`);
@@ -1,2 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-FhOqtrmT.js";import{b as t,x as n}from"./vendor-markdown-0Mxgxy0L.js";import{t as r}from"./createLucideIcon-BjHrJDVb.js";import{n as i,r as a,t as o}from"./plus-51UQ45rf.js";import{t as s}from"./chevron-right-BzAdxJRG.js";import{t as c}from"./database-D4DIhgi-.js";import{n as l,r as u,t as d}from"./x-DlFGzN8d.js";import{t as f}from"./refresh-cw-LlbZDJpO.js";import{t as p}from"./trash-2-CJYoLw7Q.js";import{t as m}from"./api-client-Bn-Pi9k5.js";import"./vendor-mermaid-BlWh9BJO.js";import"./settings-store-B9axDbuA.js";import{t as h}from"./tab-store-B3M9hjho.js";import{E as g,K as _,N as v,O as y,P as b,h as x,k as S}from"./index-Bl3W1FYm.js";import"./use-monaco-theme-BERjR8IA.js";import{t as C}from"./sql-query-editor-BnpKNGG0.js";import{n as w}from"./csv-parser--2WJNgS7.js";var T=r(`columns-3`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}]]),E=r(`funnel`,[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`,key:`sc7q7i`}]]),D=e(n(),1);function O(e,t,n,r){return`ppm-db-${e}-${n}.${t}-p${r}`}function ee(e,t,n,r){try{let i=sessionStorage.getItem(O(e,t,n,r));return i?JSON.parse(i):null}catch{return null}}function te(e,t,n,r,i,a){try{sessionStorage.setItem(O(e,t,n,r),JSON.stringify({data:i,cols:a}))}catch{}}function ne(e){let t=`/api/db/connections/${e}`,[n,r]=(0,D.useState)(null),[i,a]=(0,D.useState)(`public`),[o,s]=(0,D.useState)(null),[c,l]=(0,D.useState)([]),[u,d]=(0,D.useState)(!1),[f,p]=(0,D.useState)(null),[h,g]=(0,D.useState)(1),[_,v]=(0,D.useState)(null),[y,b]=(0,D.useState)(null),[x,S]=(0,D.useState)(!1),[C,w]=(0,D.useState)(null),[T,E]=(0,D.useState)(`ASC`),O=(0,D.useCallback)(async(r,a,o,c,u)=>{let f=r??n,g=a??i;if(!f)return;d(!0);let _=c===void 0?C:c,v=u??T;try{let n=_?`&orderBy=${encodeURIComponent(_)}&orderDir=${v}`:``,[r,i]=await Promise.all([m.get(`${t}/data?table=${encodeURIComponent(f)}&schema=${g}&page=${o??h}&limit=100${n}`),m.get(`${t}/schema?table=${encodeURIComponent(f)}&schema=${g}`)]);s(r),l(i),te(e,f,g,o??h,r,i)}catch(e){p(e.message)}finally{d(!1)}},[t,e,n,i,h,C,T]),ne=(0,D.useCallback)((t,n=`public`)=>{r(t),a(n),g(1),v(null);let i=ee(e,t,n,1);i?(s(i.data),l(i.cols),d(!1),O(t,n,1)):O(t,n,1)},[e,O]),k=(0,D.useCallback)(e=>{g(e),O(void 0,void 0,e)},[O]),A=(0,D.useCallback)(async e=>{S(!0),b(null);try{let r=await m.post(`${t}/query`,{sql:e});v(r),r.changeType===`modify`&&O(n??void 0,i)}catch(e){b(e.message)}finally{S(!1)}},[t,n,i,O]),re=(0,D.useCallback)(async(e,r,a,o)=>{if(!n)return;let s=n,c=i;try{await m.put(`${t}/cell`,{table:s,schema:c,pkColumn:e,pkValue:r,column:a,value:o}),O(s,c)}catch(e){p(e.message)}},[t,n,i,O]),j=(0,D.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await m.del(`${t}/row`,{table:a,schema:o,pkColumn:e,pkValue:r}),O(a,o)}catch(e){p(e.message)}},[t,n,i,O]);return{selectedTable:n,selectedSchema:i,selectTable:ne,tableData:o,schema:c,loading:u,error:f,page:h,setPage:k,orderBy:C,orderDir:T,toggleSort:(0,D.useCallback)(e=>{let t,n=`ASC`;C===e?T===`ASC`?(t=e,n=`DESC`):(t=null,n=`ASC`):(t=e,n=`ASC`),w(t),E(n),g(1),O(void 0,void 0,1,t,n)},[C,T,O]),queryResult:_,queryError:y,queryLoading:x,executeQuery:A,updateCell:re,deleteRow:j,bulkDelete:(0,D.useCallback)(async(e,r)=>{if(!n)return;let a=n,o=i;try{await m.post(`${t}/rows/delete`,{table:a,schema:o,pkColumn:e,pkValues:r}),O(a,o)}catch(e){p(e.message)}},[t,n,i,O]),insertRow:(0,D.useCallback)(async e=>{if(!n)return;let r=n,a=i;try{await m.post(`${t}/row`,{table:r,schema:a,values:e}),O(r,a)}catch(e){throw p(e.message),e}},[t,n,i,O]),refreshData:O,queryAsTable:(0,D.useCallback)(async e=>{d(!0);try{let n=await m.post(`${t}/query`,{sql:e});n.changeType===`select`&&s({columns:n.columns,rows:n.rows,total:n.rows.length,page:1,limit:n.rows.length})}catch(e){p(e.message)}finally{d(!1)}},[t])}}var k=t();function A(e,t,n){let r=new Blob([t],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=e,a.click(),URL.revokeObjectURL(i)}function re({columns:e,rows:t,filename:n=`export`,exportAllUrl:r}){let[i,a]=(0,D.useState)(!1),[o,s]=(0,D.useState)(!1),c=(0,D.useRef)(null);(0,D.useEffect)(()=>{if(!i)return;let e=e=>{c.current&&!c.current.contains(e.target)&&a(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[i]);let l=()=>{let r=w(e,t.map(t=>e.map(e=>String(t[e]??``))));A(`${n}.csv`,r,`text/csv`),a(!1)},d=()=>{let e=JSON.stringify(t,null,2);A(`${n}.json`,e,`application/json`),a(!1)},f=async e=>{if(r){s(!0);try{let t=await(await fetch(`${r}&format=${e}&limit=10000`)).text(),i=e===`csv`?`text/csv`:`application/json`;A(`${n}-all.${e}`,t,i)}catch{}s(!1),a(!1)}};return e.length===0||t.length===0?null:(0,k.jsxs)(`div`,{className:`relative`,ref:c,children:[(0,k.jsx)(`button`,{type:`button`,onClick:()=>a(e=>!e),className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,title:`Export`,children:(0,k.jsx)(u,{className:`size-3.5`})}),i&&(0,k.jsxs)(`div`,{className:`absolute right-0 top-full mt-1 z-50 bg-popover border border-border rounded-md shadow-md py-1 min-w-[160px] text-xs`,children:[(0,k.jsx)(`button`,{onClick:l,className:`w-full text-left px-3 py-1.5 hover:bg-muted transition-colors`,children:`Export Page (CSV)`}),(0,k.jsx)(`button`,{onClick:d,className:`w-full text-left px-3 py-1.5 hover:bg-muted transition-colors`,children:`Export Page (JSON)`}),r&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`div`,{className:`border-t border-border my-1`}),(0,k.jsx)(`button`,{onClick:()=>f(`csv`),disabled:o,className:`w-full text-left px-3 py-1.5 hover:bg-muted transition-colors disabled:opacity-50`,children:o?`Exporting…`:`Export All (CSV)`}),(0,k.jsx)(`button`,{onClick:()=>f(`json`),disabled:o,className:`w-full text-left px-3 py-1.5 hover:bg-muted transition-colors disabled:opacity-50`,children:o?`Exporting…`:`Export All (JSON)`})]})]})]})}function j({tableData:e,schema:t,loading:n,page:r,onPageChange:c,onCellUpdate:l,onRowDelete:u,orderBy:f,orderDir:m,onToggleSort:b,onBulkDelete:C,onInsertRow:w,connectionId:O,selectedTable:ee,selectedSchema:te,connectionName:ne,columnFilters:A={},onColumnFilter:j}){let[M,N]=(0,D.useState)(null),[P,F]=(0,D.useState)(``),[I,L]=(0,D.useState)(null),[R,se]=(0,D.useState)(``),[z,B]=(0,D.useState)(new Set),[ce,le]=(0,D.useState)(!1),[ue,de]=(0,D.useState)({}),[fe,V]=(0,D.useState)(null),[pe,me]=(0,D.useState)(!1),{openTab:H}=h(x(e=>({openTab:e.openTab}))),he=(0,D.useCallback)(e=>{H({type:`editor`,title:e.col,projectId:null,closable:!0,metadata:{inlineContent:e.value,inlineLanguage:ie(e.value)}})},[H]),[U,ge]=(0,D.useState)(new Set),[_e,ve]=(0,D.useState)(new Set),[ye,be]=(0,D.useState)(null),[xe,W]=(0,D.useState)(!1),Se=(0,D.useRef)(null),G=(0,D.useMemo)(()=>t.find(e=>e.pk)?.name||(t.find(e=>e.name.toLowerCase()===`id`)?.name??null),[t]),Ce=(0,D.useCallback)(e=>{let t=JSON.stringify(e,null,2),n=G?String(e[G]??``):``;H({type:`editor`,title:n?`Row ${n}`:`Row`,projectId:null,closable:!0,metadata:{inlineContent:t,inlineLanguage:`json`}})},[H,G]),we=(0,D.useRef)(M);we.current=M;let K=(0,D.useRef)(P);K.current=P;let Te=(0,D.useRef)(z);Te.current=z;let Ee=(0,D.useRef)(I);Ee.current=I;let De=(0,D.useCallback)((e,t,n)=>{N({rowIdx:e,col:t}),F(n==null?``:typeof n==`object`?JSON.stringify(n):String(n))},[]),Oe=(0,D.useCallback)(()=>{let t=we.current;if(!t||!e||!G)return;let n=e.rows[t.rowIdx];if(!n)return;let r=n[t.col];String(r??``)!==K.current&&l(G,n[G],t.col,K.current===``?null:K.current),N(null)},[e,G,l]),ke=(0,D.useCallback)(()=>N(null),[]),Ae=(0,D.useCallback)(t=>{if(!e||!G||!u)return;let n=e.rows[t];n&&(u(G,n[G]),L(null))},[e,G,u]),je=(0,D.useCallback)(e=>{ge(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),Me=(0,D.useCallback)(e=>{ve(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),Ne=(0,D.useCallback)((e,t)=>{let n={...A};t?n[e]=t:delete n[e],j?.(n)},[A,j]),Pe=(0,D.useCallback)(e=>{B(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},[]),Fe=(0,D.useCallback)(()=>{e&&B(t=>t.size===e.rows.length?new Set:new Set(e.rows.map((e,t)=>t)))},[e]),Ie=(0,D.useCallback)(()=>{!e||!G||!C||(C(G,Array.from(z).map(t=>e.rows[t]?.[G]).filter(e=>e!=null)),B(new Set),me(!1))},[e,G,C,z]),Le=(0,D.useCallback)(async()=>{if(w){V(null);try{let e={};for(let[t,n]of Object.entries(ue))n!==``&&(e[t]=n);await w(e),le(!1),de({})}catch(e){V(e.message)}}},[w,ue]),q=(0,D.useMemo)(()=>{if(!e||!R)return e?.rows??[];let t=R.toLowerCase();return e.rows.filter(n=>e.columns.some(e=>String(n[e]??``).toLowerCase().includes(t)))},[e,R]),Re=(0,D.useRef)(null);(0,D.useEffect)(()=>{let t=Re.current;if(!t)return;let n=t=>{if(t.key===`Escape`){W(!1);return}let n=t.target?.tagName;if(!(n===`INPUT`||n===`TEXTAREA`)){if(t.key===`/`){t.preventDefault(),W(!0);return}if(!(!(t.metaKey||t.ctrlKey)||!e)&&(t.key===`a`&&(t.preventDefault(),B(new Set(e.rows.map((e,t)=>t)))),t.key===`c`&&z.size>0)){t.preventDefault();let n=e.columns,r=n.join(` `),i=Array.from(z).sort((e,t)=>e-t).map(t=>n.map(n=>{let r=e.rows[t]?.[n];return r==null?``:typeof r==`object`?JSON.stringify(r):String(r)}).join(` `));navigator.clipboard.writeText([r,...i].join(`
2
- `))}}};return t.addEventListener(`keydown`,n),()=>t.removeEventListener(`keydown`,n)},[e,z]);let J=(0,D.useMemo)(()=>{if(!e)return[];let t=e.columns.filter(e=>U.has(e)),n=e.columns.filter(e=>!U.has(e));return[...t,...n]},[e?.columns,U]),ze=(0,D.useRef)(null),[Be,Ve]=(0,D.useState)(0),[Y,He]=(0,D.useState)(new Map);(0,D.useEffect)(()=>{let e=ze.current;if(!e)return;let t=()=>{Ve(e.offsetHeight);let t=new Map;e.querySelectorAll(`th[data-col]`).forEach(e=>{t.set(e.dataset.col,e.offsetWidth)});let n=e.querySelector(`th[data-col='_cb']`);n&&t.set(`_cb`,n.offsetWidth),He(t)};t();let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[e?.columns,U]);let X=(0,D.useMemo)(()=>{let e=new Map,t=Y.get(`_cb`)??(G?40:0);for(let n of J){if(!U.has(n))break;e.set(n,t),t+=Y.get(n)??100}return e},[J,U,G,Y]),Z=(0,D.useMemo)(()=>Array.from(_e).sort((e,t)=>e-t).map(e=>({idx:e,row:q[e]})).filter(e=>e.row),[_e,q]),Q=(0,D.useRef)(new Map),[$,Ue]=(0,D.useState)(new Map),We=(0,D.useCallback)((e,t)=>{t?Q.current.set(e,t):Q.current.delete(e)},[]);(0,D.useEffect)(()=>{if(Z.length===0){$.size>0&&Ue(new Map);return}let e=requestAnimationFrame(()=>{let e=new Map;for(let{idx:t}of Z){let n=Q.current.get(t);n&&e.set(t,n.offsetHeight)}Ue(e)});return()=>cancelAnimationFrame(e)},[Z,e]);let Ge=(0,D.useMemo)(()=>{let e=new Map,t=Be;for(let{idx:n}of Z)e.set(n,t),t+=$.get(n)??28;return e},[Be,Z,$]),Ke=(0,D.useCallback)(e=>{let t=Se.current,n=t?.querySelector(`th[data-col="${e}"]`);if(!t||!n)return;let r=0,i=t.querySelector(`th[data-col="_cb"]`);i&&(r+=i.offsetWidth);for(let[t,n]of X)t!==e&&(r=Math.max(r,n+(Y.get(t)??0)));let a=n.offsetLeft-r;t.scrollTo({left:a,behavior:`smooth`}),W(!1)},[X,Y]);if(!e)return(0,k.jsx)(`div`,{className:`flex items-center justify-center h-full text-xs text-muted-foreground`,children:n?(0,k.jsx)(v,{className:`size-4 animate-spin`}):`Select a table`});let qe=Math.ceil(e.total/e.limit)||1,Je=z.size>0,Ye=z.size===e.rows.length&&e.rows.length>0;return(0,k.jsxs)(`div`,{ref:Re,tabIndex:0,className:`flex flex-col h-full overflow-hidden outline-none`,children:[(0,k.jsxs)(`div`,{className:`flex items-center gap-2 px-2 py-1 border-b border-border bg-background shrink-0`,children:[(0,k.jsxs)(`div`,{className:`flex items-center gap-1 flex-1`,children:[(0,k.jsx)(g,{className:`size-3 text-muted-foreground`}),(0,k.jsx)(`input`,{type:`text`,value:R,onChange:e=>se(e.target.value),placeholder:`Search current page…`,className:`flex-1 text-xs bg-transparent outline-none text-foreground placeholder:text-muted-foreground`}),R&&(0,k.jsx)(`button`,{type:`button`,onClick:()=>se(``),className:`text-muted-foreground hover:text-foreground`,children:(0,k.jsx)(d,{className:`size-3`})})]}),(0,k.jsxs)(`div`,{className:`relative`,children:[(0,k.jsx)(`button`,{type:`button`,onClick:()=>W(!xe),className:`p-0.5 rounded transition-colors ${xe?`text-primary`:`text-muted-foreground hover:text-foreground`}`,title:`Jump to column ( / )`,children:(0,k.jsx)(T,{className:`size-3.5`})}),xe&&(0,k.jsx)(ae,{columns:e.columns,onSelect:Ke,onClose:()=>W(!1)})]}),Je&&(0,k.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs`,children:[(0,k.jsxs)(`span`,{className:`text-muted-foreground`,children:[z.size,` selected`]}),C&&G&&(pe?(0,k.jsxs)(`span`,{className:`flex items-center gap-1`,children:[(0,k.jsxs)(`button`,{type:`button`,onClick:Ie,className:`text-destructive text-[10px] font-medium hover:underline`,children:[`Delete `,z.size,`?`]}),(0,k.jsx)(`button`,{type:`button`,onClick:()=>me(!1),className:`text-muted-foreground text-[10px] hover:underline`,children:`Cancel`})]}):(0,k.jsx)(`button`,{type:`button`,onClick:()=>me(!0),className:`p-0.5 text-muted-foreground hover:text-destructive`,children:(0,k.jsx)(p,{className:`size-3`})})),(0,k.jsx)(re,{columns:e.columns,rows:Array.from(z).map(t=>e.rows[t]).filter(Boolean),filename:`${ne??`db`}-selected`})]}),w&&(0,k.jsx)(`button`,{type:`button`,onClick:()=>{le(!0),de({}),V(null)},className:`p-0.5 rounded text-muted-foreground hover:text-primary transition-colors`,title:`Insert row`,children:(0,k.jsx)(o,{className:`size-3.5`})})]}),ce&&(0,k.jsxs)(`div`,{className:`px-2 py-1.5 border-b border-border bg-muted/30 text-xs space-y-1`,children:[(0,k.jsx)(`div`,{className:`flex flex-wrap gap-1.5`,children:t.filter(e=>!e.pk).map(e=>(0,k.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,k.jsx)(`label`,{className:`text-muted-foreground text-[10px] w-16 truncate`,title:e.name,children:e.name}),(0,k.jsx)(`input`,{value:ue[e.name]??``,onChange:t=>de(n=>({...n,[e.name]:t.target.value})),placeholder:e.defaultValue??(e.nullable?`NULL`:``),className:`h-5 w-24 text-[11px] px-1 rounded border border-border bg-background outline-none focus:border-primary`})]},e.name))}),(0,k.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,k.jsx)(`button`,{type:`button`,onClick:Le,className:`text-[10px] px-2 py-0.5 rounded bg-primary text-primary-foreground hover:bg-primary/90`,children:`Save`}),(0,k.jsx)(`button`,{type:`button`,onClick:()=>le(!1),className:`text-[10px] text-muted-foreground hover:underline`,children:`Cancel`}),fe&&(0,k.jsx)(`span`,{className:`text-[10px] text-destructive`,children:fe})]})]}),(0,k.jsxs)(`div`,{ref:Se,className:`flex-1 overflow-auto relative`,children:[n&&(0,k.jsx)(`div`,{className:`absolute inset-0 z-30 flex items-center justify-center bg-background/60`,children:(0,k.jsx)(v,{className:`size-5 animate-spin text-primary`})}),(0,k.jsxs)(`table`,{className:`w-full text-xs`,style:{borderCollapse:`separate`,borderSpacing:0},children:[(0,k.jsx)(`thead`,{ref:ze,className:`sticky top-0 z-20 bg-muted`,children:(0,k.jsxs)(`tr`,{children:[G&&(0,k.jsx)(`th`,{"data-col":`_cb`,className:`px-2 py-1.5 text-left font-medium text-muted-foreground border-b border-border w-10 bg-muted`,style:{position:`sticky`,left:0,zIndex:30},children:(0,k.jsx)(`input`,{type:`checkbox`,checked:Ye,onChange:Fe,className:`size-3 accent-primary`})}),J.map(e=>{let n=t.find(t=>t.name===e)?.pk,r=f===e,o=U.has(e),s=!!A[e],c=ye===e,l=X.get(e);return(0,k.jsxs)(`th`,{"data-col":e,className:`group/th px-2 py-1.5 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap bg-muted ${o?`border-r border-r-primary/20`:``}`,style:l==null?void 0:{position:`sticky`,left:l,zIndex:25},children:[(0,k.jsxs)(`div`,{className:`flex items-center gap-0.5`,children:[(0,k.jsxs)(`button`,{type:`button`,onClick:()=>b(e),className:`flex items-center gap-0.5 ${n?`font-bold`:``} hover:text-foreground transition-colors`,children:[e,r&&m===`ASC`&&(0,k.jsx)(i,{className:`size-3`}),r&&m===`DESC`&&(0,k.jsx)(a,{className:`size-3`})]}),j&&(0,k.jsx)(`button`,{type:`button`,title:`Filter column`,onClick:()=>be(c?null:e),className:`p-0.5 rounded transition-colors ${s||c?`text-primary`:`text-muted-foreground/40 md:opacity-0 md:group-hover/th:opacity-100 hover:text-foreground`}`,children:(0,k.jsx)(E,{className:`size-2.5`})}),(0,k.jsx)(`button`,{type:`button`,title:o?`Unpin column`:`Pin column`,onClick:()=>je(e),className:`p-0.5 rounded transition-colors ${o?`text-primary`:`text-muted-foreground/40 md:opacity-0 md:group-hover/th:opacity-100 hover:text-foreground`}`,children:o?(0,k.jsx)(S,{className:`size-2.5`}):(0,k.jsx)(y,{className:`size-2.5`})})]}),c&&(0,k.jsxs)(`div`,{className:`mt-1 flex items-center gap-1`,children:[(0,k.jsx)(`input`,{autoFocus:!0,type:`text`,value:A[e]??``,placeholder:`ILIKE filter…`,onChange:t=>Ne(e,t.target.value),onKeyDown:e=>{e.key===`Escape`&&be(null)},className:`w-full h-5 text-[10px] px-1 rounded border border-border bg-background outline-none focus:border-primary`}),s&&(0,k.jsx)(`button`,{type:`button`,onClick:()=>Ne(e,``),className:`text-muted-foreground hover:text-foreground`,children:(0,k.jsx)(d,{className:`size-2.5`})})]})]},e)}),u&&G&&(0,k.jsx)(`th`,{className:`px-2 py-1.5 border-b border-border w-14 bg-muted`})]})}),(0,k.jsxs)(`tbody`,{children:[Z.map(({idx:e,row:t})=>(0,k.jsx)(oe,{row:t,rowIdx:e,columns:J,selected:z.has(e),onToggleSelect:Pe,pkCol:G,editingCell:M,editValue:P,onStartEdit:De,onCommitEdit:Oe,onCancelEdit:ke,onSetEditValue:F,showDelete:!!u,confirmingDelete:I===e,onDelete:Ae,onConfirmDelete:L,onViewCell:he,onViewRow:Ce,pinned:!0,onTogglePin:Me,pinnedCols:U,pinnedColOffsets:X,stickyTop:Ge.get(e)??Be,trRef:t=>We(e,t)},`pin-${e}`)),q.map((e,t)=>_e.has(t)?null:(0,k.jsx)(oe,{row:e,rowIdx:t,columns:J,selected:z.has(t),onToggleSelect:Pe,pkCol:G,editingCell:M,editValue:P,onStartEdit:De,onCommitEdit:Oe,onCancelEdit:ke,onSetEditValue:F,showDelete:!!u,confirmingDelete:I===t,onDelete:Ae,onConfirmDelete:L,onViewCell:he,onViewRow:Ce,pinned:!1,onTogglePin:Me,pinnedCols:U,pinnedColOffsets:X},t)),q.length===0&&(0,k.jsx)(`tr`,{children:(0,k.jsx)(`td`,{colSpan:J.length+2,className:`px-2 py-8 text-center text-muted-foreground`,children:`No data`})})]})]})]}),(0,k.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-1.5 border-t border-border bg-background shrink-0 text-xs text-muted-foreground`,children:[(0,k.jsxs)(`span`,{children:[e.total.toLocaleString(),` rows`]}),(0,k.jsxs)(`div`,{className:`hidden md:flex items-center gap-2 text-[10px] text-muted-foreground/50`,children:[(0,k.jsxs)(`span`,{children:[(0,k.jsx)(`kbd`,{className:`px-1 py-0.5 rounded bg-muted text-[9px]`,children:`/`}),` columns`]}),(0,k.jsxs)(`span`,{children:[(0,k.jsxs)(`kbd`,{className:`px-1 py-0.5 rounded bg-muted text-[9px]`,children:[`⌘`,`A`]}),` select all`]}),(0,k.jsxs)(`span`,{children:[(0,k.jsxs)(`kbd`,{className:`px-1 py-0.5 rounded bg-muted text-[9px]`,children:[`⌘`,`C`]}),` copy`]})]}),(0,k.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,k.jsx)(`button`,{type:`button`,disabled:r<=1,onClick:()=>c(r-1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,k.jsx)(_,{className:`size-3.5`})}),(0,k.jsxs)(`span`,{children:[r,` / `,qe]}),(0,k.jsx)(`button`,{type:`button`,disabled:r>=qe,onClick:()=>c(r+1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,k.jsx)(s,{className:`size-3.5`})})]})]})]})}function M(e){return e==null?`NULL`:typeof e==`object`?JSON.stringify(e):String(e)}var N=200;function P(e){if(e==null)return!1;if(typeof e==`object`)return!0;let t=String(e);if(t.length>=N)return!0;let n=t.trimStart();return!!((n[0]===`{`||n[0]===`[`)&&(n.endsWith(`}`)||n.endsWith(`]`))||n.startsWith(`<?xml`)||n.startsWith(`<`)&&n.endsWith(`>`))}function ie(e){let t=e.trimStart();if(t[0]===`{`||t[0]===`[`)try{return JSON.parse(t),`json`}catch{}return t.startsWith(`<?xml`)||t.startsWith(`<`)&&/<\/\w+>/.test(t)?`xml`:t.startsWith(`---`)||/^\w+:\s/m.test(t)?`yaml`:`plaintext`}function ae({columns:e,onSelect:t,onClose:n}){let[r,i]=(0,D.useState)(``),[a,o]=(0,D.useState)(0),s=(0,D.useMemo)(()=>{if(!r)return e;let t=r.toLowerCase();return e.filter(e=>e.toLowerCase().includes(t))},[e,r]),c=(0,D.useRef)(null);return(0,D.useEffect)(()=>{c.current?.scrollIntoView({block:`nearest`})},[a]),(0,k.jsxs)(`div`,{className:`absolute top-full right-0 mt-1 z-50 w-52 max-h-56 bg-popover border border-border rounded-md shadow-lg overflow-hidden flex flex-col`,children:[(0,k.jsx)(`input`,{autoFocus:!0,type:`text`,value:r,onChange:e=>{i(e.target.value),o(0)},onKeyDown:e=>{e.key===`Escape`?n():e.key===`ArrowDown`?(e.preventDefault(),o(e=>Math.min(e+1,s.length-1))):e.key===`ArrowUp`?(e.preventDefault(),o(e=>Math.max(e-1,0))):e.key===`Enter`&&s[a]&&t(s[a])},placeholder:`Search columns…`,className:`px-2 py-1.5 text-xs bg-transparent outline-none border-b border-border text-foreground placeholder:text-muted-foreground`}),(0,k.jsx)(`div`,{className:`overflow-auto flex-1`,children:s.map((e,n)=>(0,k.jsx)(`button`,{type:`button`,onClick:()=>t(e),ref:n===a?c:void 0,className:`w-full text-left px-2 py-1 text-xs truncate ${n===a?`bg-primary/10 text-primary`:`text-foreground hover:bg-muted`}`,children:e},e))})]})}var oe=(0,D.memo)(function({row:e,rowIdx:t,columns:n,selected:r,onToggleSelect:i,pkCol:a,editingCell:o,editValue:s,onStartEdit:c,onCommitEdit:u,onCancelEdit:d,onSetEditValue:f,showDelete:m,confirmingDelete:h,onDelete:g,onConfirmDelete:_,onViewCell:v,onViewRow:b,pinned:x,onTogglePin:C,pinnedCols:w,pinnedColOffsets:T,stickyTop:E,trRef:D}){let O=x?`bg-muted`:r?`bg-primary/5`:`bg-background`;return(0,k.jsxs)(`tr`,{ref:D,className:`group ${x?``:`hover:bg-muted/30`}`,style:x?{position:`sticky`,top:E,zIndex:15}:void 0,children:[a&&(0,k.jsx)(`td`,{className:`px-2 py-1 border-b border-border/50 ${O}`,style:{position:`sticky`,left:0,zIndex:12},children:(0,k.jsxs)(`span`,{className:`flex items-center gap-0.5`,children:[(0,k.jsx)(`input`,{type:`checkbox`,checked:r,onChange:()=>i(t),className:`size-3 accent-primary`}),(0,k.jsx)(`button`,{type:`button`,title:`View row as JSON`,onClick:()=>b(e),className:`p-0.5 rounded transition-colors text-muted-foreground/30 md:opacity-0 md:group-hover:opacity-100 hover:text-foreground`,children:(0,k.jsx)(l,{className:`size-2.5`})}),(0,k.jsx)(`button`,{type:`button`,title:x?`Unpin row`:`Pin row`,onClick:()=>C(t),className:`p-0.5 rounded transition-colors ${x?`text-primary`:`text-muted-foreground/30 md:opacity-0 md:group-hover:opacity-100 hover:text-foreground`}`,children:x?(0,k.jsx)(S,{className:`size-2.5`}):(0,k.jsx)(y,{className:`size-2.5`})})]})}),n.map(n=>{let r=o?.rowIdx===t&&o?.col===n,i=e[n],p=!r&&P(i),m=w.has(n),h=T.get(n);return(0,k.jsx)(`td`,{className:`px-2 py-1 max-w-[300px] border-b border-border/50 ${m?`border-r border-r-primary/20`:``} ${m||x?O:``}`,style:h==null?void 0:{position:`sticky`,left:h,zIndex:10},children:r?(0,k.jsx)(`input`,{autoFocus:!0,className:`w-full bg-transparent border border-primary/50 rounded px-1 py-0 text-xs outline-none`,value:s,onChange:e=>f(e.target.value),onBlur:u,onKeyDown:e=>{e.key===`Enter`&&u(),e.key===`Escape`&&d()}}):(0,k.jsxs)(`span`,{className:`flex items-center gap-0.5`,children:[(0,k.jsx)(`span`,{className:`cursor-pointer truncate flex-1 ${i==null?`text-muted-foreground/40 italic`:``}`,onDoubleClick:()=>a&&c(t,n,i),title:M(i),children:M(i)}),p&&(0,k.jsx)(`button`,{type:`button`,title:`View full content`,onClick:()=>v({col:n,value:M(i)}),className:`shrink-0 p-0.5 rounded text-muted-foreground/50 hover:text-foreground transition-colors`,children:(0,k.jsx)(l,{className:`size-3`})})]})},n)}),m&&a&&(0,k.jsx)(`td`,{className:`px-2 py-1 border-b border-border/50 ${x?O:``}`,children:h?(0,k.jsxs)(`span`,{className:`flex items-center gap-1 whitespace-nowrap`,children:[(0,k.jsx)(`button`,{type:`button`,onClick:()=>g(t),className:`text-destructive text-[10px] font-medium hover:underline`,children:`Confirm`}),(0,k.jsx)(`button`,{type:`button`,onClick:()=>_(null),className:`text-muted-foreground text-[10px] hover:underline`,children:`Cancel`})]}):(0,k.jsx)(`button`,{type:`button`,onClick:()=>_(t),className:`p-0.5 rounded md:opacity-0 md:group-hover:opacity-100 hover:bg-destructive/10 hover:text-destructive transition-opacity`,title:`Delete row`,children:(0,k.jsx)(p,{className:`size-3`})})})]})});function F(e){let t={},n=/"(\w+)"\s+ILIKE\s+'%([^']*?)%'/gi,r;for(;(r=n.exec(e))!==null;)t[r[1]]=r[2].replace(/''/g,`'`);return t}function I({metadata:e}){let t=e?.connectionId,n=e?.connectionName,r=e?.tableName,i=e?.schemaName??`public`,a=e?.initialSql,o=ne(t),[s,l]=(0,D.useState)([]),[u,d]=(0,D.useState)(180),p=(0,D.useRef)(null),[h,g]=(0,D.useState)({}),_=(0,D.useMemo)(()=>{if(a&&!o.selectedTable)return a;if(o.selectedTable){let e=`SELECT * FROM "${o.selectedTable}"`,t=Object.entries(h).filter(([,e])=>e.trim()).map(([e,t])=>`"${e}" ILIKE '%${t.replace(/'/g,`''`)}%'`);t.length>0&&(e+=` WHERE ${t.join(` AND `)}`),o.orderBy&&(e+=` ORDER BY "${o.orderBy}" ${o.orderDir}`);let n=(o.page-1)*100;return e+=` LIMIT 100`,n>0&&(e+=` OFFSET ${n}`),e}return`SELECT * FROM `},[a,o.selectedTable,o.orderBy,o.orderDir,o.page,h]),v=(0,D.useCallback)(e=>{g(e)},[]),y=(0,D.useRef)(void 0);(0,D.useEffect)(()=>{if(!(!o.selectedTable||Object.keys(h).length===0))return clearTimeout(y.current),y.current=setTimeout(()=>{o.queryAsTable(_)},500),()=>clearTimeout(y.current)},[h]);let x=(0,D.useCallback)(e=>{e.preventDefault();let t=e.clientY,n=u,r=e=>{let r=e.clientY-t;d(Math.max(80,Math.min(n+r,(p.current?.clientHeight??600)-100)))},i=()=>{document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[u]);(0,D.useEffect)(()=>{m.get(`/api/db/connections/${t}/tables?cached=1`).then(e=>l(e.map(e=>({name:e.name,schema:e.schema})))).catch(()=>{})},[t]);let S=(0,D.useMemo)(()=>{if(s.length!==0)return{tables:s,getColumns:async(e,n)=>await m.get(`/api/db/connections/${t}/schema?table=${encodeURIComponent(e)}${n?`&schema=${encodeURIComponent(n)}`:``}`)}},[t,s]),w=(0,D.useRef)(!1);(0,D.useEffect)(()=>{w.current||(w.current=!0,a?o.executeQuery(a):r&&o.selectTable(r,i))},[r,i,a]);let[T,E]=(0,D.useState)(!!a),O=(0,D.useCallback)(e=>{let t=e.trim();if(o.selectedTable&&RegExp(`^SELECT\\s+\\*\\s+FROM\\s+"?${o.selectedTable.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"?\\b`,`i`).test(t)){g(F(t)),o.queryAsTable(t);return}E(!0),o.executeQuery(e)},[o.executeQuery,o.queryAsTable,o.selectedTable]),ee=(0,D.useCallback)(e=>{E(!1),o.toggleSort(e)},[o.toggleSort]),te=(0,D.useCallback)(e=>{E(!1),o.setPage(e)},[o.setPage]),A=o.queryResult,M=T&&!!(A||o.queryError),N=o.selectedTable&&!M;return(0,k.jsx)(`div`,{ref:p,className:`flex h-full w-full overflow-hidden`,children:(0,k.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden`,children:[(0,k.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,k.jsx)(c,{className:`size-3.5 text-muted-foreground`}),(0,k.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:n??`Database`}),o.selectedTable&&(0,k.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,o.selectedTable]}),(0,k.jsxs)(`div`,{className:`ml-auto flex items-center gap-1`,children:[o.tableData&&(0,k.jsx)(re,{columns:o.tableData.columns,rows:o.tableData.rows,filename:n?`${n}-${o.selectedTable??`data`}`:o.selectedTable??`data`,exportAllUrl:o.selectedTable?`/api/db/connections/${t}/export?table=${encodeURIComponent(o.selectedTable)}&schema=${o.selectedSchema}`:void 0}),(0,k.jsx)(`button`,{type:`button`,onClick:()=>o.refreshData(),title:`Reload data`,className:`p-1 rounded text-muted-foreground hover:text-foreground transition-colors`,children:(0,k.jsx)(f,{className:`size-3 ${o.loading?`animate-spin`:``}`})})]})]}),(0,k.jsx)(`div`,{className:`shrink-0 border-b border-border`,style:{height:u},children:(0,k.jsx)(C,{onExecute:O,loading:o.queryLoading,defaultValue:_,schemaInfo:S})}),(0,k.jsx)(`div`,{onMouseDown:x,className:`shrink-0 h-1.5 cursor-row-resize bg-border/50 hover:bg-primary/30 flex items-center justify-center transition-colors`,children:(0,k.jsx)(b,{className:`size-3 text-muted-foreground/50`})}),(0,k.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[N&&(0,k.jsx)(j,{tableData:o.tableData,schema:o.schema,loading:o.loading,page:o.page,onPageChange:te,onCellUpdate:o.updateCell,onRowDelete:o.deleteRow,orderBy:o.orderBy,orderDir:o.orderDir,onToggleSort:ee,onBulkDelete:o.bulkDelete,onInsertRow:o.insertRow,connectionId:t,selectedTable:o.selectedTable,selectedSchema:o.selectedSchema,connectionName:n,columnFilters:h,onColumnFilter:v}),M&&(0,k.jsx)(R,{result:A,error:o.queryError,loading:o.queryLoading,schema:o.schema,connectionName:n})]})]})})}var L=()=>{};function R({result:e,error:t,loading:n,schema:r,connectionName:i}){let a=(0,D.useMemo)(()=>e?.changeType===`select`&&e.rows.length>0?{columns:e.columns,rows:e.rows,total:e.rows.length,limit:e.rows.length}:null,[e]),o=(0,D.useMemo)(()=>r?.length?r:(e?.columns??[]).map(e=>({name:e,type:`text`,nullable:!0,pk:!1,defaultValue:null})),[r,e?.columns]);return(0,k.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden text-xs`,children:[t&&(0,k.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5 shrink-0`,children:t}),e?.changeType===`modify`&&(0,k.jsxs)(`div`,{className:`px-3 py-2 text-green-500 shrink-0`,children:[e.rowsAffected,` row(s) affected`,e.executionTimeMs!=null&&(0,k.jsxs)(`span`,{className:`text-muted-foreground ml-2`,children:[e.executionTimeMs,`ms`]})]}),a&&(0,k.jsxs)(`div`,{className:`flex-1 overflow-hidden`,children:[(0,k.jsx)(j,{tableData:a,schema:o,loading:!!n,page:1,onPageChange:L,onCellUpdate:L,orderBy:null,orderDir:`ASC`,onToggleSort:L,connectionName:i}),e?.executionTimeMs!=null&&(0,k.jsxs)(`div`,{className:`px-3 py-0.5 border-t border-border text-[10px] text-muted-foreground shrink-0`,children:[e.rows.length,` rows · `,e.executionTimeMs,`ms`]})]}),e?.changeType===`select`&&e.rows.length===0&&(0,k.jsxs)(`div`,{className:`px-3 py-2 text-muted-foreground shrink-0`,children:[`No results`,e.executionTimeMs!=null&&(0,k.jsxs)(`span`,{className:`ml-2 text-muted-foreground/60`,children:[e.executionTimeMs,`ms`]})]}),!e&&!t&&(0,k.jsx)(`div`,{className:`flex items-center justify-center h-full text-muted-foreground`,children:n?(0,k.jsx)(v,{className:`size-4 animate-spin`}):`Run a query to see results`})]})}export{I as DatabaseViewer};