simplemdg-dev-cli 2.5.0 → 2.7.0
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/README.md +33 -0
- package/USER_GUIDE.md +58 -1
- package/dist/commands/cache.command.d.ts +2 -0
- package/dist/commands/cache.command.js +129 -0
- package/dist/commands/cache.command.js.map +1 -0
- package/dist/commands/cf.command.js +201 -122
- package/dist/commands/cf.command.js.map +1 -1
- package/dist/commands/gitlab.command.js +33 -23
- package/dist/commands/gitlab.command.js.map +1 -1
- package/dist/core/cache/smart-cache-events.d.ts +3 -0
- package/dist/core/cache/smart-cache-events.js +20 -0
- package/dist/core/cache/smart-cache-events.js.map +1 -0
- package/dist/core/cache/smart-cache-manager.d.ts +20 -0
- package/dist/core/cache/smart-cache-manager.js +148 -0
- package/dist/core/cache/smart-cache-manager.js.map +1 -0
- package/dist/core/cache/smart-cache-store.d.ts +8 -0
- package/dist/core/cache/smart-cache-store.js +74 -0
- package/dist/core/cache/smart-cache-store.js.map +1 -0
- package/dist/core/cache/smart-cache.d.ts +18 -0
- package/dist/core/cache/smart-cache.js +117 -0
- package/dist/core/cache/smart-cache.js.map +1 -0
- package/dist/core/cache/smart-cache.types.d.ts +62 -0
- package/dist/core/cache/smart-cache.types.js +17 -0
- package/dist/core/cache/smart-cache.types.js.map +1 -0
- package/dist/core/cf/cf-target-cache.d.ts +7 -0
- package/dist/core/cf/cf-target-cache.js +58 -0
- package/dist/core/cf/cf-target-cache.js.map +1 -0
- package/dist/core/cf/cf-target.types.d.ts +11 -0
- package/dist/core/cf/cf-target.types.js +11 -0
- package/dist/core/cf/cf-target.types.js.map +1 -0
- package/dist/core/db/db-studio-client.d.ts +1 -1
- package/dist/core/db/db-studio-client.js +250 -55
- package/dist/core/db/db-studio-client.js.map +1 -1
- package/dist/core/db/db-studio-server.js +171 -0
- package/dist/core/db/db-studio-server.js.map +1 -1
- package/dist/core/db/db-studio-styles.d.ts +1 -1
- package/dist/core/db/db-studio-styles.js +63 -0
- package/dist/core/db/db-studio-styles.js.map +1 -1
- package/dist/core/db/db-types.d.ts +54 -0
- package/dist/core/db/studio/sql-formatter.d.ts +25 -0
- package/dist/core/db/studio/sql-formatter.js +139 -0
- package/dist/core/db/studio/sql-formatter.js.map +1 -0
- package/dist/core/db/studio/studio-settings.d.ts +4 -0
- package/dist/core/db/studio/studio-settings.js +39 -0
- package/dist/core/db/studio/studio-settings.js.map +1 -0
- package/dist/core/db/studio/workspace-cache.d.ts +3 -0
- package/dist/core/db/studio/workspace-cache.js +51 -0
- package/dist/core/db/studio/workspace-cache.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/cache.command.ts +159 -0
- package/src/commands/cf.command.ts +232 -129
- package/src/commands/gitlab.command.ts +37 -21
- package/src/core/cache/smart-cache-events.ts +20 -0
- package/src/core/cache/smart-cache-manager.ts +169 -0
- package/src/core/cache/smart-cache-store.ts +83 -0
- package/src/core/cache/smart-cache.ts +97 -0
- package/src/core/cache/smart-cache.types.ts +79 -0
- package/src/core/cf/cf-target-cache.ts +61 -0
- package/src/core/cf/cf-target.types.ts +17 -0
- package/src/core/db/db-studio-client.ts +250 -55
- package/src/core/db/db-studio-server.ts +156 -1
- package/src/core/db/db-studio-styles.ts +63 -0
- package/src/core/db/db-types.ts +61 -0
- package/src/core/db/studio/sql-formatter.ts +139 -0
- package/src/core/db/studio/studio-settings.ts +36 -0
- package/src/core/db/studio/workspace-cache.ts +51 -0
- package/src/index.ts +3 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listFavoriteTargets = listFavoriteTargets;
|
|
4
|
+
exports.isFavoriteTarget = isFavoriteTarget;
|
|
5
|
+
exports.addFavoriteTarget = addFavoriteTarget;
|
|
6
|
+
exports.removeFavoriteTarget = removeFavoriteTarget;
|
|
7
|
+
exports.listRecentTargets = listRecentTargets;
|
|
8
|
+
exports.addRecentTarget = addRecentTarget;
|
|
9
|
+
const smart_cache_store_1 = require("../cache/smart-cache-store");
|
|
10
|
+
const cf_target_types_1 = require("./cf-target.types");
|
|
11
|
+
const FAVORITES_NAMESPACE = "cf-favorite-targets";
|
|
12
|
+
const RECENT_NAMESPACE = "cf-recent-targets";
|
|
13
|
+
const MAX_RECENT = 20;
|
|
14
|
+
function toEntry(target) {
|
|
15
|
+
const now = new Date().toISOString();
|
|
16
|
+
return {
|
|
17
|
+
key: (0, cf_target_types_1.cfTargetKey)(target),
|
|
18
|
+
data: target,
|
|
19
|
+
createdAt: now,
|
|
20
|
+
updatedAt: target.lastUsedAt ?? now,
|
|
21
|
+
source: "cache",
|
|
22
|
+
status: "fresh",
|
|
23
|
+
ttlMs: Number.POSITIVE_INFINITY,
|
|
24
|
+
version: 1,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function listFavoriteTargets() {
|
|
28
|
+
const entries = await (0, smart_cache_store_1.readAllEntries)(FAVORITES_NAMESPACE);
|
|
29
|
+
return Object.values(entries).map((entry) => ({ ...entry.data, isFavorite: true }));
|
|
30
|
+
}
|
|
31
|
+
async function isFavoriteTarget(target) {
|
|
32
|
+
return Boolean(await (0, smart_cache_store_1.readEntry)(FAVORITES_NAMESPACE, (0, cf_target_types_1.cfTargetKey)(target)));
|
|
33
|
+
}
|
|
34
|
+
async function addFavoriteTarget(target) {
|
|
35
|
+
await (0, smart_cache_store_1.writeEntry)(FAVORITES_NAMESPACE, (0, cf_target_types_1.cfTargetKey)(target), toEntry({ ...target, isFavorite: true }));
|
|
36
|
+
}
|
|
37
|
+
async function removeFavoriteTarget(target) {
|
|
38
|
+
await (0, smart_cache_store_1.removeEntry)(FAVORITES_NAMESPACE, (0, cf_target_types_1.cfTargetKey)(target));
|
|
39
|
+
}
|
|
40
|
+
async function listRecentTargets(limit = 10) {
|
|
41
|
+
const entries = await (0, smart_cache_store_1.readAllEntries)(RECENT_NAMESPACE);
|
|
42
|
+
return Object.values(entries)
|
|
43
|
+
.map((entry) => entry.data)
|
|
44
|
+
.sort((left, right) => String(right.lastUsedAt ?? "").localeCompare(String(left.lastUsedAt ?? "")))
|
|
45
|
+
.slice(0, limit);
|
|
46
|
+
}
|
|
47
|
+
async function addRecentTarget(target) {
|
|
48
|
+
const recent = { ...target, lastUsedAt: new Date().toISOString() };
|
|
49
|
+
await (0, smart_cache_store_1.writeEntry)(RECENT_NAMESPACE, (0, cf_target_types_1.cfTargetKey)(recent), toEntry(recent));
|
|
50
|
+
const entries = await (0, smart_cache_store_1.readAllEntries)(RECENT_NAMESPACE);
|
|
51
|
+
const sorted = Object.values(entries)
|
|
52
|
+
.map((entry) => entry.data)
|
|
53
|
+
.sort((left, right) => String(right.lastUsedAt ?? "").localeCompare(String(left.lastUsedAt ?? "")));
|
|
54
|
+
for (const stale of sorted.slice(MAX_RECENT)) {
|
|
55
|
+
await (0, smart_cache_store_1.removeEntry)(RECENT_NAMESPACE, (0, cf_target_types_1.cfTargetKey)(stale)).catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=cf-target-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cf-target-cache.js","sourceRoot":"","sources":["../../../src/core/cf/cf-target-cache.ts"],"names":[],"mappings":";;AAuBA,kDAGC;AAED,4CAEC;AAED,8CAEC;AAED,oDAEC;AAED,8CAMC;AAED,0CAYC;AA5DD,kEAAgG;AAChG,uDAAgD;AAIhD,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAClD,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAC7C,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,SAAS,OAAO,CAAC,MAAiB;IAChC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO;QACL,GAAG,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG;QACnC,MAAM,EAAE,OAAO;QACf,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,MAAM,CAAC,iBAAiB;QAC/B,OAAO,EAAE,CAAC;KACX,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,mBAAmB;IACvC,MAAM,OAAO,GAAG,MAAM,IAAA,kCAAc,EAAY,mBAAmB,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,MAAiB;IACtD,OAAO,OAAO,CAAC,MAAM,IAAA,6BAAS,EAAY,mBAAmB,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC;AAEM,KAAK,UAAU,iBAAiB,CAAC,MAAiB;IACvD,MAAM,IAAA,8BAAU,EAAC,mBAAmB,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACvG,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAC,MAAiB;IAC1D,MAAM,IAAA,+BAAW,EAAC,mBAAmB,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC;AAEM,KAAK,UAAU,iBAAiB,CAAC,KAAK,GAAG,EAAE;IAChD,MAAM,OAAO,GAAG,MAAM,IAAA,kCAAc,EAAY,gBAAgB,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;SAC1B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC;SAClG,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrB,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,MAAiB;IACrD,MAAM,MAAM,GAAc,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAC9E,MAAM,IAAA,8BAAU,EAAC,gBAAgB,EAAE,IAAA,6BAAW,EAAC,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzE,MAAM,OAAO,GAAG,MAAM,IAAA,kCAAc,EAAY,gBAAgB,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;SAClC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEtG,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAA,+BAAW,EAAC,gBAAgB,EAAE,IAAA,6BAAW,EAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type TCfTarget = {
|
|
2
|
+
region: string;
|
|
3
|
+
apiEndpoint: string;
|
|
4
|
+
org: string;
|
|
5
|
+
space: string;
|
|
6
|
+
isFavorite?: boolean;
|
|
7
|
+
lastUsedAt?: string;
|
|
8
|
+
lastRefreshedAt?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function cfTargetKey(target: Pick<TCfTarget, "region" | "org" | "space">): string;
|
|
11
|
+
export declare function cfTargetLabel(target: Pick<TCfTarget, "region" | "org" | "space">): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cfTargetKey = cfTargetKey;
|
|
4
|
+
exports.cfTargetLabel = cfTargetLabel;
|
|
5
|
+
function cfTargetKey(target) {
|
|
6
|
+
return `${target.region}::${target.org}::${target.space || ""}`;
|
|
7
|
+
}
|
|
8
|
+
function cfTargetLabel(target) {
|
|
9
|
+
return `${target.region} / ${target.org}${target.space ? ` / ${target.space}` : ""}`;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=cf-target.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cf-target.types.js","sourceRoot":"","sources":["../../../src/core/cf/cf-target.types.ts"],"names":[],"mappings":";;AAUA,kCAEC;AAED,sCAEC;AAND,SAAgB,WAAW,CAAC,MAAmD;IAC7E,OAAO,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;AAClE,CAAC;AAED,SAAgB,aAAa,CAAC,MAAmD;IAC/E,OAAO,GAAG,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACvF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const STUDIO_CLIENT_SCRIPT = "\n\"use strict\";\n(function(){\nvar RO_DEFAULT = !!window.SMDG_READONLY_DEFAULT;\nvar ENV_COLORS = { DEV:\"#22c55e\", QAS:\"#f59e0b\", PROD:\"#ef4444\", SANDBOX:\"#6366f1\", CUSTOM:\"#3b82f6\" };\nvar SWATCHES = [\"#3b82f6\",\"#22c55e\",\"#f59e0b\",\"#ef4444\",\"#a855f7\",\"#06b6d4\",\"#ec4899\",\"#84cc16\",\"#64748b\"];\nvar ICONS = {\n db:\"M4 6c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z|M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6|M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3\",\n sch:\"M12 3l9 5-9 5-9-5 9-5z|M3 12l9 5 9-5|M3 16l9 5 9-5\",\n fld:\"M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z\",\n tbl:\"M3 5h18v14H3z|M3 10h18|M9 5v14|M15 5v14\",\n viw:\"M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z|M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z\",\n prc:\"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 2v3|M12 19v3|M2 12h3|M19 12h3|M5 5l2 2|M17 17l2 2|M19 5l-2 2|M7 17l-2 2\",\n fun:\"M6 3h9l4 4v14H6z|M14 3v4h4|M9 12h6|M9 16h6\",\n syn:\"M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1|M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1\",\n idx:\"M14 4l6 6-9 9H5v-6z|M3 21h18\",\n search:\"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z|M21 21l-4.3-4.3\",\n star:\"M12 3l2.9 6 6.1.5-4.6 4 1.4 6-5.8-3.3L6.2 19.5l1.4-6L3 9.5 9.1 9z\",\n refresh:\"M21 12a9 9 0 1 1-3-6.7|M21 4v5h-5\",\n x:\"M6 6l12 12|M18 6L6 18\",\n plus:\"M12 5v14|M5 12h14\",\n imp:\"M12 3v12|M7 10l5 5 5-5|M5 21h14\",\n sql:\"M4 5h16v14H4z|M7 9l3 3-3 3|M13 15h4\",\n run:\"M13 3L4 14h7l-1 7 9-11h-7z\",\n save:\"M5 3h11l3 3v15H5z|M8 3v6h7V3|M8 21v-7h8v7\",\n gear:\"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M19.4 13a7.9 7.9 0 0 0 0-2l2-1.5-2-3.4-2.3 1a8 8 0 0 0-1.7-1l-.4-2.6h-4l-.4 2.6a8 8 0 0 0-1.7 1l-2.3-1-2 3.4L4.6 11a7.9 7.9 0 0 0 0 2l-2 1.5 2 3.4 2.3-1a8 8 0 0 0 1.7 1l.4 2.6h4l.4-2.6a8 8 0 0 0 1.7-1l2.3 1 2-3.4z\",\n home:\"M3 11l9-8 9 8|M5 10v10h14V10\",\n table2:\"M4 4h16v16H4z|M4 9h16|M9 4v16\",\n col:\"M5 4v16|M12 4v16|M19 4v16\"\n};\nfunction svgFor(name){var d=ICONS[name]||\"\";return '<svg class=\"ic\" viewBox=\"0 0 24 24\">'+d.split(\"|\").map(function(p){return '<path d=\"'+p+'\"></path>';}).join(\"\")+'</svg>';}\nfunction icEl(name,cls){var s=document.createElement(\"span\");s.className=\"ticon \"+(cls||\"\");s.innerHTML=svgFor(name);return s;}\n\n/* ---------- dom helpers ---------- */\nfunction $(id){return document.getElementById(id);}\nfunction el(tag,attrs,kids){var n=document.createElement(tag);if(attrs)for(var k in attrs){var v=attrs[k];if(v==null)continue;if(k===\"class\")n.className=v;else if(k===\"text\")n.textContent=v;else if(k===\"html\")n.innerHTML=v;else if(k.slice(0,2)===\"on\"&&typeof v===\"function\")n.addEventListener(k.slice(2),v);else n.setAttribute(k,v);}if(kids!=null){(Array.isArray(kids)?kids:[kids]).forEach(function(c){if(c==null)return;n.appendChild(typeof c===\"string\"||typeof c===\"number\"?document.createTextNode(String(c)):c);});}return n;}\nfunction clear(n){while(n&&n.firstChild)n.removeChild(n.firstChild);return n;}\nfunction esc(v){return String(v==null?\"\":v).replace(/[&<>\"']/g,function(s){return {\"&\":\"&\",\"<\":\"<\",\">\":\">\",\"\\\"\":\""\",\"'\":\"'\"}[s];});}\nfunction debounce(fn,ms){var t;return function(){var a=arguments,c=this;clearTimeout(t);t=setTimeout(function(){fn.apply(c,a);},ms||220);};}\nfunction topSpin(on){$(\"topSpin\").className=on?\"spin\":\"spin hidden\";}\n\n/* ---------- api ---------- */\nfunction api(method,path,body){topSpin(true);var opt={method:method,headers:{}};if(body!==undefined){opt.headers[\"content-type\"]=\"application/json\";opt.body=JSON.stringify(body);}return fetch(path,opt).then(function(r){return r.text().then(function(t){var j;try{j=t?JSON.parse(t):{};}catch(e){j={error:t};}if(!r.ok)throw new Error(j.error||(\"HTTP \"+r.status));return j;});}).finally(function(){topSpin(false);});}\nfunction qstr(o){return Object.keys(o).filter(function(k){return o[k]!=null&&o[k]!==\"\";}).map(function(k){return encodeURIComponent(k)+\"=\"+encodeURIComponent(o[k]);}).join(\"&\");}\n\n/* ---------- toast + status ---------- */\nfunction toast(msg,kind){var t=el(\"div\",{class:\"toast \"+(kind||\"\"),text:msg});$(\"toasts\").appendChild(t);setTimeout(function(){t.style.opacity=\"0\";setTimeout(function(){t.remove();},250);},kind===\"err\"?5200:3000);}\nfunction logMsg(msg,kind){toast(msg,kind);}\nfunction setConnStatus(text,kind){$(\"stConn\").innerHTML=\"\";$(\"stConn\").appendChild(el(\"span\",{class:\"st-dot \"+(kind||\"\")}));$(\"stConn\").appendChild(el(\"span\",{text:\" \"+text}));}\nfunction setRun(on){$(\"stConn\").firstChild.className=\"st-dot \"+(on?\"run\":\"ok\");}\n\n/* ---------- global state ---------- */\nvar S = { connections:[], activeConnId:\"\", connType:\"\", activeSchema:\"\", readOnly:RO_DEFAULT, tabs:[], activeTabId:\"\", seq:0, savedQueries:[] };\nfunction activeConn(){return S.connections.filter(function(c){return c.id===S.activeConnId;})[0];}\n\n/* ====================================================================\n CONTEXT MENU\n ==================================================================== */\nfunction showCtx(x,y,items){var m=$(\"contextMenu\");clear(m);items.forEach(function(it){if(it.sep){m.appendChild(el(\"div\",{class:\"ctxsep\"}));return;}m.appendChild(el(\"div\",{class:\"ctxitem\"+(it.danger?\" danger\":\"\"),onclick:function(){hideCtx();it.onClick();}},[icEl(it.icon||\"\",\"\"),el(\"span\",{text:it.label})]));});m.classList.remove(\"hidden\");var w=m.offsetWidth,h=m.offsetHeight;m.style.left=Math.min(x,window.innerWidth-w-8)+\"px\";m.style.top=Math.min(y,window.innerHeight-h-8)+\"px\";}\nfunction hideCtx(){$(\"contextMenu\").classList.add(\"hidden\");}\ndocument.addEventListener(\"click\",hideCtx);\ndocument.addEventListener(\"scroll\",hideCtx,true);\n\n/* ====================================================================\n MODAL\n ==================================================================== */\nfunction openModal(node){var root=$(\"modalRoot\");clear(root);var overlay=el(\"div\",{class:\"modal\",onclick:function(e){if(e.target===overlay)closeModal();}},[node]);root.appendChild(overlay);root.classList.remove(\"hidden\");}\nfunction closeModal(){$(\"modalRoot\").classList.add(\"hidden\");clear($(\"modalRoot\"));}\n\n/* ====================================================================\n CONNECTIONS\n ==================================================================== */\nfunction loadConnections(){var box=$(\"connList\");box.innerHTML='<div class=\"skel\"></div><div class=\"skel\"></div>';return api(\"GET\",\"/api/connections\").then(function(r){S.connections=r.connections||[];renderConnections();}).catch(function(e){box.innerHTML='<div class=\"empty\">'+esc(e.message)+'</div>';});}\nfunction renderConnections(){var q=($(\"connSearch\").value||\"\").toLowerCase();var box=clear($(\"connList\"));var rows=S.connections.filter(function(c){return (c.name+\" \"+c.type+\" \"+(c.org||\"\")+\" \"+(c.app||\"\")+\" \"+(c.environment||\"\")).toLowerCase().indexOf(q)>=0;});rows.sort(function(a,b){return (b.isFavorite?1:0)-(a.isFavorite?1:0);});if(!rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:S.connections.length?\"No match.\":\"No connections yet. Click + New or Import.\"}));return;}rows.forEach(function(c){box.appendChild(connCard(c));});}\nfunction connCard(c){var color=c.color||ENV_COLORS[c.environment]||\"#64748b\";var card=el(\"div\",{class:\"conn-card\"+(c.id===S.activeConnId?\" active\":\"\"),style:\"border-left-color:\"+color,oncontextmenu:function(e){e.preventDefault();connMenu(e,c);}});card.addEventListener(\"click\",function(){activateConnection(c.id);});\n var star=el(\"span\",{class:\"star\"+(c.isFavorite?\" on\":\"\"),title:\"Favorite\",onclick:function(e){e.stopPropagation();toggleFavorite(c);}});star.innerHTML=svgFor(\"star\");\n card.appendChild(el(\"div\",{class:\"conn-top\"},[icEl(\"db\",\"db\"),el(\"span\",{class:\"conn-name\",text:c.name,title:c.name}),star]));\n var sub=[c.org,c.space].filter(Boolean).join(\" / \")||c.host;\n card.appendChild(el(\"div\",{class:\"conn-sub\",text:sub,title:sub}));\n var tags=el(\"div\",{class:\"conn-tags\"});\n tags.appendChild(el(\"span\",{class:\"tag type\",text:c.type===\"hana\"?\"HANA\":\"PostgreSQL\"}));\n if(c.environment)tags.appendChild(el(\"span\",{class:\"tag env-\"+c.environment,text:c.environment}));\n if(c.schema||c.serviceName)tags.appendChild(el(\"span\",{class:\"tag\",text:c.serviceName||c.schema}));\n card.appendChild(tags);\n return card;\n}\nfunction isProdConn(c){return c&&/prod|production|prd|live/i.test((c.environment||\"\")+\" \"+(c.org||\"\")+\" \"+(c.app||\"\")+\" \"+(c.space||\"\"));}\nfunction activateConnection(id){S.activeConnId=id;var c=activeConn();S.connType=c?c.type:\"\";renderConnections();updateTopBadges();buildTreeForConnection();if(c&&isProdConn(c))logMsg(\"Warning: '\"+c.name+\"' looks like a production target.\",\"warn\");}\nfunction updateTopBadges(){var c=activeConn();$(\"connBadge\").textContent=c?(\"Conn: \"+c.name):\"No connection\";$(\"connBadge\").className=\"badge\"+(c?\" on\":\"\");var tb=$(\"typeBadge\");if(c){tb.classList.remove(\"hidden\");tb.className=\"badge \"+(c.type===\"hana\"?\"hana\":\"pg\");tb.textContent=c.type===\"hana\"?\"HANA\":\"PostgreSQL\";}else tb.classList.add(\"hidden\");$(\"schemaBadge\").textContent=\"Schema: \"+(S.activeSchema||\"-\");var pb=$(\"prodBadge\");if(c&&isProdConn(c))pb.classList.remove(\"hidden\");else pb.classList.add(\"hidden\");}\nfunction toggleFavorite(c){api(\"POST\",\"/api/connections/update\",{id:c.id,isFavorite:!c.isFavorite}).then(function(){return loadConnections();}).catch(function(e){logMsg(e.message,\"err\");});}\nfunction connMenu(e,c){showCtx(e.clientX,e.clientY,[\n {label:\"Open SQL Console\",icon:\"sql\",onClick:function(){S.activeConnId=c.id;S.connType=c.type;renderConnections();updateTopBadges();openSqlTab();}},\n {label:\"Connect / Refresh tree\",icon:\"refresh\",onClick:function(){activateConnection(c.id);}},\n {sep:true},\n {label:\"Test connection\",icon:\"run\",onClick:function(){testConn(c);}},\n {label:\"Edit (name, color, env)\",icon:\"gear\",onClick:function(){editConnModal(c);}},\n {label:c.isFavorite?\"Unfavorite\":\"Favorite\",icon:\"star\",onClick:function(){toggleFavorite(c);}},\n {label:\"Refresh from BTP app env\",icon:\"imp\",onClick:function(){if(c.app){api(\"POST\",\"/api/connections/import-from-app\",{app:c.app,serviceName:c.serviceName,type:c.type}).then(function(){logMsg(\"Refreshed from \"+c.app,\"ok\");return loadConnections();}).catch(function(er){logMsg(er.message,\"err\");});}else logMsg(\"This connection has no linked BTP app.\",\"warn\");}},\n {label:\"Duplicate\",icon:\"plus\",onClick:function(){api(\"POST\",\"/api/connections/duplicate\",{id:c.id}).then(function(){return loadConnections();}).then(function(){logMsg(\"Duplicated.\",\"ok\");});}},\n {sep:true},\n {label:\"Remove\",icon:\"x\",danger:true,onClick:function(){if(confirm(\"Remove connection '\"+c.name+\"'?\"))api(\"POST\",\"/api/connections/remove\",{id:c.id}).then(function(){if(S.activeConnId===c.id){S.activeConnId=\"\";clear($(\"tree\"));updateTopBadges();}return loadConnections();}).then(function(){logMsg(\"Removed.\",\"ok\");});}}\n]);}\nfunction testConn(c){setConnStatus(\"Testing \"+c.name+\"...\",\"run\");api(\"POST\",\"/api/connections/test\",{connectionId:c.id}).then(function(r){if(r.success){setConnStatus(\"Connected\", \"ok\");logMsg(\"Connection OK (\"+(r.serverVersion||\"\")+\") \"+r.durationMs+\"ms\",\"ok\");}else{setConnStatus(\"Failed\",\"err\");logMsg(\"Test failed: \"+r.message,\"err\");}}).catch(function(e){setConnStatus(\"Failed\",\"err\");logMsg(e.message,\"err\");});}\nfunction editConnModal(c){var sel={color:c.color||\"\",env:c.environment||\"\"};var nameI=el(\"input\",{class:\"input\",value:c.name});var sw=el(\"div\",{class:\"swatches\"});SWATCHES.forEach(function(col){var s=el(\"div\",{class:\"swatch\"+(sel.color===col?\" sel\":\"\"),style:\"background:\"+col,onclick:function(){sel.color=col;Array.prototype.forEach.call(sw.children,function(x){x.classList.remove(\"sel\");});s.classList.add(\"sel\");}});sw.appendChild(s);});\n var envSel=el(\"select\",{class:\"select\"});[\"\",\"DEV\",\"QAS\",\"PROD\",\"SANDBOX\",\"CUSTOM\"].forEach(function(en){envSel.appendChild(el(\"option\",{value:en,text:en||\"(none)\"}));});envSel.value=sel.env;\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Edit connection\"}),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Display name\"}),nameI]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Color\"}),sw]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Environment\"}),envSel]),el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Save\",onclick:function(){api(\"POST\",\"/api/connections/update\",{id:c.id,name:nameI.value.trim()||c.name,color:sel.color,environment:envSel.value}).then(function(){closeModal();return loadConnections();}).then(function(){updateTopBadges();logMsg(\"Connection updated.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});}})])]);openModal(d);}\n\n/* ====================================================================\n OBJECT TREE (DBeaver-style, lazy)\n ==================================================================== */\nfunction treeNode(opts){\n var chev=el(\"span\",{class:\"tchev\"+(opts.leaf?\" leaf\":\"\"),html:\"\\u203a\"});\n var label=el(\"span\",{class:\"tlabel\",text:opts.label,title:opts.label});\n var badge=el(\"span\",{class:\"tbadge\"});\n var spin=el(\"span\",{class:\"hidden\"});\n var row=el(\"div\",{class:\"trow\"},[chev,icEl(opts.icon,opts.iconCls),label,badge,spin]);\n var kids=el(\"div\",{class:\"tchildren hidden\"});\n var node=el(\"div\",{class:\"tnode\"},[row,kids]);\n node._loaded=false;node._open=false;\n function setLoading(on){spin.className=on?\"spin\":\"hidden\";}\n function setBadge(t){badge.textContent=t==null?\"\":\"(\"+t+\")\";}\n function expand(){if(opts.leaf)return;node._open=true;chev.classList.add(\"open\");kids.classList.remove(\"hidden\");if(!node._loaded&&opts.onExpand){node._loaded=true;setLoading(true);Promise.resolve(opts.onExpand(kids,setBadge)).catch(function(e){kids.appendChild(el(\"div\",{class:\"tnote\",text:\"Error: \"+e.message}));}).finally(function(){setLoading(false);});}}\n function collapse(){node._open=false;chev.classList.remove(\"open\");kids.classList.add(\"hidden\");}\n function toggle(){node._open?collapse():expand();}\n if(!opts.leaf)chev.addEventListener(\"click\",function(e){e.stopPropagation();toggle();});\n row.addEventListener(\"click\",function(){if(opts.onClick)opts.onClick();else if(!opts.leaf)toggle();});\n if(opts.onDblClick)row.addEventListener(\"dblclick\",opts.onDblClick);\n if(opts.onMenu)row.addEventListener(\"contextmenu\",function(e){e.preventDefault();opts.onMenu(e);});\n node._row=row;node._kids=kids;node._expand=expand;node._reload=function(){node._loaded=false;clear(kids);if(node._open)expand();};\n return node;\n}\nfunction buildTreeForConnection(){var t=clear($(\"tree\"));var c=activeConn();if(!c){t.appendChild(el(\"div\",{class:\"tnote\",text:\"Select a connection.\"}));return;}\n var root=treeNode({label:c.name,icon:\"db\",iconCls:\"db\",onExpand:function(kids){\n var cat=treeNode({label:\"Catalog\",icon:\"fld\",iconCls:\"fld\",onExpand:function(k2){\n var schemas=treeNode({label:\"Schemas\",icon:\"sch\",iconCls:\"sch\",onExpand:loadSchemasNode});\n k2.appendChild(schemas);schemas._expand();\n }});\n kids.appendChild(cat);cat._expand();\n }});\n t.appendChild(root);root._expand();\n}\nfunction loadSchemasNode(kids,setBadge){return api(\"GET\",\"/api/catalog/schemas?\"+qstr({connectionId:S.activeConnId})).then(function(r){var schemas=r.schemas||[];setBadge(schemas.length);var c=activeConn();var preferred=c&&c.schema;schemas.sort(function(a,b){return (a.isSystem?1:0)-(b.isSystem?1:0);});schemas.forEach(function(s){kids.appendChild(schemaNode(s));});var pref=schemas.filter(function(s){return s.name===preferred;})[0]||schemas.filter(function(s){return !s.isSystem;})[0];if(pref){S.activeSchema=pref.name;updateTopBadges();}});}\nfunction schemaNode(s){return treeNode({label:s.name,icon:\"sch\",iconCls:\"sch\",onClick:function(){S.activeSchema=s.name;updateTopBadges();},onExpand:function(kids){\n var folders=[[\"Tables\",\"table\",\"tbl\"],[\"Views\",\"view\",\"viw\"],[\"Procedures\",\"procedure\",\"prc\"],[\"Functions\",\"function\",\"fun\"],[\"Synonyms\",\"synonym\",\"syn\"],[\"Indexes\",\"index\",\"idx\"]];\n folders.forEach(function(f){kids.appendChild(folderNode(s.name,f[0],f[1],f[2]));});\n}});}\nfunction folderNode(schema,label,kind,iconCls){return treeNode({label:label,icon:\"fld\",iconCls:\"fld\",onExpand:function(kids,setBadge){\n if(kind===\"index\"){kids.appendChild(el(\"div\",{class:\"tnote\",text:\"Open a table's Structure to view its indexes.\"}));setBadge(null);return;}\n var listBox=el(\"div\");\n var search=el(\"input\",{class:\"input\",placeholder:\"Search \"+label.toLowerCase()+\"...\"});\n var sb=el(\"div\",{class:\"searchbox tsearch\"},[el(\"span\",{html:svgFor(\"search\")}),search]);\n kids.appendChild(sb);kids.appendChild(listBox);\n var run=function(){var sp=el(\"span\",{class:\"spin\"});clear(listBox).appendChild(el(\"div\",{class:\"tnote\"},[sp,\" loading...\"]));api(\"GET\",\"/api/catalog/objects?\"+qstr({connectionId:S.activeConnId,schema:schema,kinds:kind,search:search.value||\"\"})).then(function(r){var objs=r.objects||[];setBadge(objs.length);clear(listBox);if(!objs.length){listBox.appendChild(el(\"div\",{class:\"tnote\",text:\"None.\"}));return;}objs.forEach(function(o){listBox.appendChild(objectNode(schema,o,iconCls));});}).catch(function(e){clear(listBox).appendChild(el(\"div\",{class:\"tnote\",text:e.message}));});};\n search.addEventListener(\"input\",debounce(run,250));\n run();\n}});}\nfunction objectNode(schema,o,iconCls){var canData=o.kind===\"table\"||o.kind===\"view\"||o.kind===\"column-view\";return treeNode({label:o.name,icon:iconCls,iconCls:iconCls,leaf:true,\n onClick:function(){selectTreeRow(this);},\n onDblClick:canData?function(){openDataTab(schema,o.name);}:null,\n onMenu:canData?function(e){objectMenu(e,schema,o);}:function(e){objectMenu(e,schema,o,true);}\n});}\nvar _selRow=null;\nfunction selectTreeRow(node){if(_selRow)_selRow._row.classList.remove(\"sel\");_selRow=node;node._row.classList.add(\"sel\");}\nfunction objectMenu(e,schema,o,limited){var qn='\"'+schema+'\".\"'+o.name+'\"';var items=[];if(!limited){items.push({label:\"Open Data\",icon:\"table2\",onClick:function(){openDataTab(schema,o.name);}});items.push({label:\"Open Structure\",icon:\"col\",onClick:function(){openStructureTab(schema,o.name);}});items.push({sep:true});items.push({label:\"Generate SELECT\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/sql\",{connectionId:S.activeConnId,schema:schema,table:o.name,limit:100}).then(function(r){openSqlTab(r.select,o.name);});}});items.push({label:\"Generate COUNT\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/sql\",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){openSqlTab(r.count,o.name);});}});}\n items.push({label:\"Copy Full Name\",icon:\"col\",onClick:function(){navigator.clipboard.writeText(qn);logMsg(\"Copied \"+qn,\"ok\");}});\n showCtx(e.clientX,e.clientY,items);}\n\n/* ====================================================================\n WORKSPACE TABS\n ==================================================================== */\nfunction renderTabBar(){var bar=clear($(\"tabbar\"));S.tabs.forEach(function(tab){var chip=el(\"div\",{class:\"wtab\"+(tab.id===S.activeTabId?\" active\":\"\"),onclick:function(){switchTab(tab.id);}});chip.appendChild(el(\"span\",{class:\"t-ico\",html:svgFor(tab.icon||\"sql\")}));chip.appendChild(el(\"span\",{class:\"t-title\",text:tab.title,title:tab.title}));if(tab.dirty)chip.appendChild(el(\"span\",{class:\"dot\"}));if(tab.closable!==false)chip.appendChild(el(\"span\",{class:\"x\",html:svgFor(\"x\"),onclick:function(e){e.stopPropagation();closeTab(tab.id);}}));bar.appendChild(chip);});}\nfunction openTab(spec){var ex=S.tabs.filter(function(t){return t.key===spec.key;})[0];if(ex){switchTab(ex.id);return ex;}var id=\"wt\"+(++S.seq);var pane=el(\"div\",{class:\"tabpane hidden\"});$(\"tabcontent\").appendChild(pane);var tab={id:id,key:spec.key,kind:spec.kind,title:spec.title,icon:spec.icon,dirty:false,closable:spec.closable!==false,el:pane,state:{}};S.tabs.push(tab);spec.build(pane,tab);renderTabBar();switchTab(id);return tab;}\nfunction switchTab(id){S.activeTabId=id;S.tabs.forEach(function(t){t.el.classList.toggle(\"hidden\",t.id!==id);});renderTabBar();var tab=tabById(id);if(tab&&tab.onShow)tab.onShow();updatePendingStatus();}\nfunction tabById(id){return S.tabs.filter(function(t){return t.id===id;})[0];}\nfunction setDirty(tab,on){tab.dirty=on;renderTabBar();updatePendingStatus();}\nfunction closeTab(id){var tab=tabById(id);if(!tab)return;if(tab.dirty&&!confirm(\"'\"+tab.title+\"' has unsaved changes. Close anyway?\"))return;tab.el.remove();var idx=S.tabs.indexOf(tab);S.tabs=S.tabs.filter(function(t){return t.id!==id;});if(S.activeTabId===id){var next=S.tabs[Math.max(0,idx-1)];if(next)switchTab(next.id);else openWelcome();}renderTabBar();}\nfunction updatePendingStatus(){var tab=tabById(S.activeTabId);var n=tab&&tab.state&&tab.state.g?pendingCount(tab.state.g):0;$(\"stPending\").textContent=n>0?(n+\" pending change\"+(n>1?\"s\":\"\")):\"\";$(\"stPending\").className=n>0?\"st-item st-pending\":\"st-item\";}\n\n/* ====================================================================\n WELCOME\n ==================================================================== */\nfunction openWelcome(){openTab({key:\"welcome\",kind:\"welcome\",title:\"Welcome\",icon:\"home\",closable:false,build:buildWelcome});}\nfunction buildWelcome(pane){var w=el(\"div\",{class:\"welcome\"});w.appendChild(el(\"h1\",{text:\"SimpleMDG CF DB Studio\"}));w.appendChild(el(\"div\",{class:\"lede\",text:\"A local HANA / PostgreSQL explorer with BTP credential import. Local only \\u00b7 127.0.0.1\"}));\n var cards=el(\"div\",{class:\"wcards\"});\n cards.appendChild(wcard(\"imp\",\"Import from BTP App\",\"Read cf env and detect HANA/PostgreSQL credentials.\",openBtpWizard));\n cards.appendChild(wcard(\"plus\",\"Add direct connection\",\"Connect by host/port/user like DBeaver.\",function(){newConnModal();}));\n cards.appendChild(wcard(\"sql\",\"Open SQL Console\",\"Write and run SQL with safety checks.\",function(){if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");openSqlTab();}));\n cards.appendChild(wcard(\"db\",\"Connect to cached DB\",\"Pick a saved connection from the left.\",function(){if(S.connections[0])activateConnection(S.connections[0].id);}));\n w.appendChild(cards);\n var cols=el(\"div\",{class:\"wcols\"});\n var recent=el(\"div\",{class:\"wcol\"},[el(\"h4\",{text:\"Recent connections\"})]);var rl=el(\"div\",{class:\"wlist\"});S.connections.slice(0,5).forEach(function(c){rl.appendChild(el(\"div\",{class:\"wli\",onclick:function(){activateConnection(c.id);}},[el(\"b\",{text:c.name}),el(\"div\",{class:\"note\",text:(c.type===\"hana\"?\"HANA\":\"PostgreSQL\")+\" \\u00b7 \"+(c.org||c.host)})]));});if(!S.connections.length)rl.appendChild(el(\"div\",{class:\"empty\",text:\"None yet.\"}));recent.appendChild(rl);\n var rq=el(\"div\",{class:\"wcol\"},[el(\"h4\",{text:\"Recent queries\"})]);var ql=el(\"div\",{class:\"wlist\"});S.savedQueries.slice(0,5).forEach(function(q){ql.appendChild(el(\"div\",{class:\"wli\",onclick:function(){openSqlTab(q.sql,q.name);}},[el(\"b\",{text:q.name}),el(\"div\",{class:\"note\",text:(q.connectionType||\"\")+\" \\u00b7 \"+new Date(q.updatedAt).toLocaleString()})]));});if(!S.savedQueries.length)ql.appendChild(el(\"div\",{class:\"empty\",text:\"None yet.\"}));rq.appendChild(ql);\n cols.appendChild(recent);cols.appendChild(rq);w.appendChild(cols);\n pane.appendChild(w);\n}\nfunction wcard(icon,title,desc,onClick){return el(\"div\",{class:\"wcard\",onclick:onClick},[el(\"div\",{class:\"wc-ic\",html:svgFor(icon)}),el(\"h3\",{text:title}),el(\"p\",{text:desc})]);}\n\n/* ====================================================================\n SQL CONSOLE TAB\n ==================================================================== */\nvar DANGER=/\\b(drop|truncate|alter|grant|revoke)\\b/i;\nfunction openSqlTab(sql,nameHint){var title=\"SQL\"+(nameHint?\": \"+nameHint:\" Console\");openTab({key:\"sql:\"+(++S.seq),kind:\"sql\",title:title,icon:\"sql\",build:function(pane,tab){buildSqlPane(pane,tab,sql||\"select * from DUMMY\");}});}\nfunction buildSqlPane(pane,tab,initialSql){\n var editor=el(\"textarea\",{class:\"editor\",spellcheck:\"false\"});editor.value=initialSql;tab.state.editor=editor;\n editor.addEventListener(\"input\",function(){setDirty(tab,true);});\n editor.addEventListener(\"keydown\",function(e){if((e.ctrlKey||e.metaKey)&&e.key===\"Enter\"){e.preventDefault();runSqlTab(tab);}});\n var limitSel=el(\"select\",{class:\"select\",style:\"width:auto\"});[\"100\",\"500\",\"1000\",\"5000\",\"0\"].forEach(function(v){limitSel.appendChild(el(\"option\",{value:v,text:v===\"0\"?\"No limit\":v}));});tab.state.limit=limitSel;\n var runBtn=el(\"button\",{class:\"btn\",onclick:function(){runSqlTab(tab);}},[el(\"span\",{html:svgFor(\"run\")}),\" Run\"]);tab.state.runBtn=runBtn;\n var tb=el(\"div\",{class:\"toolbar\"},[runBtn,el(\"button\",{class:\"btn sec\",text:\"Format\",onclick:function(){editor.value=formatSql(editor.value);}}),el(\"button\",{class:\"btn ghost\",text:\"Explain\",onclick:function(){explainSqlTab(tab);}}),el(\"span\",{class:\"note\",text:\"Limit\"}),limitSel,el(\"button\",{class:\"btn ghost\",text:\"Save\",onclick:function(){saveQueryTab(tab);}}),el(\"button\",{class:\"btn ghost\",text:\"CSV\",onclick:function(){exportResult(tab,\"csv\");}}),el(\"button\",{class:\"btn ghost\",text:\"JSON\",onclick:function(){exportResult(tab,\"json\");}}),el(\"span\",{class:\"grow\"}),el(\"span\",{class:\"note\",id:\"sqlmeta_\"+tab.id})]);\n var body=el(\"div\",{class:\"pane-body\"});var errBox=el(\"div\",{class:\"errbox hidden\"});var grid=el(\"div\",{class:\"gridwrap\"});tab.state.err=errBox;tab.state.grid=grid;\n body.appendChild(editor);body.appendChild(errBox);body.appendChild(el(\"div\",{class:\"note\",text:\"Result\"}));body.appendChild(grid);\n pane.appendChild(tb);pane.appendChild(body);\n}\nfunction currentSqlText(tab){var ed=tab.state.editor;if(ed.selectionStart!=ed.selectionEnd){var s=ed.value.substring(ed.selectionStart,ed.selectionEnd).trim();if(s)return s;}return ed.value.trim();}\nfunction runSqlTab(tab,confirmed){var sql=currentSqlText(tab);if(!sql)return logMsg(\"Editor is empty.\",\"warn\");if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");\n if(!confirmed&&DANGER.test(sql)){if(!confirm(\"This statement may modify or drop data:\\n\\n\"+sql.slice(0,160)+\"\\n\\nRun anyway?\"))return;}\n tab.state.err.classList.add(\"hidden\");tab.state.runBtn.disabled=true;tab.state.runBtn.innerHTML=\"\";tab.state.runBtn.appendChild(el(\"span\",{class:\"spin\"}));tab.state.runBtn.appendChild(document.createTextNode(\" Running...\"));setRun(true);setConnStatus(\"Running query...\",\"run\");\n var limit=parseInt(tab.state.limit.value,10);\n api(\"POST\",\"/api/query/run\",{connectionId:S.activeConnId,sql:sql,limit:limit,readOnly:S.readOnly,confirmDangerous:true}).then(function(r){\n tab.state.runBtn.innerHTML=svgFor(\"run\")+\" Run\";tab.state.runBtn.disabled=false;setConnStatus(\"Connected\",\"ok\");\n if(r.blocked){tab.state.err.textContent=\"Read-only mode blocks: \"+(r.safety&&r.safety.matchedKeywords?r.safety.matchedKeywords.join(\", \"):\"write/DDL\");tab.state.err.classList.remove(\"hidden\");return;}\n if(!r.ok){tab.state.err.textContent=\"SQL failed (\"+(S.connType===\"hana\"?\"HANA\":\"PostgreSQL\")+\")\\n\"+r.error;tab.state.err.classList.remove(\"hidden\");return;}\n tab.state.lastResult=r.result;renderResultGrid(tab.state.grid,r.result,null);\n $(\"sqlmeta_\"+tab.id).textContent=\"Rows: \"+r.result.rowCount+(r.result.affectedRows!=null?\" \\u00b7 Affected: \"+r.result.affectedRows:\"\")+\" \\u00b7 \"+r.result.durationMs+\"ms\"+(r.result.truncated?\" \\u00b7 truncated\":\"\");\n $(\"stDuration\").textContent=r.result.durationMs+\"ms\";$(\"stRows\").textContent=r.result.rowCount+\" rows\";\n }).catch(function(e){tab.state.runBtn.innerHTML=svgFor(\"run\")+\" Run\";tab.state.runBtn.disabled=false;setConnStatus(\"Connected\",\"ok\");tab.state.err.textContent=e.message;tab.state.err.classList.remove(\"hidden\");});\n}\nfunction explainSqlTab(tab){if(S.connType!==\"postgresql\")return logMsg(\"Explain currently supports PostgreSQL.\",\"warn\");tab.state.editor.value=\"EXPLAIN \"+currentSqlText(tab);runSqlTab(tab,true);}\nfunction saveQueryTab(tab){var sql=tab.state.editor.value.trim();if(!sql)return logMsg(\"Nothing to save.\",\"warn\");var name=prompt(\"Query name\",\"Query \"+new Date().toLocaleString());if(!name)return;api(\"POST\",\"/api/queries\",{name:name,sql:sql,connectionId:S.activeConnId,connectionType:S.connType}).then(function(){setDirty(tab,false);loadSavedQueries();logMsg(\"Query saved.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});}\nfunction exportResult(tab,fmt){var res=tab.state.lastResult;if(!res||!res.rows.length)return logMsg(\"No result to export.\",\"warn\");var fields=res.fields&&res.fields.length?res.fields:Object.keys(res.rows[0]);fetch(fmt===\"csv\"?\"/api/export/csv\":\"/api/export/json\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({fields:fields,rows:res.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement(\"a\");a.href=URL.createObjectURL(b);a.download=fmt===\"csv\"?\"result.csv\":\"result.json\";a.click();logMsg(\"Exported \"+fmt.toUpperCase(),\"ok\");});}\nfunction formatSql(sql){return sql.replace(/\\s+/g,\" \").replace(/\\b(select|from|where|and|or|order by|group by|having|limit|offset|inner join|left join|right join|join|on|union|values|set|insert into|update|delete from|create table|alter table)\\b/gi,function(m){return \"\\n\"+m.toUpperCase();}).trim();}\n\n/* generic read-only result grid (sql console) */\nfunction renderResultGrid(box,result,onSort){clear(box);if(!result||!result.rows||!result.rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:result&&result.affectedRows!=null?(\"Affected rows: \"+result.affectedRows):\"No rows.\"}));return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var table=el(\"table\",{class:\"grid\"});var thead=el(\"thead\");var htr=el(\"tr\");htr.appendChild(el(\"th\",{class:\"rowhdr\",text:\"#\"}));fields.forEach(function(f){htr.appendChild(el(\"th\",{text:f,title:f}));});thead.appendChild(htr);table.appendChild(thead);var tb=el(\"tbody\");result.rows.forEach(function(row,ri){var tr=el(\"tr\");tr.appendChild(el(\"td\",{class:\"rowhdr\",text:ri+1}));fields.forEach(function(f){var v=row[f];var disp=v==null?\"\":typeof v===\"object\"?JSON.stringify(v):String(v);var td=el(\"td\",{class:typeof v===\"number\"?\"num\":\"\",title:disp,text:disp.length>400?disp.slice(0,400)+\"\\u2026\":disp});td.addEventListener(\"dblclick\",function(){navigator.clipboard.writeText(disp);setConnStatus(\"Cell copied\",\"ok\");});tr.appendChild(td);});tb.appendChild(tr);});table.appendChild(tb);box.appendChild(table);}\n\n/* ====================================================================\n DATA GRID TAB (editable, pending changes)\n ==================================================================== */\nfunction openDataTab(schema,table){openTab({key:\"data:\"+S.activeConnId+\":\"+schema+\".\"+table,kind:\"data\",title:table,icon:\"table2\",build:function(pane,tab){buildDataPane(pane,tab,schema,table);}});}\nfunction pendingCount(g){return Object.keys(g.edits).length+Object.keys(g.deletes).length+g.inserts.length;}\nfunction rowKeyOf(g,row){return g.pk.map(function(k){return String(row[k]);}).join(\"\\u0001\");}\nfunction buildDataPane(pane,tab,schema,table){\n var g={schema:schema,table:table,pk:[],columns:[],rows:[],offset:0,pageSize:100,where:\"\",orderBy:\"\",orderDir:\"asc\",total:null,edits:{},deletes:{},inserts:[],errors:{},editable:false,selRow:null,iseq:0};tab.state.g=g;\n var whereI=el(\"input\",{class:\"input\",style:\"flex:1\",placeholder:\"WHERE clause, e.g. STATUS = 'A'\"});g.whereI=whereI;\n var pageSel=el(\"select\",{class:\"select\",style:\"width:auto\"});[\"100\",\"500\",\"1000\"].forEach(function(v){pageSel.appendChild(el(\"option\",{value:v,text:v}));});g.pageSel=pageSel;\n var pageInfo=el(\"span\",{class:\"note\"});g.pageInfo=pageInfo;\n var tb1=el(\"div\",{class:\"toolbar\"},[whereI,el(\"button\",{class:\"btn\",text:\"Apply\",onclick:function(){g.where=whereI.value;g.offset=0;loadData(tab);}}),el(\"button\",{class:\"btn sec\",text:\"Refresh\",onclick:function(){loadData(tab);}}),el(\"span\",{class:\"note\",text:\"Page\"}),pageSel,el(\"button\",{class:\"btn sec\",text:\"Prev\",onclick:function(){g.offset=Math.max(0,g.offset-parseInt(pageSel.value,10));loadData(tab);}}),el(\"button\",{class:\"btn sec\",text:\"Next\",onclick:function(){g.offset+=parseInt(pageSel.value,10);loadData(tab);}}),pageInfo,el(\"span\",{class:\"grow\"}),el(\"button\",{class:\"btn ghost\",text:\"CSV\",onclick:function(){exportData(tab,\"csv\");}}),el(\"button\",{class:\"btn ghost\",text:\"JSON\",onclick:function(){exportData(tab,\"json\");}})]);\n var saveBtn=el(\"button\",{class:\"btn\",text:\"Save changes\",onclick:function(){saveDataChanges(tab);}});var revertBtn=el(\"button\",{class:\"btn ghost\",text:\"Revert all\",onclick:function(){revertAll(tab);}});g.saveBtn=saveBtn;g.revertBtn=revertBtn;\n var editHint=el(\"span\",{class:\"note\"});g.editHint=editHint;\n var tb2=el(\"div\",{class:\"toolbar\"},[el(\"button\",{class:\"btn sec\",text:\"+ Insert row\",onclick:function(){addInsertRow(tab);}}),el(\"button\",{class:\"btn ghost\",text:\"Delete selected\",onclick:function(){toggleDeleteSelected(tab);}}),el(\"button\",{class:\"btn ghost\",text:\"Structure\",onclick:function(){openStructureTab(schema,table);}}),saveBtn,revertBtn,editHint]);\n var grid=el(\"div\",{class:\"gridwrap\"});g.grid=grid;\n pane.appendChild(tb1);pane.appendChild(tb2);pane.appendChild(grid);\n updateDirtyButtons(tab);\n api(\"GET\",\"/api/catalog/columns?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.columns=r.columns||[];}).catch(function(){});\n api(\"GET\",\"/api/catalog/primary-key?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.pk=(r.primaryKey&&r.primaryKey.columns)||[];g.editable=g.pk.length>0;editHint.textContent=g.editable?(\"Editable \\u00b7 double-click a cell \\u00b7 key: \"+g.pk.join(\", \")):\"Read-only (no primary key detected)\";renderGrid(tab);}).catch(function(){editHint.textContent=\"\";});\n loadData(tab);\n loadCount(tab);\n}\nfunction loadData(tab){var g=tab.state.g;g.pageSize=parseInt(g.pageSel.value,10);clear(g.grid).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading data...\"]));api(\"POST\",\"/api/table/data\",{connectionId:S.activeConnId,schema:g.schema,table:g.table,limit:g.pageSize,offset:g.offset,where:g.where,orderBy:g.orderBy,orderDirection:g.orderDir}).then(function(r){g.rows=r.result.rows;g.selRow=null;renderGrid(tab);g.pageInfo.textContent=\"Offset \"+g.offset+\" \\u00b7 \"+r.result.rowCount+\" rows \\u00b7 \"+r.result.durationMs+\"ms\";$(\"stDuration\").textContent=r.result.durationMs+\"ms\";$(\"stRows\").textContent=(g.total!=null?g.total+\" total\":r.result.rowCount+\" rows\");}).catch(function(e){clear(g.grid).appendChild(el(\"div\",{class:\"errbox\",text:\"Cannot load data.\\nReason: \"+e.message+\"\\nAction: test the connection or refresh from BTP app env.\"}));});}\nfunction loadCount(tab){var g=tab.state.g;api(\"POST\",\"/api/table/count\",{connectionId:S.activeConnId,schema:g.schema,table:g.table}).then(function(r){g.total=r.count;$(\"stRows\").textContent=r.count+\" total\";}).catch(function(){});}\nfunction dataSortToggle(tab,field){var g=tab.state.g;if(g.orderBy===field)g.orderDir=g.orderDir===\"asc\"?\"desc\":\"asc\";else{g.orderBy=field;g.orderDir=\"asc\";}g.offset=0;loadData(tab);}\nfunction renderGrid(tab){var g=tab.state.g;var box=clear(g.grid);if(!g.rows.length&&!g.inserts.length){box.appendChild(el(\"div\",{class:\"empty\",text:\"No rows.\"}));return;}\n var fields=g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);\n var table=el(\"table\",{class:\"grid\"});var thead=el(\"thead\");var htr=el(\"tr\");htr.appendChild(el(\"th\",{class:\"rowhdr\",text:\"#\"}));fields.forEach(function(f){var arrow=g.orderBy===f?(g.orderDir===\"desc\"?\" \\u25BC\":\" \\u25B2\"):\"\";var th=el(\"th\",{title:\"Click to sort\",text:f+arrow});th.addEventListener(\"click\",function(){dataSortToggle(tab,f);});htr.appendChild(th);});thead.appendChild(htr);table.appendChild(thead);\n var tbody=el(\"tbody\");\n g.rows.forEach(function(row,ri){var key=rowKeyOf(g,row);var deleted=!!g.deletes[key];var edited=g.edits[key];var err=g.errors[key];var tr=el(\"tr\",{class:(g.selRow===ri?\"selrow \":\"\")+(deleted?\"row-del \":\"\")+(err?\"row-err \":\"\")});\n var flag=edited?'<span class=\"rowflag d\"></span>':(deleted?'<span class=\"rowflag del\"></span>':\"\");\n var num=el(\"td\",{class:\"rowhdr\",html:flag+(g.offset+ri+1)});num.addEventListener(\"click\",function(){g.selRow=(g.selRow===ri?null:ri);renderGrid(tab);});tr.appendChild(num);\n fields.forEach(function(f){var hasEdit=edited&&Object.prototype.hasOwnProperty.call(edited,f);var v=hasEdit?edited[f]:row[f];var disp=v==null?\"\":typeof v===\"object\"?JSON.stringify(v):String(v);var td=el(\"td\",{class:(typeof v===\"number\"?\"num \":\"\")+(hasEdit?\"edited\":\"\"),title:disp,text:disp.length>400?disp.slice(0,400)+\"\\u2026\":disp});if(g.editable&&!deleted){td.addEventListener(\"dblclick\",function(){startEdit(tab,td,ri,f,row);});}else{td.addEventListener(\"dblclick\",function(){navigator.clipboard.writeText(disp);setConnStatus(\"Cell copied\",\"ok\");});}tr.appendChild(td);});\n if(err){var etr=tr;etr.title=err;}\n tbody.appendChild(tr);});\n g.inserts.forEach(function(ins){var tr=el(\"tr\",{class:\"row-ins\"});tr.appendChild(el(\"td\",{class:\"rowhdr\",html:'<span class=\"rowflag ins\"></span>'+\"new\",onclick:function(){g.inserts=g.inserts.filter(function(x){return x!==ins;});updateDirtyButtons(tab);renderGrid(tab);}}));\n fields.forEach(function(f){var inp=el(\"input\",{class:\"cellinput\",value:ins.values[f]!=null?ins.values[f]:\"\"});inp.addEventListener(\"input\",function(){if(inp.value===\"\")delete ins.values[f];else ins.values[f]=inp.value;});var td=el(\"td\");td.appendChild(inp);tr.appendChild(td);});\n if(ins.error){tr.classList.add(\"row-err\");tr.title=ins.error;}\n tbody.appendChild(tr);});\n table.appendChild(tbody);box.appendChild(table);\n updateDirtyButtons(tab);\n}\nfunction startEdit(tab,td,ri,field,row){if(td.querySelector(\"input\"))return;var g=tab.state.g;var key=rowKeyOf(g,row);var cur=g.edits[key]&&Object.prototype.hasOwnProperty.call(g.edits[key],field)?g.edits[key][field]:row[field];var input=el(\"input\",{class:\"cellinput\"});input.value=cur==null?\"\":typeof cur===\"object\"?JSON.stringify(cur):String(cur);clear(td).appendChild(input);input.focus();input.select();var done=false;function commit(){if(done)return;done=true;var origStr=row[field]==null?\"\":String(row[field]);if(input.value===origStr){if(g.edits[key]){delete g.edits[key][field];if(!Object.keys(g.edits[key]).length)delete g.edits[key];}}else{g.edits[key]=g.edits[key]||{};g.edits[key][field]=input.value;}renderGrid(tab);}input.addEventListener(\"keydown\",function(e){if(e.key===\"Enter\"){e.preventDefault();commit();}else if(e.key===\"Escape\"){e.preventDefault();done=true;renderGrid(tab);}});input.addEventListener(\"blur\",commit);}\nfunction toggleDeleteSelected(tab){var g=tab.state.g;if(g.selRow==null)return logMsg(\"Click a row number to select it first.\",\"warn\");if(!g.editable)return logMsg(\"Cannot delete: no primary key.\",\"warn\");var key=rowKeyOf(g,g.rows[g.selRow]);if(g.deletes[key])delete g.deletes[key];else g.deletes[key]=true;renderGrid(tab);}\nfunction addInsertRow(tab){var g=tab.state.g;g.inserts.push({iseq:++g.iseq,values:{}});renderGrid(tab);}\nfunction revertAll(tab){var g=tab.state.g;g.edits={};g.deletes={};g.inserts=[];g.errors={};renderGrid(tab);logMsg(\"Reverted pending changes.\",\"ok\");}\nfunction updateDirtyButtons(tab){var g=tab.state.g;var n=pendingCount(g);g.saveBtn.style.display=n>0?\"\":\"none\";g.revertBtn.style.display=n>0?\"\":\"none\";setDirty(tab,n>0);}\nfunction exportData(tab,fmt){var g=tab.state.g;if(!g.rows.length)return logMsg(\"No rows.\",\"warn\");var fields=g.columns.length?g.columns.map(function(c){return c.name;}):Object.keys(g.rows[0]);fetch(fmt===\"csv\"?\"/api/export/csv\":\"/api/export/json\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({fields:fields,rows:g.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement(\"a\");a.href=URL.createObjectURL(b);a.download=fmt===\"csv\"?g.table+\".csv\":g.table+\".json\";a.click();});}\nfunction saveDataChanges(tab){var g=tab.state.g;if(S.readOnly)return logMsg(\"Read-only mode is on.\",\"warn\");\n var updates=Object.keys(g.edits).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,changes:g.edits[key]};});\n var deletes=Object.keys(g.deletes).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,_k:key};});\n var inserts=g.inserts.filter(function(i){return Object.keys(i.values).length;}).map(function(i){return {values:i.values,_ref:i};});\n var total=updates.length+deletes.length+inserts.length;if(!total)return logMsg(\"No changes to save.\",\"warn\");\n if(!confirm(\"Save changes?\\n\\nUpdates: \"+updates.length+\"\\nInserts: \"+inserts.length+\"\\nDeletes: \"+deletes.length))return;\n setConnStatus(\"Saving changes...\",\"run\");\n api(\"POST\",\"/api/table/save-changes\",{connectionId:S.activeConnId,schema:g.schema,table:g.table,primaryKeyColumns:g.pk,updates:updates.map(function(u){return {key:u.key,changes:u.changes};}),inserts:inserts.map(function(i){return {values:i.values};}),deletes:deletes.map(function(d){return {key:d.key};}),readOnly:S.readOnly}).then(function(resp){setConnStatus(\"Connected\",\"ok\");\n if(resp.blocked){logMsg(resp.error||\"Blocked by read-only.\",\"err\");return;}\n var rr=resp.result.rowResults||[];var ui=0;\n // updates first, then inserts, then deletes (server order)\n var failedU=0,failedI=0,failedD=0;\n updates.forEach(function(u){var res=rr[ui++];if(res&&res.success){delete g.edits[rowKeyOf(g,g.rows.filter(function(r){return JSON.stringify(pkObj(g,r))===JSON.stringify(u.key);})[0]||{})];}else{failedU++;var k=rowKeyFromKeyObj(g,u.key);g.errors[k]=res?res.error:\"failed\";}});\n inserts.forEach(function(i){var res=rr[ui++];if(res&&res.success){g.inserts=g.inserts.filter(function(x){return x!==i._ref;});}else{i._ref.error=res?res.error:\"failed\";failedI++;}});\n deletes.forEach(function(d){var res=rr[ui++];if(res&&res.success){delete g.deletes[d._k];}else{g.errors[d._k]=res?res.error:\"failed\";failedD++;}});\n var ok=resp.result.updated+resp.result.inserted+resp.result.deleted;var fail=failedU+failedI+failedD;\n if(fail===0){logMsg(ok+\" change(s) saved.\",\"ok\");loadData(tab);loadCount(tab);}\n else{logMsg(ok+\" saved, \"+fail+\" failed. Failed rows kept pending with error markers.\",\"err\");renderGrid(tab);}\n updateDirtyButtons(tab);\n }).catch(function(e){setConnStatus(\"Connected\",\"ok\");logMsg(\"Save failed: \"+e.message,\"err\");});\n}\nfunction pkObj(g,row){var o={};g.pk.forEach(function(k){o[k]=row[k];});return o;}\nfunction rowKeyFromKeyObj(g,keyObj){return g.pk.map(function(k){return String(keyObj[k]);}).join(\"\\u0001\");}\n\n/* ====================================================================\n STRUCTURE / METADATA TAB\n ==================================================================== */\nfunction openStructureTab(schema,table){openTab({key:\"struct:\"+S.activeConnId+\":\"+schema+\".\"+table,kind:\"structure\",title:\"Structure: \"+table,icon:\"col\",build:function(pane,tab){buildStructure(pane,tab,schema,table);}});}\nfunction buildStructure(pane,tab,schema,table){\n var subtabs=el(\"div\",{class:\"meta-tabs\"});var body=el(\"div\",{class:\"pane-body\"});\n var defs=[[\"columns\",\"Columns\"],[\"indexes\",\"Indexes\"],[\"ddl\",\"DDL\"],[\"info\",\"Info\"]];\n var active=\"columns\";var data={};\n function render(){clear(body);if(active===\"columns\")renderCols();else if(active===\"indexes\")renderIdx();else if(active===\"ddl\")renderDdl();else renderInfo();Array.prototype.forEach.call(subtabs.children,function(ch,i){ch.classList.toggle(\"active\",defs[i][0]===active);});}\n defs.forEach(function(d){subtabs.appendChild(el(\"div\",{class:\"meta-tab\",text:d[1],onclick:function(){active=d[0];render();}}));});\n pane.appendChild(el(\"div\",{class:\"toolbar\"},[el(\"b\",{text:'\"'+schema+'\".\"'+table+'\"'}),el(\"span\",{class:\"grow\"}),el(\"button\",{class:\"btn sec\",text:\"Open Data\",onclick:function(){openDataTab(schema,table);}})]));\n pane.appendChild(subtabs);pane.appendChild(body);\n function renderCols(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"GET\",\"/api/catalog/columns?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){data.columns=r.columns||[];if(active!==\"columns\")return;clear(body);var t=el(\"table\",{class:\"grid\"});t.appendChild(el(\"thead\",{html:\"<tr><th>Name</th><th>Type</th><th>Length</th><th>Scale</th><th>Nullable</th><th>Key</th><th>Default</th><th>Comment</th></tr>\"}));var tb=el(\"tbody\");data.columns.forEach(function(c){tb.appendChild(el(\"tr\",{html:\"<td>\"+esc(c.name)+\"</td><td>\"+esc(c.dataType)+'</td><td class=\"num\">'+esc(c.length==null?\"\":c.length)+'</td><td class=\"num\">'+esc(c.scale==null?\"\":c.scale)+\"</td><td>\"+(c.nullable?\"YES\":\"NO\")+\"</td><td>\"+(c.isPrimaryKey?'<span class=\"pill pk\">PK</span>':\"\")+\"</td><td>\"+esc(c.defaultValue==null?\"\":c.defaultValue)+\"</td><td>\"+esc(c.comment==null?\"\":c.comment)+\"</td>\"}));});t.appendChild(tb);clear(body).appendChild(t);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderIdx(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"GET\",\"/api/catalog/constraints?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){if(active!==\"indexes\")return;clear(body);var pk=r.primaryKey&&r.primaryKey.columns||[];body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Primary key\"}),el(\"div\",{text:pk.length?pk.join(\", \"):\"(none)\"})]));var idx=r.indexes||[];if(!idx.length){body.appendChild(el(\"div\",{class:\"empty\",text:\"No indexes.\"}));return;}var t=el(\"table\",{class:\"grid\"});t.appendChild(el(\"thead\",{html:\"<tr><th>Index</th><th>Columns</th><th>Unique</th><th>Primary</th></tr>\"}));var tb=el(\"tbody\");idx.forEach(function(i){tb.appendChild(el(\"tr\",{html:\"<td>\"+esc(i.name)+\"</td><td>\"+esc((i.columns||[]).join(\", \"))+\"</td><td>\"+(i.isUnique?\"YES\":\"NO\")+\"</td><td>\"+(i.isPrimaryKey?\"YES\":\"NO\")+\"</td>\"}));});t.appendChild(tb);body.appendChild(t);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderDdl(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" generating...\"]));api(\"GET\",\"/api/catalog/ddl?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){if(active!==\"ddl\")return;clear(body);var ta=el(\"textarea\",{class:\"editor\",readonly:\"readonly\"});ta.value=r.ddl;body.appendChild(el(\"div\",{class:\"row\"},[el(\"button\",{class:\"btn sec\",text:\"Open in SQL Console\",onclick:function(){openSqlTab(r.ddl,table);}}),el(\"button\",{class:\"btn ghost\",text:\"Copy\",onclick:function(){navigator.clipboard.writeText(r.ddl);logMsg(\"Copied DDL\",\"ok\");}})]));body.appendChild(ta);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderInfo(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"POST\",\"/api/table/count\",{connectionId:S.activeConnId,schema:schema,table:table}).then(function(r){if(active!==\"info\")return;clear(body).appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Schema\"}),el(\"div\",{text:schema}),el(\"div\",{class:\"k\",text:\"Object\"}),el(\"div\",{text:table}),el(\"div\",{class:\"k\",text:\"Row count\"}),el(\"div\",{text:String(r.count)}),el(\"div\",{class:\"k\",text:\"Columns\"}),el(\"div\",{text:String((data.columns||[]).length)})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n render();\n}\n\n/* ====================================================================\n SAVED QUERIES\n ==================================================================== */\nfunction loadSavedQueries(){return api(\"GET\",\"/api/queries\").then(function(r){S.savedQueries=r.queries||[];renderSavedQueries();}).catch(function(){});}\nfunction renderSavedQueries(){var q=($(\"querySearch\").value||\"\").toLowerCase();var box=clear($(\"queryList\"));var rows=S.savedQueries.filter(function(x){return (x.name+\" \"+(x.tags||[]).join(\" \")).toLowerCase().indexOf(q)>=0;});if(!rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:\"No saved queries.\"}));return;}rows.forEach(function(x){var item=el(\"div\",{class:\"wli\",onclick:function(){openSqlTab(x.sql,x.name);},oncontextmenu:function(e){e.preventDefault();queryMenu(e,x);}});item.appendChild(el(\"b\",{text:x.name}));item.appendChild(el(\"div\",{class:\"note\",text:(x.connectionType||\"\")+\" \\u00b7 \"+new Date(x.updatedAt).toLocaleDateString()}));box.appendChild(item);});}\nfunction queryMenu(e,x){showCtx(e.clientX,e.clientY,[{label:\"Open\",icon:\"sql\",onClick:function(){openSqlTab(x.sql,x.name);}},{label:\"Rename\",icon:\"gear\",onClick:function(){var n=prompt(\"New name\",x.name);if(n)api(\"PUT\",\"/api/queries/\"+encodeURIComponent(x.id),{name:n}).then(loadSavedQueries);}},{label:\"Delete\",icon:\"x\",danger:true,onClick:function(){if(confirm(\"Delete '\"+x.name+\"'?\"))api(\"DELETE\",\"/api/queries/\"+encodeURIComponent(x.id)).then(loadSavedQueries);}}]);}\n\n/* ====================================================================\n DIRECT CONNECTION MODAL\n ==================================================================== */\nfunction newConnModal(){var typeSel=el(\"select\",{class:\"select\"});typeSel.appendChild(el(\"option\",{value:\"postgresql\",text:\"PostgreSQL\"}));typeSel.appendChild(el(\"option\",{value:\"hana\",text:\"SAP HANA\"}));\n var f={name:el(\"input\",{class:\"input\"}),host:el(\"input\",{class:\"input\"}),port:el(\"input\",{class:\"input\",value:\"5432\"}),database:el(\"input\",{class:\"input\"}),schema:el(\"input\",{class:\"input\",value:\"public\"}),user:el(\"input\",{class:\"input\"}),pass:el(\"input\",{class:\"input\",type:\"password\"}),ssl:el(\"input\",{type:\"checkbox\"})};f.ssl.checked=true;\n typeSel.addEventListener(\"change\",function(){f.port.value=typeSel.value===\"hana\"?\"443\":\"5432\";f.schema.value=typeSel.value===\"hana\"?\"\":\"public\";});\n var msg=el(\"div\",{class:\"note\"});\n function body(){return {name:f.name.value.trim(),type:typeSel.value,host:f.host.value.trim(),port:parseInt(f.port.value,10)||(typeSel.value===\"hana\"?443:5432),database:f.database.value.trim(),schema:f.schema.value.trim(),username:f.user.value.trim(),password:f.pass.value,ssl:f.ssl.checked};}\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"New direct connection\"}),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Name\"}),f.name]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Type\"}),typeSel]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Host\"}),f.host]),el(\"div\",{class:\"field\",style:\"width:110px\"},[el(\"label\",{text:\"Port\"}),f.port])]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Database\"}),f.database]),el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Schema\"}),f.schema])]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Username\"}),f.user]),el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Password\"}),f.pass])]),el(\"label\",{class:\"note\"},[f.ssl,\" Use SSL\"]),el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn sec\",text:\"Test\",onclick:function(){msg.textContent=\"Testing...\";api(\"POST\",\"/api/connections/test-draft\",body()).then(function(r){msg.textContent=r.success?(\"OK \"+(r.serverVersion||\"\")):(\"Failed: \"+r.message);});}}),el(\"button\",{class:\"btn\",text:\"Save & use\",onclick:function(){var b=body();if(!b.name||!b.host||!b.username)return msg.textContent=\"Name, host, username required.\";api(\"POST\",\"/api/connections/create\",b).then(function(r){closeModal();return loadConnections().then(function(){activateConnection(r.connection.id);});}).catch(function(e){msg.textContent=e.message;});}})]),msg]);openModal(d);}\n\n/* ====================================================================\n BTP IMPORT WIZARD\n ==================================================================== */\nfunction openBtpWizard(){var stState={apps:[],services:[],app:\"\",svc:null,color:\"\",env:\"\"};\n var steps=el(\"div\",{class:\"steps\"});[\"Target\",\"App\",\"Services\",\"Save\"].forEach(function(s){steps.appendChild(el(\"div\",{class:\"step\",text:s}));});\n var body=el(\"div\");var msg=el(\"div\",{class:\"note\"});\n function setStep(i){Array.prototype.forEach.call(steps.children,function(ch,idx){ch.className=\"step\"+(idx===i?\" active\":idx<i?\" done\":\"\");});}\n function stepTarget(){setStep(0);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" checking CF target...\"]));api(\"GET\",\"/api/btp/current-target\").then(function(r){var t=r.target||{};clear(body);body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Logged in\"}),el(\"div\",{text:r.loggedIn?\"yes\":\"no\"}),el(\"div\",{class:\"k\",text:\"Region\"}),el(\"div\",{text:t.region||\"-\"}),el(\"div\",{class:\"k\",text:\"Org\"}),el(\"div\",{text:t.org||\"-\"}),el(\"div\",{class:\"k\",text:\"Space\"}),el(\"div\",{text:t.space||\"-\"})]));if(r.productionWarning)body.appendChild(el(\"div\",{class:\"badge prod\",text:\"Production-like target\"}));if(!r.loggedIn)body.appendChild(el(\"div\",{class:\"note\",text:r.message||\"Run: smdg cf login\"}));body.appendChild(el(\"div\",{class:\"note\",text:\"Switch org/space with: smdg cf org\"}));body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Load apps \\u2192\",onclick:stepApps}),el(\"button\",{class:\"btn sec\",text:\"Refresh apps\",onclick:function(){stState.apps=[];stepApps(true);}})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepApps(refresh){setStep(1);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading apps...\"]));api(\"GET\",\"/api/btp/apps\"+(refresh?\"?refresh=true\":\"\")).then(function(r){clear(body);if(!r.loggedIn){body.appendChild(el(\"div\",{class:\"errbox\",text:r.message||\"Not logged in. Run: smdg cf login\"}));return;}stState.apps=r.apps||[];var search=el(\"input\",{class:\"input\",placeholder:\"Search apps...\"});var list=el(\"div\",{class:\"wlistbox\"});function draw(){clear(list);var q=(search.value||\"\").toLowerCase();stState.apps.filter(function(a){return JSON.stringify(a).toLowerCase().indexOf(q)>=0;}).forEach(function(a){list.appendChild(el(\"div\",{class:\"wrow\"+(stState.app===a.name?\" sel\":\"\"),onclick:function(){stState.app=a.name;stepServices();}},[icEl(\"db\",\"db\"),el(\"div\",{style:\"flex:1\"},[el(\"b\",{text:a.name}),el(\"div\",{class:\"note\",text:(a.requestedState||\"\")+(a.routes?\" \\u00b7 \"+a.routes:\"\")})])]));});if(!stState.apps.length)list.appendChild(el(\"div\",{class:\"empty\",text:\"No apps.\"}));}search.addEventListener(\"input\",debounce(draw,200));body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Select an app\"}),search]));body.appendChild(list);body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:stepTarget}),el(\"button\",{class:\"btn sec\",text:\"Refresh\",onclick:function(){stepApps(true);}})]));draw();}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepServices(){setStep(2);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" reading cf env \"+esc(stState.app)+\"...\"]));api(\"POST\",\"/api/btp/env\",{app:stState.app}).then(function(r){clear(body);stState.services=r.services||[];if(!stState.services.length){body.appendChild(el(\"div\",{class:\"errbox\",text:\"No HANA/PostgreSQL service detected in \"+stState.app}));}var list=el(\"div\",{class:\"wlistbox\"});stState.services.forEach(function(svc){list.appendChild(el(\"div\",{class:\"wrow\",onclick:function(){stState.svc=svc;stepSave();}},[el(\"div\",{style:\"flex:1\"},[el(\"b\",{text:svc.serviceName}),el(\"div\",{class:\"note\",text:svc.type+\" \\u00b7 \"+svc.host+\" \\u00b7 \"+(svc.schema||svc.database||\"\")})])]));});body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Detected database services\"}),list]));body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:function(){stepApps();}})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepSave(){setStep(3);clear(body);var svc=stState.svc;var nameI=el(\"input\",{class:\"input\",value:(stState.app+\" / \"+svc.serviceName)});var envSel=el(\"select\",{class:\"select\"});[\"\",\"DEV\",\"QAS\",\"PROD\",\"SANDBOX\",\"CUSTOM\"].forEach(function(en){envSel.appendChild(el(\"option\",{value:en,text:en||\"(none)\"}));});\n var sel={color:\"\"};var sw=el(\"div\",{class:\"swatches\"});SWATCHES.forEach(function(col){var s=el(\"div\",{class:\"swatch\",style:\"background:\"+col,onclick:function(){sel.color=col;Array.prototype.forEach.call(sw.children,function(x){x.classList.remove(\"sel\");});s.classList.add(\"sel\");}});sw.appendChild(s);});\n var fav=el(\"input\",{type:\"checkbox\"});\n body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Service\"}),el(\"div\",{text:svc.serviceName}),el(\"div\",{class:\"k\",text:\"Type\"}),el(\"div\",{text:svc.type}),el(\"div\",{class:\"k\",text:\"Host\"}),el(\"div\",{text:svc.host})]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Display name\"}),nameI]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Environment\"}),envSel]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Color\"}),sw]));\n body.appendChild(el(\"label\",{class:\"note\"},[fav,\" Mark as favorite\"]));\n body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:12px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:stepServices},\"\"),el(\"button\",{class:\"btn\",text:\"Save & activate\",onclick:function(){msg.textContent=\"Importing & testing...\";api(\"POST\",\"/api/connections/import-from-app\",{app:stState.app,serviceName:svc.serviceName,type:svc.type}).then(function(r){var id=r.connection.id;return api(\"POST\",\"/api/connections/update\",{id:id,name:nameI.value.trim(),environment:envSel.value,color:sel.color,isFavorite:fav.checked}).then(function(){return api(\"POST\",\"/api/connections/test\",{connectionId:id}).then(function(t){closeModal();return loadConnections().then(function(){activateConnection(id);logMsg(t.success?(\"Imported & connected: \"+nameI.value):(\"Imported (test failed: \"+t.message+\")\"),t.success?\"ok\":\"warn\");});});});}).catch(function(e){msg.textContent=e.message;});}})]));\n body.appendChild(msg);\n }\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Import from BTP App\"}),steps,body]);openModal(d);stepTarget();\n}\n\n/* ====================================================================\n READ-ONLY + INIT\n ==================================================================== */\nfunction applyReadOnly(){var b=$(\"roBadge\");b.className=\"badge ro\"+(S.readOnly?\" active\":\"\");b.textContent=S.readOnly?\"Read-only\":\"Read/Write\";}\nfunction toggleReadOnly(){S.readOnly=!S.readOnly;applyReadOnly();logMsg(\"Read-only mode: \"+(S.readOnly?\"ON\":\"OFF\"),\"ok\");}\n\nfunction initSidebarCollapse(){Array.prototype.forEach.call(document.querySelectorAll(\".side-head\"),function(h){h.addEventListener(\"click\",function(){h.parentNode.classList.toggle(\"collapsed\");});});}\nfunction initResizer(){var r=$(\"resizer\"),sb=$(\"sidebar\"),drag=false;r.addEventListener(\"mousedown\",function(){drag=true;document.body.style.userSelect=\"none\";});window.addEventListener(\"mousemove\",function(e){if(!drag)return;sb.style.width=Math.min(560,Math.max(220,e.clientX))+\"px\";});window.addEventListener(\"mouseup\",function(){drag=false;document.body.style.userSelect=\"\";});}\n\nwindow.addEventListener(\"load\",function(){\n applyReadOnly();\n initSidebarCollapse();initResizer();\n $(\"roBadge\").addEventListener(\"click\",toggleReadOnly);\n $(\"btnImport\").addEventListener(\"click\",openBtpWizard);\n $(\"btnImport2\").addEventListener(\"click\",openBtpWizard);\n $(\"btnNewConn\").addEventListener(\"click\",newConnModal);\n $(\"btnSettings\").addEventListener(\"click\",function(){logMsg(\"Read-only toggle and connection colors are in the top bar / connection menu.\",\"ok\");});\n $(\"btnHome\").addEventListener(\"click\",openWelcome);\n $(\"connSearch\").addEventListener(\"input\",debounce(renderConnections,200));\n $(\"querySearch\").addEventListener(\"input\",debounce(renderSavedQueries,200));\n $(\"topSearch\").addEventListener(\"input\",debounce(function(){$(\"connSearch\").value=$(\"topSearch\").value;renderConnections();},200));\n $(\"btnNewQuery\").addEventListener(\"click\",function(){if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");openSqlTab();});\n setConnStatus(\"Ready\",\"ok\");\n openWelcome();\n loadConnections();\n loadSavedQueries();\n});\n})();\n";
|
|
1
|
+
export declare const STUDIO_CLIENT_SCRIPT = "\n\"use strict\";\n(function(){\nvar RO_DEFAULT = !!window.SMDG_READONLY_DEFAULT;\nvar ENV_COLORS = { DEV:\"#22c55e\", QAS:\"#f59e0b\", PROD:\"#ef4444\", SANDBOX:\"#6366f1\", CUSTOM:\"#3b82f6\" };\nvar SWATCHES = [\"#3b82f6\",\"#22c55e\",\"#f59e0b\",\"#ef4444\",\"#a855f7\",\"#06b6d4\",\"#ec4899\",\"#84cc16\",\"#64748b\"];\nvar ICONS = {\n db:\"M4 6c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z|M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6|M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3\",\n sch:\"M12 3l9 5-9 5-9-5 9-5z|M3 12l9 5 9-5|M3 16l9 5 9-5\",\n fld:\"M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z\",\n tbl:\"M3 5h18v14H3z|M3 10h18|M9 5v14|M15 5v14\",\n viw:\"M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7z|M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z\",\n prc:\"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 2v3|M12 19v3|M2 12h3|M19 12h3|M5 5l2 2|M17 17l2 2|M19 5l-2 2|M7 17l-2 2\",\n fun:\"M6 3h9l4 4v14H6z|M14 3v4h4|M9 12h6|M9 16h6\",\n syn:\"M10 13a5 5 0 0 0 7 0l2-2a5 5 0 0 0-7-7l-1 1|M14 11a5 5 0 0 0-7 0l-2 2a5 5 0 0 0 7 7l1-1\",\n idx:\"M14 4l6 6-9 9H5v-6z|M3 21h18\",\n search:\"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z|M21 21l-4.3-4.3\",\n star:\"M12 3l2.9 6 6.1.5-4.6 4 1.4 6-5.8-3.3L6.2 19.5l1.4-6L3 9.5 9.1 9z\",\n refresh:\"M21 12a9 9 0 1 1-3-6.7|M21 4v5h-5\",\n x:\"M6 6l12 12|M18 6L6 18\",\n plus:\"M12 5v14|M5 12h14\",\n imp:\"M12 3v12|M7 10l5 5 5-5|M5 21h14\",\n sql:\"M4 5h16v14H4z|M7 9l3 3-3 3|M13 15h4\",\n run:\"M13 3L4 14h7l-1 7 9-11h-7z\",\n save:\"M5 3h11l3 3v15H5z|M8 3v6h7V3|M8 21v-7h8v7\",\n gear:\"M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M19.4 13a7.9 7.9 0 0 0 0-2l2-1.5-2-3.4-2.3 1a8 8 0 0 0-1.7-1l-.4-2.6h-4l-.4 2.6a8 8 0 0 0-1.7 1l-2.3-1-2 3.4L4.6 11a7.9 7.9 0 0 0 0 2l-2 1.5 2 3.4 2.3-1a8 8 0 0 0 1.7 1l.4 2.6h4l.4-2.6a8 8 0 0 0 1.7-1l2.3 1 2-3.4z\",\n home:\"M3 11l9-8 9 8|M5 10v10h14V10\",\n table2:\"M4 4h16v16H4z|M4 9h16|M9 4v16\",\n col:\"M5 4v16|M12 4v16|M19 4v16\",\n trash:\"M4 7h16|M9 7V4h6v3|M6 7l1 13h10l1-13|M10 11v6|M14 11v6\",\n chevL:\"M15 6l-6 6 6 6\",\n chevR:\"M9 6l6 6-6 6\",\n filter:\"M3 5h18l-7 8v6l-4 2v-8z\",\n undo:\"M9 7L4 12l5 5|M4 12h11a5 5 0 0 1 0 10h-3\"\n};\nfunction svgFor(name){var d=ICONS[name]||\"\";return '<svg class=\"ic\" viewBox=\"0 0 24 24\">'+d.split(\"|\").map(function(p){return '<path d=\"'+p+'\"></path>';}).join(\"\")+'</svg>';}\nfunction icEl(name,cls){var s=document.createElement(\"span\");s.className=\"ticon \"+(cls||\"\");s.innerHTML=svgFor(name);return s;}\nfunction gbtn(icon,title,onClick,extra){var b=el(\"button\",{class:\"gbtn \"+(extra||\"\"),title:title,html:svgFor(icon)});b.addEventListener(\"click\",function(e){onClick(e);});return b;}\n\n/* ---------- dom helpers ---------- */\nfunction $(id){return document.getElementById(id);}\nfunction el(tag,attrs,kids){var n=document.createElement(tag);if(attrs)for(var k in attrs){var v=attrs[k];if(v==null)continue;if(k===\"class\")n.className=v;else if(k===\"text\")n.textContent=v;else if(k===\"html\")n.innerHTML=v;else if(k.slice(0,2)===\"on\"&&typeof v===\"function\")n.addEventListener(k.slice(2),v);else n.setAttribute(k,v);}if(kids!=null){(Array.isArray(kids)?kids:[kids]).forEach(function(c){if(c==null)return;n.appendChild(typeof c===\"string\"||typeof c===\"number\"?document.createTextNode(String(c)):c);});}return n;}\nfunction clear(n){while(n&&n.firstChild)n.removeChild(n.firstChild);return n;}\nfunction esc(v){return String(v==null?\"\":v).replace(/[&<>\"']/g,function(s){return {\"&\":\"&\",\"<\":\"<\",\">\":\">\",\"\\\"\":\""\",\"'\":\"'\"}[s];});}\nfunction highlightMatch(text,search){var t=String(text==null?\"\":text);var s=String(search==null?\"\":search).trim();if(!s)return esc(t);var lt=t.toLowerCase(),ls=s.toLowerCase(),out=\"\",i=0,idx;while((idx=lt.indexOf(ls,i))>=0){out+=esc(t.slice(i,idx))+'<mark class=\"hl\">'+esc(t.slice(idx,idx+s.length))+'</mark>';i=idx+s.length;}out+=esc(t.slice(i));return out;}\nfunction wireSearch(input,onRun,onClear){input.addEventListener(\"keydown\",function(e){if(e.key===\"Enter\"){e.preventDefault();onRun();}else if(e.key===\"Escape\"){e.preventDefault();input.value=\"\";if(onClear)onClear();else onRun();}});}\nfunction debounce(fn,ms){var t;return function(){var a=arguments,c=this;clearTimeout(t);t=setTimeout(function(){fn.apply(c,a);},ms||220);};}\nfunction topSpin(on){$(\"topSpin\").className=on?\"spin\":\"spin hidden\";}\n\n/* ---------- api ---------- */\nfunction api(method,path,body){topSpin(true);var opt={method:method,headers:{}};if(body!==undefined){opt.headers[\"content-type\"]=\"application/json\";opt.body=JSON.stringify(body);}return fetch(path,opt).then(function(r){return r.text().then(function(t){var j;try{j=t?JSON.parse(t):{};}catch(e){j={error:t};}if(!r.ok)throw new Error(j.error||(\"HTTP \"+r.status));return j;});}).finally(function(){topSpin(false);});}\nfunction qstr(o){return Object.keys(o).filter(function(k){return o[k]!=null&&o[k]!==\"\";}).map(function(k){return encodeURIComponent(k)+\"=\"+encodeURIComponent(o[k]);}).join(\"&\");}\n\n/* ---------- toast + status ---------- */\nfunction toast(msg,kind){var t=el(\"div\",{class:\"toast \"+(kind||\"\"),text:msg});$(\"toasts\").appendChild(t);setTimeout(function(){t.style.opacity=\"0\";setTimeout(function(){t.remove();},250);},kind===\"err\"?5200:3000);}\nfunction logMsg(msg,kind){toast(msg,kind);}\nfunction setConnStatus(text,kind){$(\"stConn\").innerHTML=\"\";$(\"stConn\").appendChild(el(\"span\",{class:\"st-dot \"+(kind||\"\")}));$(\"stConn\").appendChild(el(\"span\",{text:\" \"+text}));}\nfunction setRun(on){$(\"stConn\").firstChild.className=\"st-dot \"+(on?\"run\":\"ok\");}\n\n/* ---------- global state ---------- */\nvar S = { connections:[], activeConnId:\"\", connType:\"\", activeSchema:\"\", readOnly:RO_DEFAULT, tabs:[], activeTabId:\"\", seq:0, savedQueries:[], settings:{ restoreWorkspace:true, defaultRowLimit:100, defaultSchema:\"\", readOnlyByDefault:RO_DEFAULT, queryTimeoutMs:30000, autoFormatGeneratedSql:true, autoSaveDelayMs:500, maxHistoryItems:300, showProductionWarning:true, theme:\"dark\" } };\nfunction activeConn(){return S.connections.filter(function(c){return c.id===S.activeConnId;})[0];}\n\n/* ====================================================================\n CONTEXT MENU\n ==================================================================== */\nfunction showCtx(x,y,items){var m=$(\"contextMenu\");clear(m);items.forEach(function(it){if(it.sep){m.appendChild(el(\"div\",{class:\"ctxsep\"}));return;}m.appendChild(el(\"div\",{class:\"ctxitem\"+(it.danger?\" danger\":\"\"),onclick:function(){hideCtx();it.onClick();}},[icEl(it.icon||\"\",\"\"),el(\"span\",{text:it.label})]));});m.classList.remove(\"hidden\");var w=m.offsetWidth,h=m.offsetHeight;m.style.left=Math.min(x,window.innerWidth-w-8)+\"px\";m.style.top=Math.min(y,window.innerHeight-h-8)+\"px\";}\nfunction hideCtx(){$(\"contextMenu\").classList.add(\"hidden\");}\ndocument.addEventListener(\"click\",hideCtx);\ndocument.addEventListener(\"scroll\",hideCtx,true);\n\n/* ====================================================================\n MODAL\n ==================================================================== */\nfunction openModal(node){var root=$(\"modalRoot\");clear(root);var overlay=el(\"div\",{class:\"modal\",onclick:function(e){if(e.target===overlay)closeModal();}},[node]);root.appendChild(overlay);root.classList.remove(\"hidden\");}\nfunction closeModal(){$(\"modalRoot\").classList.add(\"hidden\");clear($(\"modalRoot\"));}\n\n/* ====================================================================\n CONNECTIONS\n ==================================================================== */\nfunction loadConnections(){var box=$(\"connList\");box.innerHTML='<div class=\"skel\"></div><div class=\"skel\"></div>';return api(\"GET\",\"/api/connections\").then(function(r){S.connections=r.connections||[];renderConnections();}).catch(function(e){box.innerHTML='<div class=\"empty\">'+esc(e.message)+'</div>';});}\nfunction renderConnections(){var raw=($(\"connSearch\").value||\"\");var q=raw.toLowerCase();var box=clear($(\"connList\"));var rows=S.connections.filter(function(c){return (c.name+\" \"+c.type+\" \"+(c.org||\"\")+\" \"+(c.app||\"\")+\" \"+(c.environment||\"\")).toLowerCase().indexOf(q)>=0;});rows.sort(function(a,b){return (b.isFavorite?1:0)-(a.isFavorite?1:0);});if(!rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:S.connections.length?\"No results found\":\"No connections yet. Click + New or Import.\"}));return;}rows.forEach(function(c){box.appendChild(connCard(c,raw));});}\nfunction connCard(c,q){var color=c.color||ENV_COLORS[c.environment]||\"#64748b\";var card=el(\"div\",{class:\"conn-card\"+(c.id===S.activeConnId?\" active\":\"\"),style:\"border-left-color:\"+color,oncontextmenu:function(e){e.preventDefault();connMenu(e,c);}});card.addEventListener(\"click\",function(){activateConnection(c.id);});\n var star=el(\"span\",{class:\"star\"+(c.isFavorite?\" on\":\"\"),title:\"Favorite\",onclick:function(e){e.stopPropagation();toggleFavorite(c);}});star.innerHTML=svgFor(\"star\");\n card.appendChild(el(\"div\",{class:\"conn-top\"},[icEl(\"db\",\"db\"),el(\"span\",{class:\"conn-name\",html:highlightMatch(c.name,q),title:c.name}),star]));\n var sub=[c.org,c.space].filter(Boolean).join(\" / \")||c.host;\n card.appendChild(el(\"div\",{class:\"conn-sub\",text:sub,title:sub}));\n var tags=el(\"div\",{class:\"conn-tags\"});\n tags.appendChild(el(\"span\",{class:\"tag type\",text:c.type===\"hana\"?\"HANA\":\"PostgreSQL\"}));\n if(c.environment)tags.appendChild(el(\"span\",{class:\"tag env-\"+c.environment,text:c.environment}));\n if(c.schema||c.serviceName)tags.appendChild(el(\"span\",{class:\"tag\",text:c.serviceName||c.schema}));\n card.appendChild(tags);\n return card;\n}\nfunction isProdConn(c){return c&&/prod|production|prd|live/i.test((c.environment||\"\")+\" \"+(c.org||\"\")+\" \"+(c.app||\"\")+\" \"+(c.space||\"\"));}\nfunction activateConnection(id){S.activeConnId=id;var c=activeConn();S.connType=c?c.type:\"\";renderConnections();updateTopBadges();buildTreeForConnection();if(c&&isProdConn(c))logMsg(\"Warning: '\"+c.name+\"' looks like a production target.\",\"warn\");}\nfunction updateTopBadges(){var c=activeConn();$(\"connBadge\").textContent=c?(\"Conn: \"+c.name):\"No connection\";$(\"connBadge\").className=\"badge\"+(c?\" on\":\"\");var tb=$(\"typeBadge\");if(c){tb.classList.remove(\"hidden\");tb.className=\"badge \"+(c.type===\"hana\"?\"hana\":\"pg\");tb.textContent=c.type===\"hana\"?\"HANA\":\"PostgreSQL\";}else tb.classList.add(\"hidden\");$(\"schemaBadge\").textContent=\"Schema: \"+(S.activeSchema||\"-\");var pb=$(\"prodBadge\");if(c&&isProdConn(c))pb.classList.remove(\"hidden\");else pb.classList.add(\"hidden\");}\nfunction toggleFavorite(c){api(\"POST\",\"/api/connections/update\",{id:c.id,isFavorite:!c.isFavorite}).then(function(){return loadConnections();}).catch(function(e){logMsg(e.message,\"err\");});}\nfunction connMenu(e,c){showCtx(e.clientX,e.clientY,[\n {label:\"Open SQL Console\",icon:\"sql\",onClick:function(){S.activeConnId=c.id;S.connType=c.type;renderConnections();updateTopBadges();openSqlTab();}},\n {label:\"Connect / Refresh tree\",icon:\"refresh\",onClick:function(){activateConnection(c.id);}},\n {sep:true},\n {label:\"Test connection\",icon:\"run\",onClick:function(){testConn(c);}},\n {label:\"Edit (name, color, env)\",icon:\"gear\",onClick:function(){editConnModal(c);}},\n {label:c.isFavorite?\"Unfavorite\":\"Favorite\",icon:\"star\",onClick:function(){toggleFavorite(c);}},\n {label:\"Refresh from BTP app env\",icon:\"imp\",onClick:function(){if(c.app){api(\"POST\",\"/api/connections/import-from-app\",{app:c.app,serviceName:c.serviceName,type:c.type}).then(function(){logMsg(\"Refreshed from \"+c.app,\"ok\");return loadConnections();}).catch(function(er){logMsg(er.message,\"err\");});}else logMsg(\"This connection has no linked BTP app.\",\"warn\");}},\n {label:\"Duplicate\",icon:\"plus\",onClick:function(){api(\"POST\",\"/api/connections/duplicate\",{id:c.id}).then(function(){return loadConnections();}).then(function(){logMsg(\"Duplicated.\",\"ok\");});}},\n {sep:true},\n {label:\"Remove\",icon:\"x\",danger:true,onClick:function(){if(confirm(\"Remove connection '\"+c.name+\"'?\"))api(\"POST\",\"/api/connections/remove\",{id:c.id}).then(function(){if(S.activeConnId===c.id){S.activeConnId=\"\";clear($(\"tree\"));updateTopBadges();}return loadConnections();}).then(function(){logMsg(\"Removed.\",\"ok\");});}}\n]);}\nfunction testConn(c){setConnStatus(\"Testing \"+c.name+\"...\",\"run\");api(\"POST\",\"/api/connections/test\",{connectionId:c.id}).then(function(r){if(r.success){setConnStatus(\"Connected\", \"ok\");logMsg(\"Connection OK (\"+(r.serverVersion||\"\")+\") \"+r.durationMs+\"ms\",\"ok\");}else{setConnStatus(\"Failed\",\"err\");logMsg(\"Test failed: \"+r.message,\"err\");}}).catch(function(e){setConnStatus(\"Failed\",\"err\");logMsg(e.message,\"err\");});}\nfunction editConnModal(c){var sel={color:c.color||\"\",env:c.environment||\"\"};var nameI=el(\"input\",{class:\"input\",value:c.name});var sw=el(\"div\",{class:\"swatches\"});SWATCHES.forEach(function(col){var s=el(\"div\",{class:\"swatch\"+(sel.color===col?\" sel\":\"\"),style:\"background:\"+col,onclick:function(){sel.color=col;Array.prototype.forEach.call(sw.children,function(x){x.classList.remove(\"sel\");});s.classList.add(\"sel\");}});sw.appendChild(s);});\n var envSel=el(\"select\",{class:\"select\"});[\"\",\"DEV\",\"QAS\",\"PROD\",\"SANDBOX\",\"CUSTOM\"].forEach(function(en){envSel.appendChild(el(\"option\",{value:en,text:en||\"(none)\"}));});envSel.value=sel.env;\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Edit connection\"}),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Display name\"}),nameI]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Color\"}),sw]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Environment\"}),envSel]),el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Save\",onclick:function(){api(\"POST\",\"/api/connections/update\",{id:c.id,name:nameI.value.trim()||c.name,color:sel.color,environment:envSel.value}).then(function(){closeModal();return loadConnections();}).then(function(){updateTopBadges();logMsg(\"Connection updated.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});}})])]);openModal(d);}\n\n/* ====================================================================\n OBJECT TREE (DBeaver-style, lazy)\n ==================================================================== */\nfunction treeNode(opts){\n var chev=el(\"span\",{class:\"tchev\"+(opts.leaf?\" leaf\":\"\"),html:\"\\u203a\"});\n var label=el(\"span\",{class:\"tlabel\",title:opts.label});if(opts.labelHtml)label.innerHTML=opts.labelHtml;else label.textContent=opts.label;\n var badge=el(\"span\",{class:\"tbadge\"});\n var spin=el(\"span\",{class:\"hidden\"});\n var row=el(\"div\",{class:\"trow\"},[chev,icEl(opts.icon,opts.iconCls),label,badge,spin]);\n var kids=el(\"div\",{class:\"tchildren hidden\"});\n var node=el(\"div\",{class:\"tnode\"},[row,kids]);\n node._loaded=false;node._open=false;\n function setLoading(on){spin.className=on?\"spin\":\"hidden\";}\n function setBadge(t){badge.textContent=t==null?\"\":\"(\"+t+\")\";}\n function expand(){if(opts.leaf)return;node._open=true;chev.classList.add(\"open\");kids.classList.remove(\"hidden\");if(!node._loaded&&opts.onExpand){node._loaded=true;setLoading(true);Promise.resolve(opts.onExpand(kids,setBadge)).catch(function(e){kids.appendChild(el(\"div\",{class:\"tnote\",text:\"Error: \"+e.message}));}).finally(function(){setLoading(false);});}}\n function collapse(){node._open=false;chev.classList.remove(\"open\");kids.classList.add(\"hidden\");}\n function toggle(){node._open?collapse():expand();}\n if(!opts.leaf)chev.addEventListener(\"click\",function(e){e.stopPropagation();toggle();});\n row.addEventListener(\"click\",function(){if(opts.onClick)opts.onClick();else if(!opts.leaf)toggle();});\n if(opts.onDblClick)row.addEventListener(\"dblclick\",opts.onDblClick);\n if(opts.onMenu)row.addEventListener(\"contextmenu\",function(e){e.preventDefault();opts.onMenu(e);});\n node._row=row;node._kids=kids;node._expand=expand;node._reload=function(){node._loaded=false;clear(kids);if(node._open)expand();};\n return node;\n}\nfunction buildTreeForConnection(){var t=clear($(\"tree\"));var c=activeConn();if(!c){t.appendChild(el(\"div\",{class:\"tnote\",text:\"Select a connection.\"}));return;}\n var root=treeNode({label:c.name,icon:\"db\",iconCls:\"db\",onExpand:function(kids){\n var cat=treeNode({label:\"Catalog\",icon:\"fld\",iconCls:\"fld\",onExpand:function(k2){\n var schemas=treeNode({label:\"Schemas\",icon:\"sch\",iconCls:\"sch\",onExpand:loadSchemasNode});\n k2.appendChild(schemas);schemas._expand();\n }});\n kids.appendChild(cat);cat._expand();\n }});\n t.appendChild(root);root._expand();\n}\nfunction loadSchemasNode(kids,setBadge){return api(\"GET\",\"/api/catalog/schemas?\"+qstr({connectionId:S.activeConnId})).then(function(r){var schemas=r.schemas||[];setBadge(schemas.length);var c=activeConn();var preferred=c&&c.schema;schemas.sort(function(a,b){return (a.isSystem?1:0)-(b.isSystem?1:0);});schemas.forEach(function(s){kids.appendChild(schemaNode(s));});var pref=schemas.filter(function(s){return s.name===preferred;})[0]||schemas.filter(function(s){return !s.isSystem;})[0];if(pref){S.activeSchema=pref.name;updateTopBadges();}});}\nfunction schemaNode(s){return treeNode({label:s.name,icon:\"sch\",iconCls:\"sch\",onClick:function(){S.activeSchema=s.name;updateTopBadges();},onExpand:function(kids){\n var folders=[[\"Tables\",\"table\",\"tbl\"],[\"Views\",\"view\",\"viw\"],[\"Procedures\",\"procedure\",\"prc\"],[\"Functions\",\"function\",\"fun\"],[\"Synonyms\",\"synonym\",\"syn\"],[\"Indexes\",\"index\",\"idx\"]];\n folders.forEach(function(f){kids.appendChild(folderNode(s.name,f[0],f[1],f[2]));});\n}});}\nfunction folderNode(schema,label,kind,iconCls){return treeNode({label:label,icon:\"fld\",iconCls:\"fld\",onExpand:function(kids,setBadge){\n if(kind===\"index\"){kids.appendChild(el(\"div\",{class:\"tnote\",text:\"Open a table's Structure to view its indexes.\"}));setBadge(null);return;}\n var listBox=el(\"div\");\n var search=el(\"input\",{class:\"input\",placeholder:\"Search \"+label.toLowerCase()+\"...\"});\n var sb=el(\"div\",{class:\"searchbox tsearch\"},[el(\"span\",{html:svgFor(\"search\")}),search]);\n kids.appendChild(sb);kids.appendChild(listBox);\n var run=function(){var sp=el(\"span\",{class:\"spin\"});clear(listBox).appendChild(el(\"div\",{class:\"tnote\"},[sp,\" loading...\"]));var q=search.value||\"\";api(\"GET\",\"/api/catalog/objects?\"+qstr({connectionId:S.activeConnId,schema:schema,kinds:kind,search:q})).then(function(r){var objs=r.objects||[];setBadge(objs.length);clear(listBox);if(!objs.length){listBox.appendChild(el(\"div\",{class:\"tnote\",text:q?\"No results found\":\"None.\"}));return;}objs.forEach(function(o){listBox.appendChild(objectNode(schema,o,iconCls,q));});}).catch(function(e){clear(listBox).appendChild(el(\"div\",{class:\"tnote\",text:e.message}));});};\n search.addEventListener(\"input\",debounce(run,250));wireSearch(search,run);\n run();\n}});}\nfunction objectNode(schema,o,iconCls,q){var canData=o.kind===\"table\"||o.kind===\"view\"||o.kind===\"column-view\";return treeNode({label:o.name,labelHtml:highlightMatch(o.name,q),icon:iconCls,iconCls:iconCls,leaf:true,\n onClick:function(){selectTreeRow(this);},\n onDblClick:canData?function(){openDataTab(schema,o.name);}:null,\n onMenu:canData?function(e){objectMenu(e,schema,o);}:function(e){objectMenu(e,schema,o,true);}\n});}\nvar _selRow=null;\nfunction selectTreeRow(node){if(_selRow)_selRow._row.classList.remove(\"sel\");_selRow=node;node._row.classList.add(\"sel\");}\nfunction objectMenu(e,schema,o,limited){var qn='\"'+schema+'\".\"'+o.name+'\"';var items=[];if(!limited){items.push({label:\"Open Data\",icon:\"table2\",onClick:function(){openDataTab(schema,o.name);}});items.push({label:\"Open Structure\",icon:\"col\",onClick:function(){openStructureTab(schema,o.name);}});items.push({sep:true});items.push({label:\"Generate SELECT\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/sql\",{connectionId:S.activeConnId,schema:schema,table:o.name,limit:100}).then(function(r){openSqlTab(r.select,o.name);});}});items.push({label:\"Generate COUNT\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/sql\",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){openSqlTab(r.count,o.name);});}});}\n items.push({label:\"Copy Full Name\",icon:\"col\",onClick:function(){navigator.clipboard.writeText(qn);logMsg(\"Copied \"+qn,\"ok\");}});\n items.push({label:\"Copy Name\",icon:\"col\",onClick:function(){navigator.clipboard.writeText(o.name);logMsg(\"Copied \"+o.name,\"ok\");}});\n if(!limited){items.push({sep:true});\n items.push({label:\"Copy SELECT\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/generate-sql\",{connectionId:S.activeConnId,schema:schema,table:o.name,limit:100}).then(function(r){navigator.clipboard.writeText(r.select);logMsg(\"Copied SELECT\",\"ok\");}).catch(function(er){logMsg(er.message,\"err\");});}});\n items.push({label:\"Copy INSERT template\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/generate-sql\",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){navigator.clipboard.writeText(r.insert);logMsg(\"Copied INSERT template\",\"ok\");}).catch(function(er){logMsg(er.message,\"err\");});}});\n items.push({label:\"Copy UPDATE template\",icon:\"sql\",onClick:function(){api(\"POST\",\"/api/table/generate-sql\",{connectionId:S.activeConnId,schema:schema,table:o.name}).then(function(r){navigator.clipboard.writeText(r.update);logMsg(\"Copied UPDATE template\",\"ok\");}).catch(function(er){logMsg(er.message,\"err\");});}});\n }\n showCtx(e.clientX,e.clientY,items);}\n\n/* ====================================================================\n WORKSPACE TABS\n ==================================================================== */\nvar _dragTabId=null;\nfunction orderedTabs(){return S.tabs.slice().sort(function(a,b){return (b.pinned?1:0)-(a.pinned?1:0);});}\nfunction renderTabBar(){var bar=clear($(\"tabbar\"));orderedTabs().forEach(function(tab){var chip=el(\"div\",{class:\"wtab\"+(tab.id===S.activeTabId?\" active\":\"\")+(tab.pinned?\" pinned\":\"\"),draggable:\"true\"});chip.addEventListener(\"click\",function(){switchTab(tab.id);});chip.addEventListener(\"contextmenu\",function(e){e.preventDefault();tabMenu(e,tab);});\n chip.addEventListener(\"dragstart\",function(e){_dragTabId=tab.id;chip.classList.add(\"dragging\");if(e.dataTransfer)e.dataTransfer.effectAllowed=\"move\";});\n chip.addEventListener(\"dragend\",function(){chip.classList.remove(\"dragging\");Array.prototype.forEach.call(bar.children,function(x){x.classList.remove(\"dragover\");});});\n chip.addEventListener(\"dragover\",function(e){e.preventDefault();chip.classList.add(\"dragover\");});\n chip.addEventListener(\"dragleave\",function(){chip.classList.remove(\"dragover\");});\n chip.addEventListener(\"drop\",function(e){e.preventDefault();chip.classList.remove(\"dragover\");if(_dragTabId&&_dragTabId!==tab.id)reorderTab(_dragTabId,tab.id);});\n chip.appendChild(el(\"span\",{class:\"t-ico\"+(tab.pinned?\" pin\":\"\"),html:svgFor(tab.pinned?\"star\":(tab.icon||\"sql\"))}));\n chip.appendChild(el(\"span\",{class:\"t-title\",text:tab.title,title:tab.title}));\n if(tab.dirty)chip.appendChild(el(\"span\",{class:\"dot\"}));\n if(tab.closable!==false)chip.appendChild(el(\"span\",{class:\"x\",html:svgFor(\"x\"),onclick:function(e){e.stopPropagation();closeTab(tab.id);}}));\n bar.appendChild(chip);});}\nfunction reorderTab(srcId,targetId){var src=tabById(srcId);var ti=-1;for(var i=0;i<S.tabs.length;i++)if(S.tabs[i].id===targetId)ti=i;if(!src||ti<0)return;S.tabs=S.tabs.filter(function(t){return t.id!==srcId;});var idx=-1;for(var j=0;j<S.tabs.length;j++)if(S.tabs[j].id===targetId)idx=j;S.tabs.splice(idx,0,src);renderTabBar();scheduleWorkspaceSave();}\nfunction tabMenu(e,tab){showCtx(e.clientX,e.clientY,[\n {label:\"Close\",icon:\"x\",onClick:function(){closeTab(tab.id);}},\n {label:\"Close Others\",icon:\"x\",onClick:function(){closeOtherTabs(tab.id);}},\n {label:\"Close Tabs to the Right\",icon:\"x\",onClick:function(){closeTabsToRight(tab.id);}},\n {sep:true},\n {label:tab.pinned?\"Unpin Tab\":\"Pin Tab\",icon:\"star\",onClick:function(){tab.pinned=!tab.pinned;renderTabBar();scheduleWorkspaceSave();}},\n {label:\"Rename Tab\",icon:\"gear\",onClick:function(){var n=prompt(\"Tab name\",tab.title);if(n){tab.title=n;renderTabBar();scheduleWorkspaceSave();}}},\n {label:\"Duplicate Tab\",icon:\"plus\",onClick:function(){duplicateTab(tab);}}\n]);}\nfunction duplicateTab(tab){if(tab.kind===\"sql\"&&tab.state.editor)openSqlTab(tab.state.editor.value,tab.title);else if(tab.kind===\"data\"&&tab.meta)openDataTab(tab.meta.schema,tab.meta.table);else if(tab.kind===\"structure\"&&tab.meta)openStructureTab(tab.meta.schema,tab.meta.table);}\nfunction closeOtherTabs(keepId){var others=S.tabs.filter(function(t){return t.id!==keepId&&t.closable!==false;});if(others.some(function(t){return t.dirty;})&&!confirm(\"Close other tabs? Unsaved changes will be lost.\"))return;others.forEach(function(t){t.el.remove();});S.tabs=S.tabs.filter(function(t){return t.id===keepId||t.closable===false;});switchTab(keepId);renderTabBar();scheduleWorkspaceSave();}\nfunction closeTabsToRight(fromId){var ord=orderedTabs();var i=0;for(var k=0;k<ord.length;k++)if(ord[k].id===fromId)i=k;var toClose=ord.slice(i+1).filter(function(t){return t.closable!==false;});if(toClose.some(function(t){return t.dirty;})&&!confirm(\"Close tabs to the right? Unsaved changes will be lost.\"))return;var ids={};toClose.forEach(function(t){ids[t.id]=1;t.el.remove();});S.tabs=S.tabs.filter(function(t){return !ids[t.id];});if(ids[S.activeTabId])switchTab(fromId);renderTabBar();scheduleWorkspaceSave();}\nfunction nextTab(dir){var ord=orderedTabs();if(ord.length<2)return;var i=0;for(var k=0;k<ord.length;k++)if(ord[k].id===S.activeTabId)i=k;var ni=(i+dir+ord.length)%ord.length;switchTab(ord[ni].id);}\nfunction openTab(spec){var ex=S.tabs.filter(function(t){return t.key===spec.key;})[0];if(ex){switchTab(ex.id);return ex;}var id=spec.restoreId||(\"wt\"+(++S.seq));var pane=el(\"div\",{class:\"tabpane hidden\"});$(\"tabcontent\").appendChild(pane);var now=new Date().toISOString();var tab={id:id,key:spec.key,kind:spec.kind,title:spec.title,icon:spec.icon,dirty:false,closable:spec.closable!==false,el:pane,state:{},connectionId:spec.connectionId||\"\",meta:spec.meta||null,pinned:!!spec.pinned,openedAt:now};S.tabs.push(tab);spec.build(pane,tab);renderTabBar();switchTab(id);scheduleWorkspaceSave();return tab;}\nfunction switchTab(id){S.activeTabId=id;S.tabs.forEach(function(t){t.el.classList.toggle(\"hidden\",t.id!==id);});renderTabBar();var tab=tabById(id);if(tab&&tab.onShow)tab.onShow();updatePendingStatus();scheduleWorkspaceSave();}\nfunction tabById(id){return S.tabs.filter(function(t){return t.id===id;})[0];}\nfunction setDirty(tab,on){tab.dirty=on;renderTabBar();updatePendingStatus();}\nfunction closeTab(id){var tab=tabById(id);if(!tab)return;if(tab.dirty&&!confirm(\"'\"+tab.title+\"' has unsaved changes. Close anyway?\"))return;tab.el.remove();var idx=S.tabs.indexOf(tab);S.tabs=S.tabs.filter(function(t){return t.id!==id;});if(S.activeTabId===id){var next=S.tabs[Math.max(0,idx-1)];if(next)switchTab(next.id);else openWelcome();}renderTabBar();scheduleWorkspaceSave();}\nfunction updatePendingStatus(){var tab=tabById(S.activeTabId);var n=tab&&tab.state&&tab.state.g?pendingCount(tab.state.g):0;$(\"stPending\").textContent=n>0?(n+\" pending change\"+(n>1?\"s\":\"\")):\"\";$(\"stPending\").className=n>0?\"st-item st-pending\":\"st-item\";}\n\n/* ---- workspace persistence ---- */\nfunction kindToType(k){return k===\"data\"?\"data-grid\":k===\"structure\"?\"metadata\":k;}\nfunction serializeWorkspace(){var tabs=S.tabs.filter(function(t){return t.kind!==\"welcome\";}).map(function(t){var st={id:t.id,type:kindToType(t.kind),title:t.title,pinned:!!t.pinned,dirty:!!t.dirty,connectionId:t.connectionId||undefined,openedAt:t.openedAt||new Date().toISOString(),updatedAt:new Date().toISOString()};if(t.meta){st.schema=t.meta.schema;st.objectName=t.meta.table;st.objectType=t.meta.objectType;}if(t.kind===\"sql\"&&t.state.editor)st.sql=t.state.editor.value;if(t.kind===\"data\"&&t.state.g){var g=t.state.g;st.filter=g.whereI.value;st.pageSize=parseInt(g.pageSel.value,10);st.pageIndex=g.offset;st.sort=g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[];}return st;});return {version:1,activeTabId:S.activeTabId,tabs:tabs,tabGroups:[],layout:{readOnly:S.readOnly,sidebarWidth:$(\"sidebar\").offsetWidth},updatedAt:new Date().toISOString()};}\nvar _wsTimer=null;\nfunction scheduleWorkspaceSave(){if(_wsTimer)clearTimeout(_wsTimer);_wsTimer=setTimeout(function(){api(\"PUT\",\"/api/studio/workspace\",serializeWorkspace()).catch(function(){});},Math.max(400,S.settings.autoSaveDelayMs||500));}\nfunction loadSettings(){return api(\"GET\",\"/api/studio/settings\").then(function(r){if(r.settings)S.settings=r.settings;}).catch(function(){});}\nfunction restoreWorkspace(){if(!S.settings.restoreWorkspace)return Promise.resolve();return api(\"GET\",\"/api/studio/workspace\").then(function(r){var ws=r.workspace;if(!ws||!ws.tabs||!ws.tabs.length)return;var active=null;ws.tabs.forEach(function(st){try{\n if(st.type===\"sql\"){if(st.connectionId){var c=byId(st.connectionId);if(c){S.activeConnId=c.id;S.connType=c.type;}}var t=openSqlTab(st.sql||\"\",null);t.title=st.title;t.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=t.id;}\n else if(st.type===\"data-grid\"){var cd=byId(st.connectionId);if(!cd){logMsg(\"Tab '\"+st.title+\"' not restored: connection removed.\",\"warn\");return;}S.activeConnId=cd.id;S.connType=cd.type;var dt=openDataTab(st.schema,st.objectName,{where:st.filter,pageSize:st.pageSize,sort:st.sort,offset:st.pageIndex});dt.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=dt.id;}\n else if(st.type===\"metadata\"){var cm=byId(st.connectionId);if(!cm)return;S.activeConnId=cm.id;S.connType=cm.type;var mt=openStructureTab(st.schema,st.objectName);mt.pinned=!!st.pinned;if(st.id===ws.activeTabId)active=mt.id;}\n}catch(e){}});updateTopBadges();renderTabBar();if(active)switchTab(active);logMsg(\"Workspace restored (\"+ws.tabs.length+\" tab\"+(ws.tabs.length>1?\"s\":\"\")+\").\",\"ok\");}).catch(function(){});}\nfunction byId(id){return S.connections.filter(function(c){return c.id===id;})[0];}\n\n/* ====================================================================\n WELCOME\n ==================================================================== */\nfunction openWelcome(){openTab({key:\"welcome\",kind:\"welcome\",title:\"Welcome\",icon:\"home\",closable:false,build:buildWelcome});}\nfunction buildWelcome(pane){var w=el(\"div\",{class:\"welcome\"});w.appendChild(el(\"h1\",{text:\"SimpleMDG CF DB Studio\"}));w.appendChild(el(\"div\",{class:\"lede\",text:\"A local HANA / PostgreSQL explorer with BTP credential import. Local only \\u00b7 127.0.0.1\"}));\n var cards=el(\"div\",{class:\"wcards\"});\n cards.appendChild(wcard(\"imp\",\"Import from BTP App\",\"Read cf env and detect HANA/PostgreSQL credentials.\",openBtpWizard));\n cards.appendChild(wcard(\"plus\",\"Add direct connection\",\"Connect by host/port/user like DBeaver.\",function(){newConnModal();}));\n cards.appendChild(wcard(\"sql\",\"Open SQL Console\",\"Write and run SQL with safety checks.\",function(){if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");openSqlTab();}));\n cards.appendChild(wcard(\"db\",\"Connect to cached DB\",\"Pick a saved connection from the left.\",function(){if(S.connections[0])activateConnection(S.connections[0].id);}));\n w.appendChild(cards);\n var cols=el(\"div\",{class:\"wcols\"});\n var recent=el(\"div\",{class:\"wcol\"},[el(\"h4\",{text:\"Recent connections\"})]);var rl=el(\"div\",{class:\"wlist\"});S.connections.slice(0,5).forEach(function(c){rl.appendChild(el(\"div\",{class:\"wli\",onclick:function(){activateConnection(c.id);}},[el(\"b\",{text:c.name}),el(\"div\",{class:\"note\",text:(c.type===\"hana\"?\"HANA\":\"PostgreSQL\")+\" \\u00b7 \"+(c.org||c.host)})]));});if(!S.connections.length)rl.appendChild(el(\"div\",{class:\"empty\",text:\"None yet.\"}));recent.appendChild(rl);\n var rq=el(\"div\",{class:\"wcol\"},[el(\"h4\",{text:\"Recent queries\"})]);var ql=el(\"div\",{class:\"wlist\"});S.savedQueries.slice(0,5).forEach(function(q){ql.appendChild(el(\"div\",{class:\"wli\",onclick:function(){openSqlTab(q.sql,q.name);}},[el(\"b\",{text:q.name}),el(\"div\",{class:\"note\",text:(q.connectionType||\"\")+\" \\u00b7 \"+new Date(q.updatedAt).toLocaleString()})]));});if(!S.savedQueries.length)ql.appendChild(el(\"div\",{class:\"empty\",text:\"None yet.\"}));rq.appendChild(ql);\n cols.appendChild(recent);cols.appendChild(rq);w.appendChild(cols);\n pane.appendChild(w);\n}\nfunction wcard(icon,title,desc,onClick){return el(\"div\",{class:\"wcard\",onclick:onClick},[el(\"div\",{class:\"wc-ic\",html:svgFor(icon)}),el(\"h3\",{text:title}),el(\"p\",{text:desc})]);}\n\n/* ====================================================================\n SQL CONSOLE TAB\n ==================================================================== */\nvar DANGER=/\\b(drop|truncate|alter|grant|revoke)\\b/i;\nfunction openSqlTab(sql,nameHint,queryId){var title=\"SQL\"+(nameHint?\": \"+nameHint:\" Console\");return openTab({key:\"sql:\"+(++S.seq),kind:\"sql\",title:title,icon:\"sql\",connectionId:S.activeConnId,meta:{queryId:queryId||null},build:function(pane,tab){tab.connectionId=S.activeConnId;buildSqlPane(pane,tab,sql!=null?sql:\"select * from DUMMY\");}});}\nfunction buildSqlPane(pane,tab,initialSql){\n var editor=el(\"textarea\",{class:\"editor\",spellcheck:\"false\"});editor.value=initialSql;tab.state.editor=editor;\n var gutter=el(\"div\",{class:\"gutter\",text:\"1\"});\n function syncGutter(){var lines=editor.value.split(\"\\n\").length;var s=\"\";for(var i=1;i<=lines;i++)s+=i+\"\\n\";gutter.textContent=s;gutter.scrollTop=editor.scrollTop;}\n tab.state.syncGutter=syncGutter;\n editor.addEventListener(\"input\",function(){setDirty(tab,true);syncGutter();scheduleWorkspaceSave();});\n editor.addEventListener(\"scroll\",function(){gutter.scrollTop=editor.scrollTop;});\n editor.addEventListener(\"keydown\",function(e){if((e.ctrlKey||e.metaKey)&&e.key===\"Enter\"){e.preventDefault();runMode(tab,\"selected\");}else if(e.key===\"F5\"){e.preventDefault();runMode(tab,\"all\");}else if((e.ctrlKey||e.metaKey)&&(e.key===\"s\"||e.key===\"S\")){e.preventDefault();saveQueryTab(tab);}});\n var editwrap=el(\"div\",{class:\"editwrap\"},[gutter,editor]);\n var limitSel=el(\"select\",{class:\"select\",style:\"width:auto\"});[\"100\",\"500\",\"1000\",\"5000\",\"0\"].forEach(function(v){limitSel.appendChild(el(\"option\",{value:v,text:v===\"0\"?\"No limit\":v}));});limitSel.value=String(S.settings.defaultRowLimit||100);tab.state.limit=limitSel;\n var runBtn=el(\"button\",{class:\"btn\",onclick:function(){runMode(tab,\"selected\");}},[el(\"span\",{html:svgFor(\"run\")}),\" Run\"]);tab.state.runBtn=runBtn;\n var runMenu=el(\"button\",{class:\"btn\",style:\"padding:6px 7px\",html:\"\\u25be\",title:\"Run options\",onclick:function(e){showCtx(e.clientX,e.clientY,[{label:\"Run Selected (Ctrl+Enter)\",icon:\"run\",onClick:function(){runMode(tab,\"selected\");}},{label:\"Run Current Statement\",icon:\"run\",onClick:function(){runMode(tab,\"current\");}},{label:\"Run All (F5)\",icon:\"run\",onClick:function(){runMode(tab,\"all\");}},{label:\"Explain\",icon:\"sql\",onClick:function(){runMode(tab,\"explain\");}}]);}});\n var tb=el(\"div\",{class:\"toolbar\"},[runBtn,runMenu,el(\"button\",{class:\"btn sec\",text:\"Format\",onclick:function(){api(\"POST\",\"/api/sql/format\",{sql:editor.value}).then(function(r){setEditorValue(tab,r.sql);}).catch(function(){setEditorValue(tab,formatSql(editor.value));});}}),el(\"span\",{class:\"note\",text:\"Limit\"}),limitSel,el(\"button\",{class:\"btn ghost\",text:\"Save\",title:\"Ctrl+S\",onclick:function(){saveQueryTab(tab);}}),el(\"button\",{class:\"btn ghost\",text:\"CSV\",onclick:function(){exportResult(tab,\"csv\");}}),el(\"button\",{class:\"btn ghost\",text:\"JSON\",onclick:function(){exportResult(tab,\"json\");}}),el(\"span\",{class:\"grow\"}),el(\"span\",{class:\"note\",id:\"sqlmeta_\"+tab.id})]);\n var body=el(\"div\",{class:\"pane-body\"});var errBox=el(\"div\",{class:\"errbox hidden\"});var grid=el(\"div\",{class:\"gridwrap\"});tab.state.err=errBox;tab.state.grid=grid;\n body.appendChild(editwrap);body.appendChild(errBox);body.appendChild(el(\"div\",{class:\"note\",text:\"Result\"}));body.appendChild(grid);\n pane.appendChild(tb);pane.appendChild(body);syncGutter();\n}\nfunction setEditorValue(tab,v){tab.state.editor.value=v;if(tab.state.syncGutter)tab.state.syncGutter();setDirty(tab,true);scheduleWorkspaceSave();}\nfunction splitRangesClient(sql){var ranges=[],buf=\"\",start=-1,inStr=false,q=\"\",inLine=false,inBlock=false;function push(e){if(buf.trim())ranges.push({sql:buf.trim(),start:start,end:e});buf=\"\";start=-1;}for(var i=0;i<sql.length;i++){var ch=sql[i],nx=sql[i+1];if(start===-1&&!/\\s/.test(ch))start=i;if(inLine){buf+=ch;if(ch===\"\\n\")inLine=false;continue;}if(inBlock){buf+=ch;if(ch===\"*\"&&nx===\"/\"){buf+=nx;i++;inBlock=false;}continue;}if(inStr){buf+=ch;if(ch===q)inStr=false;continue;}if(ch===\"-\"&&nx===\"-\"){inLine=true;buf+=ch;continue;}if(ch===\"/\"&&nx===\"*\"){inBlock=true;buf+=ch;continue;}if(ch===\"'\"||ch==='\"'){inStr=true;q=ch;buf+=ch;continue;}if(ch===\";\"){push(i);continue;}buf+=ch;}push(sql.length);return ranges;}\nfunction statementAtCursor(sql,off){var r=splitRangesClient(sql);for(var i=0;i<r.length;i++)if(off>=r[i].start&&off<=r[i].end+1)return r[i].sql;return (r[r.length-1]||{}).sql||sql.trim();}\nfunction runMode(tab,mode){var ed=tab.state.editor;var hasSel=ed.selectionStart!=ed.selectionEnd;var sel=hasSel?ed.value.substring(ed.selectionStart,ed.selectionEnd).trim():\"\";var sql;if(mode===\"all\")sql=ed.value.trim();else if(mode===\"current\")sql=statementAtCursor(ed.value,ed.selectionStart);else if(mode===\"explain\"){if(S.connType!==\"postgresql\")return logMsg(\"Explain currently supports PostgreSQL.\",\"warn\");sql=\"EXPLAIN \"+(sel||statementAtCursor(ed.value,ed.selectionStart));}else sql=sel||statementAtCursor(ed.value,ed.selectionStart);if(!sql)return logMsg(\"Nothing to run.\",\"warn\");execSql(tab,sql);}\nfunction execSql(tab,sql,confirmed){if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");\n if(!confirmed&&DANGER.test(sql)){if(!confirm(\"This statement may modify or drop data:\\n\\n\"+sql.slice(0,160)+\"\\n\\nRun anyway?\"))return;}\n tab.state.err.classList.add(\"hidden\");tab.state.runBtn.disabled=true;tab.state.runBtn.innerHTML=\"\";tab.state.runBtn.appendChild(el(\"span\",{class:\"spin\"}));tab.state.runBtn.appendChild(document.createTextNode(\" Running...\"));setRun(true);setConnStatus(\"Running query...\",\"run\");\n var limit=parseInt(tab.state.limit.value,10);\n api(\"POST\",\"/api/query/run\",{connectionId:S.activeConnId,sql:sql,limit:limit,readOnly:S.readOnly,confirmDangerous:true}).then(function(r){\n tab.state.runBtn.innerHTML=svgFor(\"run\")+\" Run\";tab.state.runBtn.disabled=false;setConnStatus(\"Connected\",\"ok\");\n if(r.blocked){tab.state.err.textContent=\"Read-only mode blocks: \"+(r.safety&&r.safety.matchedKeywords?r.safety.matchedKeywords.join(\", \"):\"write/DDL\");tab.state.err.classList.remove(\"hidden\");return;}\n if(!r.ok){tab.state.err.textContent=\"SQL failed (\"+(S.connType===\"hana\"?\"HANA\":\"PostgreSQL\")+\")\\n\"+r.error;tab.state.err.classList.remove(\"hidden\");return;}\n tab.state.lastResult=r.result;renderResultGrid(tab.state.grid,r.result,null);\n $(\"sqlmeta_\"+tab.id).textContent=\"Rows: \"+r.result.rowCount+(r.result.affectedRows!=null?\" \\u00b7 Affected: \"+r.result.affectedRows:\"\")+\" \\u00b7 \"+r.result.durationMs+\"ms\"+(r.result.truncated?\" \\u00b7 truncated\":\"\");\n $(\"stDuration\").textContent=r.result.durationMs+\"ms\";$(\"stRows\").textContent=r.result.rowCount+\" rows\";\n }).catch(function(e){tab.state.runBtn.innerHTML=svgFor(\"run\")+\" Run\";tab.state.runBtn.disabled=false;setConnStatus(\"Connected\",\"ok\");tab.state.err.textContent=e.message;tab.state.err.classList.remove(\"hidden\");});\n}\nfunction saveQueryTab(tab){var sql=tab.state.editor.value.trim();if(!sql)return logMsg(\"Nothing to save.\",\"warn\");var qid=tab.meta&&tab.meta.queryId;if(qid){api(\"PUT\",\"/api/queries/\"+encodeURIComponent(qid),{name:tab.title.replace(/^SQL: ?/,\"\"),sql:sql}).then(function(){setDirty(tab,false);loadSavedQueries();logMsg(\"Query updated.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});return;}var name=prompt(\"Save query as\",\"Query \"+new Date().toLocaleString());if(!name)return;api(\"POST\",\"/api/queries\",{name:name,sql:sql,connectionId:S.activeConnId,connectionType:S.connType}).then(function(r){if(tab.meta)tab.meta.queryId=r.query.id;tab.title=\"SQL: \"+name;setDirty(tab,false);renderTabBar();loadSavedQueries();logMsg(\"Query saved.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});}\nfunction exportResult(tab,fmt){var res=tab.state.lastResult;if(!res||!res.rows.length)return logMsg(\"No result to export.\",\"warn\");var fields=res.fields&&res.fields.length?res.fields:Object.keys(res.rows[0]);fetch(fmt===\"csv\"?\"/api/export/csv\":\"/api/export/json\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({fields:fields,rows:res.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement(\"a\");a.href=URL.createObjectURL(b);a.download=fmt===\"csv\"?\"result.csv\":\"result.json\";a.click();logMsg(\"Exported \"+fmt.toUpperCase(),\"ok\");});}\nfunction formatSql(sql){return sql.replace(/\\s+/g,\" \").replace(/\\b(select|from|where|and|or|order by|group by|having|limit|offset|inner join|left join|right join|join|on|union|values|set|insert into|update|delete from|create table|alter table)\\b/gi,function(m){return \"\\n\"+m.toUpperCase();}).trim();}\n\n/* generic read-only result grid (sql console) */\nfunction renderResultGrid(box,result,onSort){clear(box);if(!result||!result.rows||!result.rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:result&&result.affectedRows!=null?(\"Affected rows: \"+result.affectedRows):\"No rows.\"}));return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var table=el(\"table\",{class:\"grid\"});var thead=el(\"thead\");var htr=el(\"tr\");htr.appendChild(el(\"th\",{class:\"rowhdr\",text:\"#\"}));fields.forEach(function(f){htr.appendChild(el(\"th\",{text:f,title:f}));});thead.appendChild(htr);table.appendChild(thead);var tb=el(\"tbody\");result.rows.forEach(function(row,ri){var tr=el(\"tr\");tr.appendChild(el(\"td\",{class:\"rowhdr\",text:ri+1}));fields.forEach(function(f){var v=row[f];var disp=v==null?\"\":typeof v===\"object\"?JSON.stringify(v):String(v);var td=el(\"td\",{class:typeof v===\"number\"?\"num\":\"\",title:disp,text:disp.length>400?disp.slice(0,400)+\"\\u2026\":disp});td.addEventListener(\"dblclick\",function(){openCellViewer(v);});tr.appendChild(td);});tb.appendChild(tr);});table.appendChild(tb);box.appendChild(table);}\n\n/* ====================================================================\n DATA GRID TAB (editable, pending changes)\n ==================================================================== */\nfunction openDataTab(schema,table,restore){return openTab({key:\"data:\"+S.activeConnId+\":\"+schema+\".\"+table,kind:\"data\",title:table,icon:\"table2\",connectionId:S.activeConnId,meta:{schema:schema,table:table,objectType:\"table\"},build:function(pane,tab){tab.connectionId=S.activeConnId;buildDataPane(pane,tab,schema,table,restore);}});}\nvar _pop=null;\nfunction showPopover(node,anchor){closePop();_pop=node;node.style.position=\"fixed\";document.body.appendChild(node);var r=anchor.getBoundingClientRect();node.style.left=Math.min(r.left,window.innerWidth-node.offsetWidth-10)+\"px\";node.style.top=(r.bottom+6)+\"px\";setTimeout(function(){document.addEventListener(\"mousedown\",popOutside,true);},0);}\nfunction popOutside(e){if(_pop&&!_pop.contains(e.target))closePop();}\nfunction closePop(){if(_pop){_pop.remove();_pop=null;document.removeEventListener(\"mousedown\",popOutside,true);}}\nfunction showFilterSql(tab,anchor){var g=tab.state.g;api(\"POST\",\"/api/sql/generate-table-query\",{connectionId:S.activeConnId,schema:g.schema,table:g.table,where:g.whereI.value||\"\",sort:g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[],limit:parseInt(g.pageSel.value,10),offset:g.offset}).then(function(r){var pre=el(\"pre\",{text:r.sql});var pop=el(\"div\",{class:\"popover\"},[el(\"div\",{class:\"row\",style:\"margin-bottom:8px\"},[el(\"b\",{text:\"Generated SQL\"}),el(\"span\",{style:\"flex:1\"}),el(\"button\",{class:\"btn sm sec\",text:\"Copy\",onclick:function(){navigator.clipboard.writeText(r.sql);logMsg(\"Copied SQL\",\"ok\");}}),el(\"button\",{class:\"btn sm\",text:\"Open in Console\",onclick:function(){closePop();openSqlTab(r.sql,g.table);}}),el(\"button\",{class:\"btn sm ghost\",text:\"Close\",onclick:closePop})]),pre]);showPopover(pop,anchor);}).catch(function(e){logMsg(e.message,\"err\");});}\nfunction pendingCount(g){return Object.keys(g.edits).length+Object.keys(g.deletes).length+g.inserts.length;}\nfunction rowKeyOf(g,row){return g.pk.map(function(k){return String(row[k]);}).join(\"\\u0001\");}\nfunction buildDataPane(pane,tab,schema,table,restore){\n var g={schema:schema,table:table,pk:[],columns:[],rows:[],offset:0,pageSize:100,where:\"\",orderBy:\"\",orderDir:\"asc\",total:null,edits:{},deletes:{},inserts:[],errors:{},editable:false,sel:{},iseq:0,undo:[],redo:[]};tab.state.g=g;\n var whereI=el(\"input\",{spellcheck:\"false\",placeholder:\"WHERE clause, e.g. STATUS = 'A' AND CREATEDBY LIKE '%admin%'\"});g.whereI=whereI;\n var clr=el(\"span\",{class:\"clr\",html:svgFor(\"x\"),title:\"Clear filter\"});\n var whereBox=el(\"div\",{class:\"wherebox\"},[el(\"span\",{html:svgFor(\"filter\")}),whereI,clr]);\n var apply=function(){g.where=whereI.value;g.offset=0;applyBtn.classList.remove(\"on\");loadData(tab);};\n var applyBtn=gbtn(\"run\",\"Apply filter (Enter)\",apply);g.applyBtn=applyBtn;\n clr.addEventListener(\"click\",function(){whereI.value=\"\";whereBox.classList.remove(\"has\");apply();});\n whereI.addEventListener(\"input\",function(){whereBox.classList.toggle(\"has\",!!whereI.value);applyBtn.classList.toggle(\"on\",whereI.value!==g.where);});\n whereI.addEventListener(\"keydown\",function(e){if(e.key===\"Enter\"){e.preventDefault();apply();if(e.ctrlKey||e.metaKey)showFilterSql(tab,applyBtn);}else if(e.key===\"Escape\"){e.preventDefault();whereI.value=\"\";whereBox.classList.remove(\"has\");apply();}});\n var insBtn=gbtn(\"plus\",\"Insert row\",function(){addInsertRow(tab);});g.insBtn=insBtn;\n var delBtn=gbtn(\"trash\",\"Mark selected rows for delete\",function(){toggleDeleteSelected(tab);},\"danger\");g.delBtn=delBtn;\n var tb=el(\"div\",{class:\"gtoolbar\"},[whereBox,applyBtn,gbtn(\"sql\",\"Show generated SQL\",function(e){showFilterSql(tab,e.currentTarget);}),gbtn(\"refresh\",\"Refresh data\",function(e){var b=e.currentTarget;b.classList.add(\"spinning\");loadData(tab,function(){b.classList.remove(\"spinning\");});}),el(\"span\",{class:\"gsep\"}),insBtn,delBtn,gbtn(\"col\",\"Open structure\",function(){openStructureTab(schema,table);}),el(\"span\",{class:\"gsep\"}),gbtn(\"imp\",\"Export data\",function(e){openExportMenu(tab,e.currentTarget);})]);\n var changeBar=el(\"div\",{class:\"changebar hidden\"});g.changeBar=changeBar;\n var grid=el(\"div\",{class:\"gridwrap\"});g.grid=grid;\n var pageSel=el(\"select\");[\"100\",\"500\",\"1000\"].forEach(function(v){pageSel.appendChild(el(\"option\",{value:v,text:v}));});g.pageSel=pageSel;\n pageSel.addEventListener(\"change\",function(){g.offset=0;loadData(tab);});\n var rangeSpan=el(\"span\",{class:\"note\"});g.rangeSpan=rangeSpan;var durSpan=el(\"span\",{class:\"note\"});g.durSpan=durSpan;\n var footer=el(\"div\",{class:\"gridfoot\"},[rangeSpan,el(\"span\",{style:\"flex:1\"}),el(\"span\",{class:\"pg\"},[gbtn(\"chevL\",\"Previous page\",function(){g.offset=Math.max(0,g.offset-parseInt(pageSel.value,10));loadData(tab);}),el(\"span\",{class:\"note\",text:\"Rows\"}),pageSel,gbtn(\"chevR\",\"Next page\",function(){g.offset+=parseInt(pageSel.value,10);loadData(tab);})]),durSpan]);\n pane.appendChild(crumbs((activeConn()||{}).name,schema,table));pane.appendChild(tb);pane.appendChild(changeBar);pane.appendChild(grid);pane.appendChild(footer);\n updateDirtyButtons(tab);\n api(\"GET\",\"/api/catalog/columns?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.columns=r.columns||[];}).catch(function(){});\n api(\"GET\",\"/api/catalog/primary-key?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.pk=(r.primaryKey&&r.primaryKey.columns)||[];g.editable=g.pk.length>0;insBtn.disabled=!g.editable;delBtn.disabled=!g.editable;insBtn.title=g.editable?\"Insert row\":\"Read-only (no primary key)\";renderGrid(tab);}).catch(function(){});\n if(restore){g.where=restore.where||\"\";whereI.value=g.where;whereBox.classList.toggle(\"has\",!!g.where);g.offset=restore.offset||0;if(restore.pageSize)pageSel.value=String(restore.pageSize);if(restore.sort&&restore.sort[0]){g.orderBy=restore.sort[0].column;g.orderDir=restore.sort[0].direction;}}\n loadData(tab);\n loadCount(tab);\n}\nfunction selectedKeys(g){return Object.keys(g.sel);}\nfunction loadData(tab,onDone){var g=tab.state.g;g.pageSize=parseInt(g.pageSel.value,10);clear(g.grid).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading data...\"]));$(\"stDuration\").textContent=\"\u2026\";api(\"POST\",\"/api/table/data\",{connectionId:S.activeConnId,schema:g.schema,table:g.table,limit:g.pageSize,offset:g.offset,where:g.where,orderBy:g.orderBy,orderDirection:g.orderDir}).then(function(r){g.rows=r.result.rows;g.sel={};renderGrid(tab);var to=g.offset+r.result.rowCount;g.rangeSpan.textContent=\"Showing \"+(r.result.rowCount?g.offset+1:0)+\"-\"+to+(g.total!=null?\" of \"+g.total.toLocaleString():\"\");g.durSpan.textContent=\"Duration: \"+r.result.durationMs+\"ms \u00B7 Offset: \"+g.offset;$(\"stDuration\").textContent=r.result.durationMs+\"ms\";$(\"stRows\").textContent=(g.total!=null?g.total+\" total\":r.result.rowCount+\" rows\");if(onDone)onDone();}).catch(function(e){clear(g.grid).appendChild(el(\"div\",{class:\"errbox\",text:\"Cannot load data.\\nReason: \"+e.message+\"\\nAction: test the connection or refresh from BTP app env.\"}));if(onDone)onDone();});}\nfunction loadCount(tab){var g=tab.state.g;api(\"POST\",\"/api/table/count\",{connectionId:S.activeConnId,schema:g.schema,table:g.table}).then(function(r){g.total=r.count;$(\"stRows\").textContent=r.count+\" total\";}).catch(function(){});}\nfunction dataSortToggle(tab,field){var g=tab.state.g;if(g.orderBy===field)g.orderDir=g.orderDir===\"asc\"?\"desc\":\"asc\";else{g.orderBy=field;g.orderDir=\"asc\";}g.offset=0;loadData(tab);}\nfunction renderGrid(tab){var g=tab.state.g;var box=clear(g.grid);if(!g.rows.length&&!g.inserts.length){box.appendChild(el(\"div\",{class:\"empty\",text:\"No rows.\"}));return;}\n var fields=g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);g.fields=fields;\n var table=el(\"table\",{class:\"grid\"});var thead=el(\"thead\");var htr=el(\"tr\");htr.appendChild(el(\"th\",{class:\"rowhdr\",text:\"#\"}));fields.forEach(function(f){var arrow=g.orderBy===f?(g.orderDir===\"desc\"?\" \\u25BC\":\" \\u25B2\"):\"\";var th=el(\"th\",{title:\"Click to sort\",text:f+arrow});th.addEventListener(\"click\",function(){dataSortToggle(tab,f);});htr.appendChild(th);});thead.appendChild(htr);table.appendChild(thead);\n var tbody=el(\"tbody\");\n g.rows.forEach(function(row,ri){var key=rowKeyOf(g,row);var deleted=!!g.deletes[key];var edited=g.edits[key];var err=g.errors[key];var tr=el(\"tr\",{class:(g.sel[key]?\"selrow \":\"\")+(deleted?\"row-del \":\"\")+(err?\"row-err \":\"\"),\"data-ri\":ri});\n tr.addEventListener(\"contextmenu\",function(e){e.preventDefault();rowContextMenu(e,tab,row);});\n var flag=edited?'<span class=\"rowflag d\"></span>':(deleted?'<span class=\"rowflag del\"></span>':\"\");\n var num=el(\"td\",{class:\"rowhdr\",html:flag+(g.offset+ri+1),title:err||\"\"});num.addEventListener(\"click\",function(e){if(!(e.ctrlKey||e.metaKey||e.shiftKey))g.sel={};if(g.sel[key])delete g.sel[key];else g.sel[key]=true;renderGrid(tab);});tr.appendChild(num);\n fields.forEach(function(f){var hasEdit=edited&&Object.prototype.hasOwnProperty.call(edited,f);var v=hasEdit?edited[f]:row[f];var disp=v==null?\"\":typeof v===\"object\"?JSON.stringify(v):String(v);var td=el(\"td\",{class:(typeof v===\"number\"?\"num \":\"\")+(hasEdit?\"edited\":\"\"),title:disp,text:disp.length>400?disp.slice(0,400)+\"\\u2026\":disp});if(g.editable&&!deleted){td.addEventListener(\"dblclick\",function(){startEdit(tab,td,ri,f,row);});}else{td.addEventListener(\"dblclick\",function(){openCellViewer(v);});}tr.appendChild(td);});\n if(err){tr.title=err;}\n tbody.appendChild(tr);});\n g.inserts.forEach(function(ins){var tr=el(\"tr\",{class:\"row-ins\"});tr.appendChild(el(\"td\",{class:\"rowhdr\",html:'<span class=\"rowflag ins\"></span>'+\"new\",onclick:function(){g.inserts=g.inserts.filter(function(x){return x!==ins;});updateDirtyButtons(tab);renderGrid(tab);}}));\n fields.forEach(function(f){var inp=el(\"input\",{class:\"cellinput\",value:ins.values[f]!=null?ins.values[f]:\"\"});inp.addEventListener(\"input\",function(){if(inp.value===\"\")delete ins.values[f];else ins.values[f]=inp.value;});var td=el(\"td\");td.appendChild(inp);tr.appendChild(td);});\n if(ins.error){tr.classList.add(\"row-err\");tr.title=ins.error;}\n tbody.appendChild(tr);});\n table.appendChild(tbody);box.appendChild(table);\n updateDirtyButtons(tab);\n}\nfunction startEdit(tab,td,ri,field,row){if(td.querySelector(\"input\"))return;var g=tab.state.g;var colIdx=g.fields?g.fields.indexOf(field):-1;var key=rowKeyOf(g,row);var cur=g.edits[key]&&Object.prototype.hasOwnProperty.call(g.edits[key],field)?g.edits[key][field]:row[field];var input=el(\"input\",{class:\"cellinput\"});input.value=cur==null?\"\":typeof cur===\"object\"?JSON.stringify(cur):String(cur);clear(td).appendChild(input);input.focus();input.select();var done=false;function commit(){if(done)return true;done=true;var origStr=row[field]==null?\"\":String(row[field]);if(input.value!==origStr){gridPushUndo(tab);g.edits[key]=g.edits[key]||{};g.edits[key][field]=input.value;}else if(g.edits[key]){delete g.edits[key][field];if(!Object.keys(g.edits[key]).length)delete g.edits[key];}return true;}\n input.addEventListener(\"keydown\",function(e){if(e.key===\"Enter\"){e.preventDefault();commit();renderGrid(tab);editAt(tab,ri+1,colIdx);}else if(e.key===\"Tab\"){e.preventDefault();commit();renderGrid(tab);editAt(tab,ri,colIdx+1);}else if(e.key===\"Escape\"){e.preventDefault();done=true;renderGrid(tab);}});\n input.addEventListener(\"blur\",function(){if(!done){commit();renderGrid(tab);}});}\nfunction toastAction(msg,actionLabel,onAction){var t=el(\"div\",{class:\"toast\"});t.appendChild(el(\"span\",{text:msg+\" \"}));t.appendChild(el(\"a\",{class:\"link\",text:actionLabel,onclick:function(){onAction();t.remove();}}));$(\"toasts\").appendChild(t);setTimeout(function(){t.style.opacity=\"0\";setTimeout(function(){t.remove();},250);},6000);}\nfunction toggleDeleteSelected(tab){var g=tab.state.g;if(!g.editable)return logMsg(\"Cannot delete: table has no primary key.\",\"warn\");var keys=selectedKeys(g);if(!keys.length)return logMsg(\"Select one or more rows (click the row number).\",\"warn\");\n if(keys.every(function(k){return g.deletes[k];})){gridPushUndo(tab);keys.forEach(function(k){delete g.deletes[k];});renderGrid(tab);return;}\n var mark=function(){gridPushUndo(tab);keys.forEach(function(k){g.deletes[k]=true;});g.sel={};renderGrid(tab);toastAction(keys.length+\" row\"+(keys.length>1?\"s\":\"\")+\" marked for delete. They are removed only when you Save Changes.\",\"Undo\",function(){gridUndo(tab);});};\n if(keys.length>1){if(confirm(\"Mark \"+keys.length+\" selected rows for deletion?\\nThey will not be deleted until you click Save Changes.\"))mark();}else mark();}\nfunction addInsertRow(tab){var g=tab.state.g;gridPushUndo(tab);g.inserts.push({iseq:++g.iseq,values:{}});renderGrid(tab);}\nfunction revertAll(tab){var g=tab.state.g;g.edits={};g.deletes={};g.inserts=[];g.errors={};renderGrid(tab);logMsg(\"Reverted pending changes.\",\"ok\");}\nfunction updateDirtyButtons(tab){var g=tab.state.g;var nu=Object.keys(g.edits).length,nd=Object.keys(g.deletes).length,ni=g.inserts.length;var n=nu+nd+ni;if(g.saveBtn)g.saveBtn.style.display=n>0?\"\":\"none\";if(g.revertBtn)g.revertBtn.style.display=n>0?\"\":\"none\";setDirty(tab,n>0);if(g.changeBar){if(n>0){g.changeBar.classList.remove(\"hidden\");clear(g.changeBar);g.changeBar.appendChild(el(\"span\",{},[\"Pending: \",el(\"span\",{class:\"cnt-u\",text:nu+\" edit\"+(nu===1?\"\":\"s\")}),\" \u00B7 \",el(\"span\",{class:\"cnt-i\",text:ni+\" insert\"+(ni===1?\"\":\"s\")}),\" \u00B7 \",el(\"span\",{class:\"cnt-d\",text:nd+\" delete\"+(nd===1?\"\":\"s\")})]));g.changeBar.appendChild(el(\"span\",{style:\"flex:1\"}));g.changeBar.appendChild(el(\"button\",{class:\"btn sm\",title:\"Ctrl+S\",text:\"Save\",onclick:function(){saveDataChanges(tab);}}));g.changeBar.appendChild(el(\"button\",{class:\"btn sm ghost\",title:\"Revert all (Ctrl+Z to undo)\",text:\"Revert\",onclick:function(){revertAll(tab);}}));g.changeBar.appendChild(el(\"button\",{class:\"btn sm ghost\",text:\"Show changes\",onclick:function(){showChanges(tab);}}));}else g.changeBar.classList.add(\"hidden\");}}\nfunction gridFields(g){return g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);}\nfunction exportFilename(g,fmt,suffix){var conn=(activeConn()||{}).name||\"db\";var stamp=new Date().toISOString().replace(/[:.]/g,\"-\").slice(0,19);return (conn+\"_\"+g.schema+\"_\"+g.table+(suffix?\"_\"+suffix:\"\")+\"_\"+stamp+\".\"+fmt).replace(/[^a-z0-9._-]+/gi,\"-\");}\nfunction exportRowsToFile(fields,rows,fmt,filename){return fetch(fmt===\"csv\"?\"/api/export/csv\":\"/api/export/json\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({fields:fields,rows:rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement(\"a\");a.href=URL.createObjectURL(b);a.download=filename;a.click();});}\nfunction exportCurrentPage(tab,fmt){var g=tab.state.g;if(!g.rows.length)return logMsg(\"No rows to export.\",\"warn\");logMsg(\"Preparing export\u2026\",\"ok\");exportRowsToFile(gridFields(g),g.rows,fmt,exportFilename(g,fmt,\"page\")).then(function(){logMsg(\"Exported current page as \"+fmt.toUpperCase(),\"ok\");});}\nfunction exportSelected(tab,fmt){var g=tab.state.g;var rows=g.rows.filter(function(r){return g.sel[rowKeyOf(g,r)];});if(!rows.length)return logMsg(\"Select rows first (click row numbers).\",\"warn\");exportRowsToFile(gridFields(g),rows,fmt,exportFilename(g,fmt,\"selected\")).then(function(){logMsg(\"Exported \"+rows.length+\" selected row(s) as \"+fmt.toUpperCase(),\"ok\");});}\nfunction exportViaApi(tab,source,fmt,extra){var g=tab.state.g;logMsg(\"Preparing export\u2026\",\"ok\");var body={connectionId:S.activeConnId,schema:g.schema,objectName:g.table,objectType:\"table\",source:source,format:fmt,whereClause:g.where,limit:g.pageSize,offset:g.offset,sort:g.orderBy?[{column:g.orderBy,direction:g.orderDir}]:[]};if(extra)for(var k in extra)body[k]=extra[k];return fetch(\"/api/export/data\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify(body)}).then(function(r){if(!r.ok)return r.text().then(function(t){throw new Error(t||(\"HTTP \"+r.status));});return r.blob();}).then(function(b){var a=document.createElement(\"a\");a.href=URL.createObjectURL(b);a.download=exportFilename(g,fmt,source);a.click();logMsg(\"Export completed (\"+fmt.toUpperCase()+\")\",\"ok\");}).catch(function(e){logMsg(\"Export failed: \"+e.message,\"err\");});}\nfunction openExportMenu(tab,anchor){var r=anchor.getBoundingClientRect();showCtx(r.left,r.bottom+4,[\n {label:\"Current page \u00B7 CSV\",icon:\"imp\",onClick:function(){exportCurrentPage(tab,\"csv\");}},\n {label:\"Current page \u00B7 JSON\",icon:\"imp\",onClick:function(){exportCurrentPage(tab,\"json\");}},\n {sep:true},\n {label:\"Current query/filter \u00B7 CSV\",icon:\"imp\",onClick:function(){exportViaApi(tab,\"current-query\",\"csv\");}},\n {label:\"Current query/filter \u00B7 JSON\",icon:\"imp\",onClick:function(){exportViaApi(tab,\"current-query\",\"json\");}},\n {sep:true},\n {label:\"Selected rows \u00B7 CSV\",icon:\"imp\",onClick:function(){exportSelected(tab,\"csv\");}},\n {label:\"Selected rows \u00B7 JSON\",icon:\"imp\",onClick:function(){exportSelected(tab,\"json\");}},\n {sep:true},\n {label:\"Export custom\u2026\",icon:\"gear\",onClick:function(){exportCustomModal(tab);}}\n]);}\nfunction exportCustomModal(tab){var g=tab.state.g;var cols=gridFields(g);\n var src=el(\"select\",{class:\"select\"});[[\"current-page\",\"Current page\"],[\"current-query\",\"Current query/filter\"],[\"selected-rows\",\"Selected rows\"],[\"whole-table\",\"Whole table (can be large)\"]].forEach(function(o){src.appendChild(el(\"option\",{value:o[0],text:o[1]}));});\n var fmt=el(\"select\",{class:\"select\"});[[\"csv\",\"CSV\"],[\"json\",\"JSON\"]].forEach(function(f){fmt.appendChild(el(\"option\",{value:f[0],text:f[1]}));});\n var checks={};var list=el(\"div\",{class:\"fieldlist\"});cols.forEach(function(c){var cb=el(\"input\",{type:\"checkbox\"});cb.checked=true;checks[c]=cb;list.appendChild(el(\"label\",{},[cb,c]));});\n var warn=el(\"div\",{class:\"note\"});src.addEventListener(\"change\",function(){warn.textContent=src.value===\"whole-table\"?\"Whole-table export can be large and may take a while.\":\"\";});\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Export data\"}),\n el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Source\"}),src]),warn,\n el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Columns\"}),list]),\n el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Format\"}),fmt]),\n el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Export\",onclick:function(){\n var selectedColumns=cols.filter(function(c){return checks[c].checked;});if(!selectedColumns.length)return logMsg(\"Select at least one column.\",\"warn\");\n var source=src.value,format=fmt.value;\n if(source===\"whole-table\"&&!confirm(\"Export the whole table? This can be large.\"))return;\n closeModal();\n if(source===\"current-page\"){exportRowsToFile(selectedColumns,g.rows,format,exportFilename(g,format,\"page\")).then(function(){logMsg(\"Exported.\",\"ok\");});}\n else if(source===\"selected-rows\"){var rows=g.rows.filter(function(r){return g.sel[rowKeyOf(g,r)];});if(!rows.length)return logMsg(\"No rows selected.\",\"warn\");exportRowsToFile(selectedColumns,rows,format,exportFilename(g,format,\"selected\")).then(function(){logMsg(\"Exported.\",\"ok\");});}\n else{exportViaApi(tab,source,format,{selectedColumns:selectedColumns});}\n }})])]);\n openModal(d);}\nfunction rowContextMenu(e,tab,row){var g=tab.state.g;showCtx(e.clientX,e.clientY,[\n {label:\"View row details\",icon:\"viw\",onClick:function(){openCellViewer(row);}},\n {label:\"Copy row as JSON\",icon:\"col\",onClick:function(){navigator.clipboard.writeText(JSON.stringify(row,null,2));logMsg(\"Copied row JSON\",\"ok\");}},\n {label:\"Copy INSERT statement\",icon:\"sql\",onClick:function(){copyRowDml(g,row,\"insert\");}},\n {label:\"Copy UPDATE statement\",icon:\"sql\",onClick:function(){copyRowDml(g,row,\"update\");}}\n]);}\nfunction copyRowDml(g,row,kind){var fields=gridFields(g);var qn='\"'+g.schema+'\".\"'+g.table+'\"';function lit(v){return v==null?\"NULL\":typeof v===\"number\"?String(v):\"'\"+String(v).replace(/'/g,\"''\")+\"'\";}\n var sql;if(kind===\"insert\"){sql=\"INSERT INTO \"+qn+\" (\"+fields.map(function(f){return '\"'+f+'\"';}).join(\", \")+\")\\nVALUES (\"+fields.map(function(f){return lit(row[f]);}).join(\", \")+\");\";}\n else{var setp=fields.filter(function(f){return g.pk.indexOf(f)<0;}).map(function(f){return '\"'+f+'\" = '+lit(row[f]);}).join(\",\\n \");var wherep=(g.pk.length?g.pk:fields).map(function(f){return '\"'+f+'\" = '+lit(row[f]);}).join(\"\\n AND \");sql=\"UPDATE \"+qn+\"\\nSET\\n \"+setp+\"\\nWHERE\\n \"+wherep+\";\";}\n navigator.clipboard.writeText(sql);logMsg(\"Copied \"+kind.toUpperCase()+\" statement\",\"ok\");}\nfunction saveDataChanges(tab){var g=tab.state.g;if(S.readOnly)return logMsg(\"Read-only mode is on.\",\"warn\");\n var updates=Object.keys(g.edits).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,changes:g.edits[key]};});\n var deletes=Object.keys(g.deletes).map(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0];var keyObj={};g.pk.forEach(function(k){keyObj[k]=row[k];});return {key:keyObj,_k:key};});\n var inserts=g.inserts.filter(function(i){return Object.keys(i.values).length;}).map(function(i){return {values:i.values,_ref:i};});\n var total=updates.length+deletes.length+inserts.length;if(!total)return logMsg(\"No changes to save.\",\"warn\");\n if(!confirm(\"Save changes?\\n\\nUpdates: \"+updates.length+\"\\nInserts: \"+inserts.length+\"\\nDeletes: \"+deletes.length))return;\n setConnStatus(\"Saving changes...\",\"run\");\n api(\"POST\",\"/api/table/save-changes\",{connectionId:S.activeConnId,schema:g.schema,table:g.table,primaryKeyColumns:g.pk,updates:updates.map(function(u){return {key:u.key,changes:u.changes};}),inserts:inserts.map(function(i){return {values:i.values};}),deletes:deletes.map(function(d){return {key:d.key};}),readOnly:S.readOnly}).then(function(resp){setConnStatus(\"Connected\",\"ok\");\n if(resp.blocked){logMsg(resp.error||\"Blocked by read-only.\",\"err\");return;}\n var rr=resp.result.rowResults||[];var ui=0;\n // updates first, then inserts, then deletes (server order)\n var failedU=0,failedI=0,failedD=0;\n updates.forEach(function(u){var res=rr[ui++];if(res&&res.success){delete g.edits[rowKeyOf(g,g.rows.filter(function(r){return JSON.stringify(pkObj(g,r))===JSON.stringify(u.key);})[0]||{})];}else{failedU++;var k=rowKeyFromKeyObj(g,u.key);g.errors[k]=res?res.error:\"failed\";}});\n inserts.forEach(function(i){var res=rr[ui++];if(res&&res.success){g.inserts=g.inserts.filter(function(x){return x!==i._ref;});}else{i._ref.error=res?res.error:\"failed\";failedI++;}});\n deletes.forEach(function(d){var res=rr[ui++];if(res&&res.success){delete g.deletes[d._k];}else{g.errors[d._k]=res?res.error:\"failed\";failedD++;}});\n var ok=resp.result.updated+resp.result.inserted+resp.result.deleted;var fail=failedU+failedI+failedD;\n if(fail===0){logMsg(ok+\" change(s) saved.\",\"ok\");loadData(tab);loadCount(tab);}\n else{logMsg(ok+\" saved, \"+fail+\" failed. Failed rows kept pending with error markers.\",\"err\");renderGrid(tab);}\n updateDirtyButtons(tab);\n }).catch(function(e){setConnStatus(\"Connected\",\"ok\");logMsg(\"Save failed: \"+e.message,\"err\");});\n}\nfunction pkObj(g,row){var o={};g.pk.forEach(function(k){o[k]=row[k];});return o;}\nfunction rowKeyFromKeyObj(g,keyObj){return g.pk.map(function(k){return String(keyObj[k]);}).join(\"\\u0001\");}\n\n/* ====================================================================\n STRUCTURE / METADATA TAB\n ==================================================================== */\nfunction openStructureTab(schema,table){return openTab({key:\"struct:\"+S.activeConnId+\":\"+schema+\".\"+table,kind:\"structure\",title:\"Structure: \"+table,icon:\"col\",connectionId:S.activeConnId,meta:{schema:schema,table:table,objectType:\"table\"},build:function(pane,tab){tab.connectionId=S.activeConnId;buildStructure(pane,tab,schema,table);}});}\nfunction buildStructure(pane,tab,schema,table){\n var subtabs=el(\"div\",{class:\"meta-tabs\"});var body=el(\"div\",{class:\"pane-body\"});\n var defs=[[\"columns\",\"Columns\"],[\"indexes\",\"Indexes\"],[\"ddl\",\"DDL\"],[\"info\",\"Info\"]];\n var active=\"columns\";var data={};\n function render(){clear(body);if(active===\"columns\")renderCols();else if(active===\"indexes\")renderIdx();else if(active===\"ddl\")renderDdl();else renderInfo();Array.prototype.forEach.call(subtabs.children,function(ch,i){ch.classList.toggle(\"active\",defs[i][0]===active);});}\n defs.forEach(function(d){subtabs.appendChild(el(\"div\",{class:\"meta-tab\",text:d[1],onclick:function(){active=d[0];render();}}));});\n pane.appendChild(crumbs((activeConn()||{}).name,schema,table));\n pane.appendChild(el(\"div\",{class:\"toolbar\"},[el(\"b\",{text:'\"'+schema+'\".\"'+table+'\"'}),el(\"span\",{class:\"grow\"}),el(\"button\",{class:\"btn sec\",text:\"Open Data\",onclick:function(){openDataTab(schema,table);}})]));\n pane.appendChild(subtabs);pane.appendChild(body);\n function renderCols(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"GET\",\"/api/catalog/columns?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){data.columns=r.columns||[];if(active!==\"columns\")return;clear(body);var t=el(\"table\",{class:\"grid\"});t.appendChild(el(\"thead\",{html:\"<tr><th>Name</th><th>Type</th><th>Length</th><th>Scale</th><th>Nullable</th><th>Key</th><th>Default</th><th>Comment</th></tr>\"}));var tb=el(\"tbody\");data.columns.forEach(function(c){tb.appendChild(el(\"tr\",{html:\"<td>\"+esc(c.name)+\"</td><td>\"+esc(c.dataType)+'</td><td class=\"num\">'+esc(c.length==null?\"\":c.length)+'</td><td class=\"num\">'+esc(c.scale==null?\"\":c.scale)+\"</td><td>\"+(c.nullable?\"YES\":\"NO\")+\"</td><td>\"+(c.isPrimaryKey?'<span class=\"pill pk\">PK</span>':\"\")+\"</td><td>\"+esc(c.defaultValue==null?\"\":c.defaultValue)+\"</td><td>\"+esc(c.comment==null?\"\":c.comment)+\"</td>\"}));});t.appendChild(tb);clear(body).appendChild(t);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderIdx(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"GET\",\"/api/catalog/constraints?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){if(active!==\"indexes\")return;clear(body);var pk=r.primaryKey&&r.primaryKey.columns||[];body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Primary key\"}),el(\"div\",{text:pk.length?pk.join(\", \"):\"(none)\"})]));var idx=r.indexes||[];if(!idx.length){body.appendChild(el(\"div\",{class:\"empty\",text:\"No indexes.\"}));return;}var t=el(\"table\",{class:\"grid\"});t.appendChild(el(\"thead\",{html:\"<tr><th>Index</th><th>Columns</th><th>Unique</th><th>Primary</th></tr>\"}));var tb=el(\"tbody\");idx.forEach(function(i){tb.appendChild(el(\"tr\",{html:\"<td>\"+esc(i.name)+\"</td><td>\"+esc((i.columns||[]).join(\", \"))+\"</td><td>\"+(i.isUnique?\"YES\":\"NO\")+\"</td><td>\"+(i.isPrimaryKey?\"YES\":\"NO\")+\"</td>\"}));});t.appendChild(tb);body.appendChild(t);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderDdl(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" generating...\"]));api(\"GET\",\"/api/catalog/ddl?\"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){if(active!==\"ddl\")return;clear(body);var ta=el(\"textarea\",{class:\"editor\",readonly:\"readonly\"});ta.value=r.ddl;body.appendChild(el(\"div\",{class:\"row\"},[el(\"button\",{class:\"btn sec\",text:\"Open in SQL Console\",onclick:function(){openSqlTab(r.ddl,table);}}),el(\"button\",{class:\"btn ghost\",text:\"Copy\",onclick:function(){navigator.clipboard.writeText(r.ddl);logMsg(\"Copied DDL\",\"ok\");}})]));body.appendChild(ta);}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function renderInfo(){body.appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading...\"]));api(\"POST\",\"/api/table/count\",{connectionId:S.activeConnId,schema:schema,table:table}).then(function(r){if(active!==\"info\")return;clear(body).appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Schema\"}),el(\"div\",{text:schema}),el(\"div\",{class:\"k\",text:\"Object\"}),el(\"div\",{text:table}),el(\"div\",{class:\"k\",text:\"Row count\"}),el(\"div\",{text:String(r.count)}),el(\"div\",{class:\"k\",text:\"Columns\"}),el(\"div\",{text:String((data.columns||[]).length)})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n render();\n}\n\n/* ====================================================================\n SAVED QUERIES\n ==================================================================== */\nfunction loadSavedQueries(){return api(\"GET\",\"/api/queries\").then(function(r){S.savedQueries=r.queries||[];renderSavedQueries();}).catch(function(){});}\nfunction renderSavedQueries(){var raw=($(\"querySearch\").value||\"\");var q=raw.toLowerCase();var box=clear($(\"queryList\"));var rows=S.savedQueries.filter(function(x){return (x.name+\" \"+(x.tags||[]).join(\" \")).toLowerCase().indexOf(q)>=0;});if(!rows.length){box.appendChild(el(\"div\",{class:\"empty\",text:S.savedQueries.length?\"No results found\":\"No saved queries.\"}));return;}rows.forEach(function(x){var item=el(\"div\",{class:\"wli\",onclick:function(){openSqlTab(x.sql,x.name,x.id);},oncontextmenu:function(e){e.preventDefault();queryMenu(e,x);}});item.appendChild(el(\"b\",{html:highlightMatch(x.name,raw)}));item.appendChild(el(\"div\",{class:\"note\",text:(x.connectionType||\"\")+\" \\u00b7 \"+new Date(x.updatedAt).toLocaleDateString()}));box.appendChild(item);});}\nfunction queryMenu(e,x){showCtx(e.clientX,e.clientY,[{label:\"Open\",icon:\"sql\",onClick:function(){openSqlTab(x.sql,x.name);}},{label:\"Rename\",icon:\"gear\",onClick:function(){var n=prompt(\"New name\",x.name);if(n)api(\"PUT\",\"/api/queries/\"+encodeURIComponent(x.id),{name:n}).then(loadSavedQueries);}},{label:\"Delete\",icon:\"x\",danger:true,onClick:function(){if(confirm(\"Delete '\"+x.name+\"'?\"))api(\"DELETE\",\"/api/queries/\"+encodeURIComponent(x.id)).then(loadSavedQueries);}}]);}\n\n/* ====================================================================\n DIRECT CONNECTION MODAL\n ==================================================================== */\nfunction newConnModal(){var typeSel=el(\"select\",{class:\"select\"});typeSel.appendChild(el(\"option\",{value:\"postgresql\",text:\"PostgreSQL\"}));typeSel.appendChild(el(\"option\",{value:\"hana\",text:\"SAP HANA\"}));\n var f={name:el(\"input\",{class:\"input\"}),host:el(\"input\",{class:\"input\"}),port:el(\"input\",{class:\"input\",value:\"5432\"}),database:el(\"input\",{class:\"input\"}),schema:el(\"input\",{class:\"input\",value:\"public\"}),user:el(\"input\",{class:\"input\"}),pass:el(\"input\",{class:\"input\",type:\"password\"}),ssl:el(\"input\",{type:\"checkbox\"})};f.ssl.checked=true;\n typeSel.addEventListener(\"change\",function(){f.port.value=typeSel.value===\"hana\"?\"443\":\"5432\";f.schema.value=typeSel.value===\"hana\"?\"\":\"public\";});\n var msg=el(\"div\",{class:\"note\"});\n function body(){return {name:f.name.value.trim(),type:typeSel.value,host:f.host.value.trim(),port:parseInt(f.port.value,10)||(typeSel.value===\"hana\"?443:5432),database:f.database.value.trim(),schema:f.schema.value.trim(),username:f.user.value.trim(),password:f.pass.value,ssl:f.ssl.checked};}\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"New direct connection\"}),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Name\"}),f.name]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Type\"}),typeSel]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Host\"}),f.host]),el(\"div\",{class:\"field\",style:\"width:110px\"},[el(\"label\",{text:\"Port\"}),f.port])]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Database\"}),f.database]),el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Schema\"}),f.schema])]),el(\"div\",{class:\"row\"},[el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Username\"}),f.user]),el(\"div\",{class:\"field\",style:\"flex:1\"},[el(\"label\",{text:\"Password\"}),f.pass])]),el(\"label\",{class:\"note\"},[f.ssl,\" Use SSL\"]),el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn sec\",text:\"Test\",onclick:function(){msg.textContent=\"Testing...\";api(\"POST\",\"/api/connections/test-draft\",body()).then(function(r){msg.textContent=r.success?(\"OK \"+(r.serverVersion||\"\")):(\"Failed: \"+r.message);});}}),el(\"button\",{class:\"btn\",text:\"Save & use\",onclick:function(){var b=body();if(!b.name||!b.host||!b.username)return msg.textContent=\"Name, host, username required.\";api(\"POST\",\"/api/connections/create\",b).then(function(r){closeModal();return loadConnections().then(function(){activateConnection(r.connection.id);});}).catch(function(e){msg.textContent=e.message;});}})]),msg]);openModal(d);}\n\n/* ====================================================================\n BTP IMPORT WIZARD\n ==================================================================== */\nfunction openBtpWizard(){var stState={apps:[],services:[],app:\"\",svc:null,color:\"\",env:\"\"};\n var steps=el(\"div\",{class:\"steps\"});[\"Target\",\"App\",\"Services\",\"Save\"].forEach(function(s){steps.appendChild(el(\"div\",{class:\"step\",text:s}));});\n var body=el(\"div\");var msg=el(\"div\",{class:\"note\"});\n function setStep(i){Array.prototype.forEach.call(steps.children,function(ch,idx){ch.className=\"step\"+(idx===i?\" active\":idx<i?\" done\":\"\");});}\n function stepTarget(){setStep(0);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" checking CF target...\"]));api(\"GET\",\"/api/btp/current-target\").then(function(r){var t=r.target||{};clear(body);body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Logged in\"}),el(\"div\",{text:r.loggedIn?\"yes\":\"no\"}),el(\"div\",{class:\"k\",text:\"Region\"}),el(\"div\",{text:t.region||\"-\"}),el(\"div\",{class:\"k\",text:\"Org\"}),el(\"div\",{text:t.org||\"-\"}),el(\"div\",{class:\"k\",text:\"Space\"}),el(\"div\",{text:t.space||\"-\"})]));if(r.productionWarning)body.appendChild(el(\"div\",{class:\"badge prod\",text:\"Production-like target\"}));if(!r.loggedIn)body.appendChild(el(\"div\",{class:\"note\",text:r.message||\"Run: smdg cf login\"}));body.appendChild(el(\"div\",{class:\"note\",text:\"Switch org/space with: smdg cf org\"}));body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Load apps \\u2192\",onclick:stepApps}),el(\"button\",{class:\"btn sec\",text:\"Refresh apps\",onclick:function(){stState.apps=[];stepApps(true);}})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepApps(refresh){setStep(1);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" loading apps...\"]));api(\"GET\",\"/api/btp/apps\"+(refresh?\"?refresh=true\":\"\")).then(function(r){clear(body);if(!r.loggedIn){body.appendChild(el(\"div\",{class:\"errbox\",text:r.message||\"Not logged in. Run: smdg cf login\"}));return;}stState.apps=r.apps||[];var search=el(\"input\",{class:\"input\",placeholder:\"Search apps...\"});var list=el(\"div\",{class:\"wlistbox\"});function draw(){clear(list);var q=(search.value||\"\").toLowerCase();stState.apps.filter(function(a){return JSON.stringify(a).toLowerCase().indexOf(q)>=0;}).forEach(function(a){list.appendChild(el(\"div\",{class:\"wrow\"+(stState.app===a.name?\" sel\":\"\"),onclick:function(){stState.app=a.name;stepServices();}},[icEl(\"db\",\"db\"),el(\"div\",{style:\"flex:1\"},[el(\"b\",{text:a.name}),el(\"div\",{class:\"note\",text:(a.requestedState||\"\")+(a.routes?\" \\u00b7 \"+a.routes:\"\")})])]));});if(!stState.apps.length)list.appendChild(el(\"div\",{class:\"empty\",text:\"No apps.\"}));}search.addEventListener(\"input\",debounce(draw,200));body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Select an app\"}),search]));body.appendChild(list);body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:stepTarget}),el(\"button\",{class:\"btn sec\",text:\"Refresh\",onclick:function(){stepApps(true);}})]));draw();}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepServices(){setStep(2);clear(body).appendChild(el(\"div\",{class:\"empty\"},[el(\"span\",{class:\"spin\"}),\" reading cf env \"+esc(stState.app)+\"...\"]));api(\"POST\",\"/api/btp/env\",{app:stState.app}).then(function(r){clear(body);stState.services=r.services||[];if(!stState.services.length){body.appendChild(el(\"div\",{class:\"errbox\",text:\"No HANA/PostgreSQL service detected in \"+stState.app}));}var list=el(\"div\",{class:\"wlistbox\"});stState.services.forEach(function(svc){list.appendChild(el(\"div\",{class:\"wrow\",onclick:function(){stState.svc=svc;stepSave();}},[el(\"div\",{style:\"flex:1\"},[el(\"b\",{text:svc.serviceName}),el(\"div\",{class:\"note\",text:svc.type+\" \\u00b7 \"+svc.host+\" \\u00b7 \"+(svc.schema||svc.database||\"\")})])]));});body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Detected database services\"}),list]));body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:function(){stepApps();}})]));}).catch(function(e){clear(body).appendChild(el(\"div\",{class:\"errbox\",text:e.message}));});}\n function stepSave(){setStep(3);clear(body);var svc=stState.svc;var nameI=el(\"input\",{class:\"input\",value:(stState.app+\" / \"+svc.serviceName)});var envSel=el(\"select\",{class:\"select\"});[\"\",\"DEV\",\"QAS\",\"PROD\",\"SANDBOX\",\"CUSTOM\"].forEach(function(en){envSel.appendChild(el(\"option\",{value:en,text:en||\"(none)\"}));});\n var sel={color:\"\"};var sw=el(\"div\",{class:\"swatches\"});SWATCHES.forEach(function(col){var s=el(\"div\",{class:\"swatch\",style:\"background:\"+col,onclick:function(){sel.color=col;Array.prototype.forEach.call(sw.children,function(x){x.classList.remove(\"sel\");});s.classList.add(\"sel\");}});sw.appendChild(s);});\n var fav=el(\"input\",{type:\"checkbox\"});\n body.appendChild(el(\"div\",{class:\"kvs\"},[el(\"div\",{class:\"k\",text:\"Service\"}),el(\"div\",{text:svc.serviceName}),el(\"div\",{class:\"k\",text:\"Type\"}),el(\"div\",{text:svc.type}),el(\"div\",{class:\"k\",text:\"Host\"}),el(\"div\",{text:svc.host})]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Display name\"}),nameI]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Environment\"}),envSel]));\n body.appendChild(el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Color\"}),sw]));\n body.appendChild(el(\"label\",{class:\"note\"},[fav,\" Mark as favorite\"]));\n body.appendChild(el(\"div\",{class:\"row right\",style:\"margin-top:12px\"},[el(\"button\",{class:\"btn ghost\",text:\"\\u2190 Back\",onclick:stepServices},\"\"),el(\"button\",{class:\"btn\",text:\"Save & activate\",onclick:function(){msg.textContent=\"Importing & testing...\";api(\"POST\",\"/api/connections/import-from-app\",{app:stState.app,serviceName:svc.serviceName,type:svc.type}).then(function(r){var id=r.connection.id;return api(\"POST\",\"/api/connections/update\",{id:id,name:nameI.value.trim(),environment:envSel.value,color:sel.color,isFavorite:fav.checked}).then(function(){return api(\"POST\",\"/api/connections/test\",{connectionId:id}).then(function(t){closeModal();return loadConnections().then(function(){activateConnection(id);logMsg(t.success?(\"Imported & connected: \"+nameI.value):(\"Imported (test failed: \"+t.message+\")\"),t.success?\"ok\":\"warn\");});});});}).catch(function(e){msg.textContent=e.message;});}})]));\n body.appendChild(msg);\n }\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Import from BTP App\"}),steps,body]);openModal(d);stepTarget();\n}\n\n/* ====================================================================\n READ-ONLY + INIT\n ==================================================================== */\n/* ====================================================================\n KEYBOARD + COMMAND PALETTE + SETTINGS\n ==================================================================== */\nfunction isTyping(t){return t&&(t.tagName===\"INPUT\"||t.tagName===\"TEXTAREA\"||t.isContentEditable);}\nfunction saveActive(){var t=tabById(S.activeTabId);if(!t)return;if(t.kind===\"sql\")saveQueryTab(t);else if(t.kind===\"data\")saveDataChanges(t);else logMsg(\"Nothing to save in this tab.\",\"warn\");}\nfunction onGlobalKey(e){var k=e.key;var ctrl=e.ctrlKey||e.metaKey;\n if(ctrl&&e.shiftKey&&(k===\"P\"||k===\"p\")){e.preventDefault();openCommandPalette();return;}\n if(k===\"Escape\"){hideCtx();closePop();if(_palette){closePalette();return;}return;}\n if(ctrl&&k===\"Tab\"){e.preventDefault();nextTab(e.shiftKey?-1:1);return;}\n if(ctrl&&(k===\"w\"||k===\"W\")){e.preventDefault();if(S.activeTabId)closeTab(S.activeTabId);return;}\n if(ctrl&&!e.shiftKey&&(k===\"f\"||k===\"F\")&&!isTyping(e.target)){e.preventDefault();$(\"topSearch\").focus();return;}\n if(k===\"F5\"&&!isTyping(e.target)){var ft=tabById(S.activeTabId);if(ft&&ft.kind===\"sql\"){e.preventDefault();runMode(ft,\"all\");}return;}\n if(ctrl&&(k===\"s\"||k===\"S\")&&!isTyping(e.target)){e.preventDefault();saveActive();return;}\n var at=tabById(S.activeTabId);\n if(at&&at.kind===\"data\"&&at.state.g&&!isTyping(e.target)){var g=at.state.g;\n if(ctrl&&(k===\"s\"||k===\"S\")){e.preventDefault();saveDataChanges(at);return;}\n if(ctrl&&(k===\"z\"||k===\"Z\")){e.preventDefault();gridUndo(at);return;}\n if(ctrl&&(k===\"y\"||k===\"Y\")){e.preventDefault();gridRedo(at);return;}\n if(k===\"Delete\"){e.preventDefault();toggleDeleteSelected(at);return;}\n }\n}\nvar PALETTE=[\n {label:\"New SQL Console\",run:function(){if(!S.activeConnId)logMsg(\"Select a connection first.\",\"warn\");else openSqlTab();}},\n {label:\"Run SQL (current tab)\",run:function(){var t=tabById(S.activeTabId);if(t&&t.kind===\"sql\")runMode(t,\"selected\");}},\n {label:\"Save Query / Grid Changes\",run:saveActive},\n {label:\"Import from BTP App\",run:function(){openBtpWizard();}},\n {label:\"New direct connection\",run:function(){newConnModal();}},\n {label:\"Focus Connections\",run:function(){$(\"connSearch\").focus();}},\n {label:\"Focus Object Explorer\",run:function(){var s=document.querySelector(\"#secTree .tsearch input\");if(s)s.focus();else $(\"topSearch\").focus();}},\n {label:\"Toggle Read-only\",run:function(){toggleReadOnly();}},\n {label:\"Open Active Table Structure\",run:function(){var t=tabById(S.activeTabId);if(t&&t.meta&&t.meta.table)openStructureTab(t.meta.schema,t.meta.table);else logMsg(\"No active table.\",\"warn\");}},\n {label:\"Export Result CSV\",run:function(){var t=tabById(S.activeTabId);if(t&&t.kind===\"sql\")exportResult(t,\"csv\");else if(t&&t.kind===\"data\")exportCurrentPage(t,\"csv\");}},\n {label:\"Close Active Tab\",run:function(){if(S.activeTabId)closeTab(S.activeTabId);}},\n {label:\"Show Keyboard Shortcuts\",run:function(){showShortcuts();}},\n {label:\"Open Settings\",run:function(){openSettings();}}\n];\nvar _palette=null;\nfunction openCommandPalette(){closePalette();var input=el(\"input\",{placeholder:\"Type a command...\"});var list=el(\"div\",{class:\"pitems\"});var sel=0;var filtered=PALETTE.slice();\n function draw(){clear(list);var q=input.value.toLowerCase();filtered=PALETTE.filter(function(c){return c.label.toLowerCase().indexOf(q)>=0;});filtered.forEach(function(c,i){list.appendChild(el(\"div\",{class:\"pitem\"+(i===sel?\" sel\":\"\"),onclick:function(){closePalette();c.run();}},[el(\"span\",{html:highlightMatch(c.label,input.value)})]));});}\n input.addEventListener(\"input\",function(){sel=0;draw();});\n input.addEventListener(\"keydown\",function(e){if(e.key===\"ArrowDown\"){e.preventDefault();sel=Math.min(filtered.length-1,sel+1);draw();}else if(e.key===\"ArrowUp\"){e.preventDefault();sel=Math.max(0,sel-1);draw();}else if(e.key===\"Enter\"){e.preventDefault();if(filtered[sel]){closePalette();filtered[sel].run();}}else if(e.key===\"Escape\"){e.preventDefault();closePalette();}});\n var ov=el(\"div\",{class:\"modal\",onclick:function(e){if(e.target===ov)closePalette();}},[el(\"div\",{class:\"palette\"},[input,list])]);document.body.appendChild(ov);_palette=ov;draw();input.focus();}\nfunction closePalette(){if(_palette){_palette.remove();_palette=null;}}\nfunction showShortcuts(){var rows=[[\"Ctrl+Shift+P\",\"Command palette\"],[\"Ctrl+Tab\",\"Next tab\"],[\"Ctrl+Shift+Tab\",\"Previous tab\"],[\"Ctrl+W\",\"Close active tab\"],[\"Ctrl+S\",\"Save query / grid changes\"],[\"Ctrl+F\",\"Focus search\"],[\"Escape\",\"Close popup / clear search\"],[\"Ctrl+Enter\",\"Run selected / current SQL\"],[\"F5\",\"Run all SQL\"],[\"Ctrl+Z / Ctrl+Y\",\"Undo / redo (editor & grid)\"],[\"Delete\",\"Mark grid row for delete\"],[\"Enter / Tab\",\"Confirm cell edit & move\"],[\"Esc\",\"Cancel cell edit\"]];\n var grid=el(\"div\",{class:\"shorts\"});rows.forEach(function(r){grid.appendChild(el(\"div\",{class:\"srow\"},[el(\"span\",{text:r[1]}),el(\"span\",{class:\"kbd\",text:r[0]})]));});\n openModal(el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Keyboard shortcuts\"}),grid,el(\"div\",{class:\"row right\",style:\"margin-top:14px\"},[el(\"button\",{class:\"btn\",text:\"Close\",onclick:closeModal})])]));}\nfunction openSettings(){var s=S.settings;var restore=el(\"input\",{type:\"checkbox\"});restore.checked=s.restoreWorkspace;var ro=el(\"input\",{type:\"checkbox\"});ro.checked=s.readOnlyByDefault;var prod=el(\"input\",{type:\"checkbox\"});prod.checked=s.showProductionWarning;var fmt=el(\"input\",{type:\"checkbox\"});fmt.checked=s.autoFormatGeneratedSql;var limit=el(\"input\",{class:\"input\",value:String(s.defaultRowLimit)});var schema=el(\"input\",{class:\"input\",value:s.defaultSchema||\"\"});var timeout=el(\"input\",{class:\"input\",value:String(s.queryTimeoutMs)});var maxh=el(\"input\",{class:\"input\",value:String(s.maxHistoryItems)});var delay=el(\"input\",{class:\"input\",value:String(s.autoSaveDelayMs)});\n var d=el(\"div\",{class:\"dialog\"},[el(\"h3\",{text:\"Studio settings\"}),el(\"label\",{class:\"toggle\"},[restore,\" Restore workspace on startup\"]),el(\"label\",{class:\"toggle\"},[ro,\" Read-only by default\"]),el(\"label\",{class:\"toggle\"},[prod,\" Show production warning\"]),el(\"label\",{class:\"toggle\"},[fmt,\" Auto-format generated SQL\"]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Default row limit\"}),limit]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Default schema\"}),schema]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Query timeout (ms)\"}),timeout]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Max history items\"}),maxh]),el(\"div\",{class:\"field\"},[el(\"label\",{text:\"Auto-save delay (ms)\"}),delay]),el(\"div\",{class:\"row right\"},[el(\"button\",{class:\"btn ghost\",text:\"Cancel\",onclick:closeModal}),el(\"button\",{class:\"btn\",text:\"Save\",onclick:function(){api(\"PUT\",\"/api/studio/settings\",{restoreWorkspace:restore.checked,readOnlyByDefault:ro.checked,showProductionWarning:prod.checked,autoFormatGeneratedSql:fmt.checked,defaultRowLimit:parseInt(limit.value,10)||100,defaultSchema:schema.value.trim(),queryTimeoutMs:parseInt(timeout.value,10)||30000,maxHistoryItems:parseInt(maxh.value,10)||300,autoSaveDelayMs:parseInt(delay.value,10)||500}).then(function(r){S.settings=r.settings;closeModal();logMsg(\"Settings saved.\",\"ok\");}).catch(function(e){logMsg(e.message,\"err\");});}})])]);openModal(d);}\nfunction openCellViewer(value){var raw=value==null?\"\":typeof value===\"object\"?JSON.stringify(value):String(value);var pretty=raw;try{pretty=JSON.stringify(JSON.parse(raw),null,2);}catch(e){}var ta=el(\"textarea\",{class:\"editor\",style:\"min-height:300px\"});ta.value=pretty;openModal(el(\"div\",{class:\"dialog\",style:\"width:700px\"},[el(\"h3\",{text:\"Cell value\"}),ta,el(\"div\",{class:\"row right\",style:\"margin-top:10px\"},[el(\"button\",{class:\"btn ghost\",text:\"Copy\",onclick:function(){navigator.clipboard.writeText(raw);logMsg(\"Copied value\",\"ok\");}}),el(\"button\",{class:\"btn\",text:\"Close\",onclick:closeModal})])]));}\nfunction crumbs(connName,schema,table){var c=el(\"div\",{class:\"crumbs\"});c.appendChild(el(\"a\",{text:connName||\"Connection\",onclick:function(){if(S.activeConnId)activateConnection(S.activeConnId);}}));c.appendChild(el(\"span\",{class:\"sep\",text:\"\\u203a\"}));c.appendChild(el(\"a\",{text:schema,onclick:function(){S.activeSchema=schema;updateTopBadges();}}));c.appendChild(el(\"span\",{class:\"sep\",text:\"\\u203a\"}));c.appendChild(el(\"span\",{text:table}));return c;}\n\n/* ---- grid undo/redo + change review ---- */\nfunction gridSnapshot(g){return JSON.stringify({edits:g.edits,deletes:g.deletes,inserts:g.inserts});}\nfunction gridApplySnap(g,s){var o=JSON.parse(s);g.edits=o.edits;g.deletes=o.deletes;g.inserts=o.inserts;}\nfunction gridPushUndo(tab){var g=tab.state.g;g.undo.push(gridSnapshot(g));if(g.undo.length>60)g.undo.shift();g.redo=[];}\nfunction gridUndo(tab){var g=tab.state.g;if(!g.undo.length)return logMsg(\"Nothing to undo.\",\"warn\");g.redo.push(gridSnapshot(g));gridApplySnap(g,g.undo.pop());renderGrid(tab);}\nfunction gridRedo(tab){var g=tab.state.g;if(!g.redo.length)return logMsg(\"Nothing to redo.\",\"warn\");g.undo.push(gridSnapshot(g));gridApplySnap(g,g.redo.pop());renderGrid(tab);}\nfunction showChanges(tab){var g=tab.state.g;var out=[];Object.keys(g.edits).forEach(function(key){var row=g.rows.filter(function(r){return rowKeyOf(g,r)===key;})[0]||{};Object.keys(g.edits[key]).forEach(function(col){out.push([\"update\",key,col,String(row[col]==null?\"\":row[col]),String(g.edits[key][col])]);});});Object.keys(g.deletes).forEach(function(key){out.push([\"delete\",key,\"\",\"\",\"\"]);});g.inserts.forEach(function(ins){out.push([\"insert\",\"(new)\",Object.keys(ins.values).join(\",\"),\"\",JSON.stringify(ins.values)]);});\n var t=el(\"table\",{class:\"grid\"});t.appendChild(el(\"thead\",{html:\"<tr><th>Type</th><th>Row key</th><th>Column</th><th>Old</th><th>New</th></tr>\"}));var tb=el(\"tbody\");if(!out.length)tb.appendChild(el(\"tr\",{html:'<td colspan=\"5\">No pending changes.</td>'}));out.forEach(function(r){tb.appendChild(el(\"tr\",{html:r.map(function(c){return \"<td>\"+esc(c)+\"</td>\";}).join(\"\")}));});t.appendChild(tb);\n openModal(el(\"div\",{class:\"dialog\",style:\"width:780px\"},[el(\"h3\",{text:\"Pending changes\"}),el(\"div\",{class:\"gridwrap\",style:\"max-height:52vh\"},[t]),el(\"div\",{class:\"row right\",style:\"margin-top:12px\"},[el(\"button\",{class:\"btn\",text:\"Close\",onclick:closeModal})])]));}\nfunction editAt(tab,ri,colIdx){var g=tab.state.g;if(ri<0||ri>=g.rows.length||colIdx<0||!g.fields||colIdx>=g.fields.length)return;var tr=g.grid.querySelector('tr[data-ri=\"'+ri+'\"]');if(!tr)return;var tds=tr.querySelectorAll(\"td\");var td=tds[colIdx+1];if(td)startEdit(tab,td,ri,g.fields[colIdx],g.rows[ri]);}\n\nfunction applyReadOnly(){var b=$(\"roBadge\");b.className=\"badge ro\"+(S.readOnly?\" active\":\"\");b.textContent=S.readOnly?\"Read-only\":\"Read/Write\";}\nfunction toggleReadOnly(){S.readOnly=!S.readOnly;applyReadOnly();logMsg(\"Read-only mode: \"+(S.readOnly?\"ON\":\"OFF\"),\"ok\");}\n\nfunction initSidebarCollapse(){Array.prototype.forEach.call(document.querySelectorAll(\".side-head\"),function(h){h.addEventListener(\"click\",function(){h.parentNode.classList.toggle(\"collapsed\");});});}\nfunction initResizer(){var r=$(\"resizer\"),sb=$(\"sidebar\"),drag=false;r.addEventListener(\"mousedown\",function(){drag=true;document.body.style.userSelect=\"none\";});window.addEventListener(\"mousemove\",function(e){if(!drag)return;sb.style.width=Math.min(560,Math.max(220,e.clientX))+\"px\";});window.addEventListener(\"mouseup\",function(){drag=false;document.body.style.userSelect=\"\";});}\n\nwindow.addEventListener(\"load\",function(){\n applyReadOnly();\n initSidebarCollapse();initResizer();\n $(\"roBadge\").addEventListener(\"click\",toggleReadOnly);\n $(\"btnImport\").addEventListener(\"click\",openBtpWizard);\n $(\"btnImport2\").addEventListener(\"click\",openBtpWizard);\n $(\"btnNewConn\").addEventListener(\"click\",newConnModal);\n $(\"btnSettings\").addEventListener(\"click\",openSettings);\n $(\"btnHome\").addEventListener(\"click\",openWelcome);\n window.addEventListener(\"keydown\",onGlobalKey);\n $(\"connSearch\").addEventListener(\"input\",debounce(renderConnections,200));wireSearch($(\"connSearch\"),renderConnections);\n $(\"querySearch\").addEventListener(\"input\",debounce(renderSavedQueries,200));wireSearch($(\"querySearch\"),renderSavedQueries);\n $(\"topSearch\").addEventListener(\"input\",debounce(function(){$(\"connSearch\").value=$(\"topSearch\").value;renderConnections();},200));\n $(\"btnNewQuery\").addEventListener(\"click\",function(){if(!S.activeConnId)return logMsg(\"Select a connection first.\",\"warn\");openSqlTab();});\n Array.prototype.forEach.call(document.querySelectorAll(\".searchbox\"),function(box){var inp=box.querySelector(\"input\");if(!inp)return;var x=el(\"span\",{class:\"sbclr\",html:svgFor(\"x\"),title:\"Clear (Esc)\"});x.addEventListener(\"click\",function(){inp.value=\"\";inp.dispatchEvent(new Event(\"input\"));inp.focus();});inp.addEventListener(\"input\",function(){x.classList.toggle(\"show\",!!inp.value);});box.appendChild(x);});\n setConnStatus(\"Ready\",\"ok\");\n openWelcome();\n loadSettings().then(function(){return loadConnections();}).then(function(){return restoreWorkspace();});\n loadSavedQueries();\n});\n})();\n";
|