simplemdg-dev-cli 2.4.4 → 2.5.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.
Files changed (35) hide show
  1. package/README.md +14 -15
  2. package/USER_GUIDE.md +11 -9
  3. package/dist/core/db/db-cache.d.ts +5 -0
  4. package/dist/core/db/db-cache.js +24 -0
  5. package/dist/core/db/db-cache.js.map +1 -1
  6. package/dist/core/db/db-hana-adapter.d.ts +4 -0
  7. package/dist/core/db/db-hana-adapter.js +8 -0
  8. package/dist/core/db/db-hana-adapter.js.map +1 -1
  9. package/dist/core/db/db-postgres-adapter.d.ts +4 -0
  10. package/dist/core/db/db-postgres-adapter.js +14 -0
  11. package/dist/core/db/db-postgres-adapter.js.map +1 -1
  12. package/dist/core/db/db-row.d.ts +7 -1
  13. package/dist/core/db/db-row.js +53 -0
  14. package/dist/core/db/db-row.js.map +1 -1
  15. package/dist/core/db/db-studio-client.d.ts +1 -0
  16. package/dist/core/db/db-studio-client.js +401 -0
  17. package/dist/core/db/db-studio-client.js.map +1 -0
  18. package/dist/core/db/db-studio-html.js +54 -408
  19. package/dist/core/db/db-studio-html.js.map +1 -1
  20. package/dist/core/db/db-studio-server.js +63 -0
  21. package/dist/core/db/db-studio-server.js.map +1 -1
  22. package/dist/core/db/db-studio-styles.d.ts +1 -0
  23. package/dist/core/db/db-studio-styles.js +225 -0
  24. package/dist/core/db/db-studio-styles.js.map +1 -0
  25. package/dist/core/db/db-types.d.ts +40 -0
  26. package/package.json +1 -1
  27. package/src/core/db/db-cache.ts +27 -0
  28. package/src/core/db/db-hana-adapter.ts +12 -0
  29. package/src/core/db/db-postgres-adapter.ts +18 -0
  30. package/src/core/db/db-row.ts +65 -1
  31. package/src/core/db/db-studio-client.ts +397 -0
  32. package/src/core/db/db-studio-html.ts +54 -408
  33. package/src/core/db/db-studio-server.ts +70 -3
  34. package/src/core/db/db-studio-styles.ts +221 -0
  35. package/src/core/db/db-types.ts +48 -0
@@ -1,4 +1,10 @@
1
- import type { IDatabaseAdapter, TDatabaseQueryResult } from "./db-types";
1
+ import type {
2
+ IDatabaseAdapter,
3
+ TDatabaseQueryResult,
4
+ TSaveRowResult,
5
+ TSaveTableChangesResult,
6
+ TTableChangeSet,
7
+ } from "./db-types";
2
8
 
3
9
  export type TRowValues = Record<string, unknown>;
4
10
 
@@ -91,3 +97,61 @@ export async function insertRow(
91
97
  const sql = `INSERT INTO ${adapter.buildQualifiedName(options.schema, options.table)} (${columnClause}) VALUES (${valueClause})`;
92
98
  return adapter.runParameterized(sql, params);
93
99
  }
100
+
101
+ function errorMessage(error: unknown): string {
102
+ return error instanceof Error ? error.message : String(error);
103
+ }
104
+
105
+ /**
106
+ * Apply a set of pending grid changes. Each change runs independently so a
107
+ * single failing row does not discard the others — the result reports per-row
108
+ * success/failure, and callers keep the failed rows pending.
109
+ */
110
+ export async function saveTableChanges(
111
+ adapter: IDatabaseAdapter,
112
+ changeSet: TTableChangeSet,
113
+ ): Promise<TSaveTableChangesResult> {
114
+ const rowResults: TSaveRowResult[] = [];
115
+ const { schema, table } = changeSet;
116
+ let updated = 0;
117
+ let inserted = 0;
118
+ let deleted = 0;
119
+
120
+ for (const update of changeSet.updates) {
121
+ try {
122
+ await updateRow(adapter, { schema, table, changes: update.changes, keys: update.key });
123
+ updated += 1;
124
+ rowResults.push({ type: "update", success: true, key: update.key });
125
+ } catch (error) {
126
+ rowResults.push({ type: "update", success: false, key: update.key, error: errorMessage(error) });
127
+ }
128
+ }
129
+
130
+ for (const insert of changeSet.inserts) {
131
+ try {
132
+ await insertRow(adapter, { schema, table, values: insert.values });
133
+ inserted += 1;
134
+ rowResults.push({ type: "insert", success: true });
135
+ } catch (error) {
136
+ rowResults.push({ type: "insert", success: false, error: errorMessage(error) });
137
+ }
138
+ }
139
+
140
+ for (const remove of changeSet.deletes) {
141
+ try {
142
+ await deleteRow(adapter, { schema, table, keys: remove.key });
143
+ deleted += 1;
144
+ rowResults.push({ type: "delete", success: true, key: remove.key });
145
+ } catch (error) {
146
+ rowResults.push({ type: "delete", success: false, key: remove.key, error: errorMessage(error) });
147
+ }
148
+ }
149
+
150
+ return {
151
+ success: rowResults.every((result) => result.success),
152
+ updated,
153
+ inserted,
154
+ deleted,
155
+ rowResults,
156
+ };
157
+ }
@@ -0,0 +1,397 @@
1
+ export const STUDIO_CLIENT_SCRIPT = `
2
+ "use strict";
3
+ (function(){
4
+ var RO_DEFAULT = !!window.SMDG_READONLY_DEFAULT;
5
+ var ENV_COLORS = { DEV:"#22c55e", QAS:"#f59e0b", PROD:"#ef4444", SANDBOX:"#6366f1", CUSTOM:"#3b82f6" };
6
+ var SWATCHES = ["#3b82f6","#22c55e","#f59e0b","#ef4444","#a855f7","#06b6d4","#ec4899","#84cc16","#64748b"];
7
+ var ICONS = {
8
+ 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",
9
+ sch:"M12 3l9 5-9 5-9-5 9-5z|M3 12l9 5 9-5|M3 16l9 5 9-5",
10
+ 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",
11
+ tbl:"M3 5h18v14H3z|M3 10h18|M9 5v14|M15 5v14",
12
+ 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",
13
+ 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",
14
+ fun:"M6 3h9l4 4v14H6z|M14 3v4h4|M9 12h6|M9 16h6",
15
+ 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",
16
+ idx:"M14 4l6 6-9 9H5v-6z|M3 21h18",
17
+ search:"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14z|M21 21l-4.3-4.3",
18
+ 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",
19
+ refresh:"M21 12a9 9 0 1 1-3-6.7|M21 4v5h-5",
20
+ x:"M6 6l12 12|M18 6L6 18",
21
+ plus:"M12 5v14|M5 12h14",
22
+ imp:"M12 3v12|M7 10l5 5 5-5|M5 21h14",
23
+ sql:"M4 5h16v14H4z|M7 9l3 3-3 3|M13 15h4",
24
+ run:"M13 3L4 14h7l-1 7 9-11h-7z",
25
+ save:"M5 3h11l3 3v15H5z|M8 3v6h7V3|M8 21v-7h8v7",
26
+ 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",
27
+ home:"M3 11l9-8 9 8|M5 10v10h14V10",
28
+ table2:"M4 4h16v16H4z|M4 9h16|M9 4v16",
29
+ col:"M5 4v16|M12 4v16|M19 4v16"
30
+ };
31
+ function 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>';}
32
+ function icEl(name,cls){var s=document.createElement("span");s.className="ticon "+(cls||"");s.innerHTML=svgFor(name);return s;}
33
+
34
+ /* ---------- dom helpers ---------- */
35
+ function $(id){return document.getElementById(id);}
36
+ function 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;}
37
+ function clear(n){while(n&&n.firstChild)n.removeChild(n.firstChild);return n;}
38
+ function esc(v){return String(v==null?"":v).replace(/[&<>"']/g,function(s){return {"&":"&amp;","<":"&lt;",">":"&gt;","\\"":"&quot;","'":"&#39;"}[s];});}
39
+ function debounce(fn,ms){var t;return function(){var a=arguments,c=this;clearTimeout(t);t=setTimeout(function(){fn.apply(c,a);},ms||220);};}
40
+ function topSpin(on){$("topSpin").className=on?"spin":"spin hidden";}
41
+
42
+ /* ---------- api ---------- */
43
+ function 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);});}
44
+ function 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("&");}
45
+
46
+ /* ---------- toast + status ---------- */
47
+ function 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);}
48
+ function logMsg(msg,kind){toast(msg,kind);}
49
+ function setConnStatus(text,kind){$("stConn").innerHTML="";$("stConn").appendChild(el("span",{class:"st-dot "+(kind||"")}));$("stConn").appendChild(el("span",{text:" "+text}));}
50
+ function setRun(on){$("stConn").firstChild.className="st-dot "+(on?"run":"ok");}
51
+
52
+ /* ---------- global state ---------- */
53
+ var S = { connections:[], activeConnId:"", connType:"", activeSchema:"", readOnly:RO_DEFAULT, tabs:[], activeTabId:"", seq:0, savedQueries:[] };
54
+ function activeConn(){return S.connections.filter(function(c){return c.id===S.activeConnId;})[0];}
55
+
56
+ /* ====================================================================
57
+ CONTEXT MENU
58
+ ==================================================================== */
59
+ function 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";}
60
+ function hideCtx(){$("contextMenu").classList.add("hidden");}
61
+ document.addEventListener("click",hideCtx);
62
+ document.addEventListener("scroll",hideCtx,true);
63
+
64
+ /* ====================================================================
65
+ MODAL
66
+ ==================================================================== */
67
+ function 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");}
68
+ function closeModal(){$("modalRoot").classList.add("hidden");clear($("modalRoot"));}
69
+
70
+ /* ====================================================================
71
+ CONNECTIONS
72
+ ==================================================================== */
73
+ function 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>';});}
74
+ function 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));});}
75
+ function 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);});
76
+ var star=el("span",{class:"star"+(c.isFavorite?" on":""),title:"Favorite",onclick:function(e){e.stopPropagation();toggleFavorite(c);}});star.innerHTML=svgFor("star");
77
+ card.appendChild(el("div",{class:"conn-top"},[icEl("db","db"),el("span",{class:"conn-name",text:c.name,title:c.name}),star]));
78
+ var sub=[c.org,c.space].filter(Boolean).join(" / ")||c.host;
79
+ card.appendChild(el("div",{class:"conn-sub",text:sub,title:sub}));
80
+ var tags=el("div",{class:"conn-tags"});
81
+ tags.appendChild(el("span",{class:"tag type",text:c.type==="hana"?"HANA":"PostgreSQL"}));
82
+ if(c.environment)tags.appendChild(el("span",{class:"tag env-"+c.environment,text:c.environment}));
83
+ if(c.schema||c.serviceName)tags.appendChild(el("span",{class:"tag",text:c.serviceName||c.schema}));
84
+ card.appendChild(tags);
85
+ return card;
86
+ }
87
+ function isProdConn(c){return c&&/prod|production|prd|live/i.test((c.environment||"")+" "+(c.org||"")+" "+(c.app||"")+" "+(c.space||""));}
88
+ function 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");}
89
+ function 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");}
90
+ function toggleFavorite(c){api("POST","/api/connections/update",{id:c.id,isFavorite:!c.isFavorite}).then(function(){return loadConnections();}).catch(function(e){logMsg(e.message,"err");});}
91
+ function connMenu(e,c){showCtx(e.clientX,e.clientY,[
92
+ {label:"Open SQL Console",icon:"sql",onClick:function(){S.activeConnId=c.id;S.connType=c.type;renderConnections();updateTopBadges();openSqlTab();}},
93
+ {label:"Connect / Refresh tree",icon:"refresh",onClick:function(){activateConnection(c.id);}},
94
+ {sep:true},
95
+ {label:"Test connection",icon:"run",onClick:function(){testConn(c);}},
96
+ {label:"Edit (name, color, env)",icon:"gear",onClick:function(){editConnModal(c);}},
97
+ {label:c.isFavorite?"Unfavorite":"Favorite",icon:"star",onClick:function(){toggleFavorite(c);}},
98
+ {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");}},
99
+ {label:"Duplicate",icon:"plus",onClick:function(){api("POST","/api/connections/duplicate",{id:c.id}).then(function(){return loadConnections();}).then(function(){logMsg("Duplicated.","ok");});}},
100
+ {sep:true},
101
+ {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");});}}
102
+ ]);}
103
+ function 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");});}
104
+ function 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);});
105
+ 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;
106
+ 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);}
107
+
108
+ /* ====================================================================
109
+ OBJECT TREE (DBeaver-style, lazy)
110
+ ==================================================================== */
111
+ function treeNode(opts){
112
+ var chev=el("span",{class:"tchev"+(opts.leaf?" leaf":""),html:"\\u203a"});
113
+ var label=el("span",{class:"tlabel",text:opts.label,title:opts.label});
114
+ var badge=el("span",{class:"tbadge"});
115
+ var spin=el("span",{class:"hidden"});
116
+ var row=el("div",{class:"trow"},[chev,icEl(opts.icon,opts.iconCls),label,badge,spin]);
117
+ var kids=el("div",{class:"tchildren hidden"});
118
+ var node=el("div",{class:"tnode"},[row,kids]);
119
+ node._loaded=false;node._open=false;
120
+ function setLoading(on){spin.className=on?"spin":"hidden";}
121
+ function setBadge(t){badge.textContent=t==null?"":"("+t+")";}
122
+ 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);});}}
123
+ function collapse(){node._open=false;chev.classList.remove("open");kids.classList.add("hidden");}
124
+ function toggle(){node._open?collapse():expand();}
125
+ if(!opts.leaf)chev.addEventListener("click",function(e){e.stopPropagation();toggle();});
126
+ row.addEventListener("click",function(){if(opts.onClick)opts.onClick();else if(!opts.leaf)toggle();});
127
+ if(opts.onDblClick)row.addEventListener("dblclick",opts.onDblClick);
128
+ if(opts.onMenu)row.addEventListener("contextmenu",function(e){e.preventDefault();opts.onMenu(e);});
129
+ node._row=row;node._kids=kids;node._expand=expand;node._reload=function(){node._loaded=false;clear(kids);if(node._open)expand();};
130
+ return node;
131
+ }
132
+ function buildTreeForConnection(){var t=clear($("tree"));var c=activeConn();if(!c){t.appendChild(el("div",{class:"tnote",text:"Select a connection."}));return;}
133
+ var root=treeNode({label:c.name,icon:"db",iconCls:"db",onExpand:function(kids){
134
+ var cat=treeNode({label:"Catalog",icon:"fld",iconCls:"fld",onExpand:function(k2){
135
+ var schemas=treeNode({label:"Schemas",icon:"sch",iconCls:"sch",onExpand:loadSchemasNode});
136
+ k2.appendChild(schemas);schemas._expand();
137
+ }});
138
+ kids.appendChild(cat);cat._expand();
139
+ }});
140
+ t.appendChild(root);root._expand();
141
+ }
142
+ function 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();}});}
143
+ function schemaNode(s){return treeNode({label:s.name,icon:"sch",iconCls:"sch",onClick:function(){S.activeSchema=s.name;updateTopBadges();},onExpand:function(kids){
144
+ var folders=[["Tables","table","tbl"],["Views","view","viw"],["Procedures","procedure","prc"],["Functions","function","fun"],["Synonyms","synonym","syn"],["Indexes","index","idx"]];
145
+ folders.forEach(function(f){kids.appendChild(folderNode(s.name,f[0],f[1],f[2]));});
146
+ }});}
147
+ function folderNode(schema,label,kind,iconCls){return treeNode({label:label,icon:"fld",iconCls:"fld",onExpand:function(kids,setBadge){
148
+ if(kind==="index"){kids.appendChild(el("div",{class:"tnote",text:"Open a table's Structure to view its indexes."}));setBadge(null);return;}
149
+ var listBox=el("div");
150
+ var search=el("input",{class:"input",placeholder:"Search "+label.toLowerCase()+"..."});
151
+ var sb=el("div",{class:"searchbox tsearch"},[el("span",{html:svgFor("search")}),search]);
152
+ kids.appendChild(sb);kids.appendChild(listBox);
153
+ 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}));});};
154
+ search.addEventListener("input",debounce(run,250));
155
+ run();
156
+ }});}
157
+ function 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,
158
+ onClick:function(){selectTreeRow(this);},
159
+ onDblClick:canData?function(){openDataTab(schema,o.name);}:null,
160
+ onMenu:canData?function(e){objectMenu(e,schema,o);}:function(e){objectMenu(e,schema,o,true);}
161
+ });}
162
+ var _selRow=null;
163
+ function selectTreeRow(node){if(_selRow)_selRow._row.classList.remove("sel");_selRow=node;node._row.classList.add("sel");}
164
+ function 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);});}});}
165
+ items.push({label:"Copy Full Name",icon:"col",onClick:function(){navigator.clipboard.writeText(qn);logMsg("Copied "+qn,"ok");}});
166
+ showCtx(e.clientX,e.clientY,items);}
167
+
168
+ /* ====================================================================
169
+ WORKSPACE TABS
170
+ ==================================================================== */
171
+ function 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);});}
172
+ function 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;}
173
+ function 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();}
174
+ function tabById(id){return S.tabs.filter(function(t){return t.id===id;})[0];}
175
+ function setDirty(tab,on){tab.dirty=on;renderTabBar();updatePendingStatus();}
176
+ function 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();}
177
+ function 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";}
178
+
179
+ /* ====================================================================
180
+ WELCOME
181
+ ==================================================================== */
182
+ function openWelcome(){openTab({key:"welcome",kind:"welcome",title:"Welcome",icon:"home",closable:false,build:buildWelcome});}
183
+ function 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"}));
184
+ var cards=el("div",{class:"wcards"});
185
+ cards.appendChild(wcard("imp","Import from BTP App","Read cf env and detect HANA/PostgreSQL credentials.",openBtpWizard));
186
+ cards.appendChild(wcard("plus","Add direct connection","Connect by host/port/user like DBeaver.",function(){newConnModal();}));
187
+ 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();}));
188
+ 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);}));
189
+ w.appendChild(cards);
190
+ var cols=el("div",{class:"wcols"});
191
+ 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);
192
+ 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);
193
+ cols.appendChild(recent);cols.appendChild(rq);w.appendChild(cols);
194
+ pane.appendChild(w);
195
+ }
196
+ function 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})]);}
197
+
198
+ /* ====================================================================
199
+ SQL CONSOLE TAB
200
+ ==================================================================== */
201
+ var DANGER=/\\b(drop|truncate|alter|grant|revoke)\\b/i;
202
+ function 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");}});}
203
+ function buildSqlPane(pane,tab,initialSql){
204
+ var editor=el("textarea",{class:"editor",spellcheck:"false"});editor.value=initialSql;tab.state.editor=editor;
205
+ editor.addEventListener("input",function(){setDirty(tab,true);});
206
+ editor.addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&e.key==="Enter"){e.preventDefault();runSqlTab(tab);}});
207
+ 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;
208
+ var runBtn=el("button",{class:"btn",onclick:function(){runSqlTab(tab);}},[el("span",{html:svgFor("run")})," Run"]);tab.state.runBtn=runBtn;
209
+ 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})]);
210
+ 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;
211
+ body.appendChild(editor);body.appendChild(errBox);body.appendChild(el("div",{class:"note",text:"Result"}));body.appendChild(grid);
212
+ pane.appendChild(tb);pane.appendChild(body);
213
+ }
214
+ function 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();}
215
+ function 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");
216
+ if(!confirmed&&DANGER.test(sql)){if(!confirm("This statement may modify or drop data:\\n\\n"+sql.slice(0,160)+"\\n\\nRun anyway?"))return;}
217
+ 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");
218
+ var limit=parseInt(tab.state.limit.value,10);
219
+ api("POST","/api/query/run",{connectionId:S.activeConnId,sql:sql,limit:limit,readOnly:S.readOnly,confirmDangerous:true}).then(function(r){
220
+ tab.state.runBtn.innerHTML=svgFor("run")+" Run";tab.state.runBtn.disabled=false;setConnStatus("Connected","ok");
221
+ 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;}
222
+ if(!r.ok){tab.state.err.textContent="SQL failed ("+(S.connType==="hana"?"HANA":"PostgreSQL")+")\\n"+r.error;tab.state.err.classList.remove("hidden");return;}
223
+ tab.state.lastResult=r.result;renderResultGrid(tab.state.grid,r.result,null);
224
+ $("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":"");
225
+ $("stDuration").textContent=r.result.durationMs+"ms";$("stRows").textContent=r.result.rowCount+" rows";
226
+ }).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");});
227
+ }
228
+ function explainSqlTab(tab){if(S.connType!=="postgresql")return logMsg("Explain currently supports PostgreSQL.","warn");tab.state.editor.value="EXPLAIN "+currentSqlText(tab);runSqlTab(tab,true);}
229
+ function 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");});}
230
+ function 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");});}
231
+ function 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();}
232
+
233
+ /* generic read-only result grid (sql console) */
234
+ function 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);}
235
+
236
+ /* ====================================================================
237
+ DATA GRID TAB (editable, pending changes)
238
+ ==================================================================== */
239
+ function 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);}});}
240
+ function pendingCount(g){return Object.keys(g.edits).length+Object.keys(g.deletes).length+g.inserts.length;}
241
+ function rowKeyOf(g,row){return g.pk.map(function(k){return String(row[k]);}).join("\\u0001");}
242
+ function buildDataPane(pane,tab,schema,table){
243
+ 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;
244
+ var whereI=el("input",{class:"input",style:"flex:1",placeholder:"WHERE clause, e.g. STATUS = 'A'"});g.whereI=whereI;
245
+ 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;
246
+ var pageInfo=el("span",{class:"note"});g.pageInfo=pageInfo;
247
+ 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");}})]);
248
+ 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;
249
+ var editHint=el("span",{class:"note"});g.editHint=editHint;
250
+ 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]);
251
+ var grid=el("div",{class:"gridwrap"});g.grid=grid;
252
+ pane.appendChild(tb1);pane.appendChild(tb2);pane.appendChild(grid);
253
+ updateDirtyButtons(tab);
254
+ api("GET","/api/catalog/columns?"+qstr({connectionId:S.activeConnId,schema:schema,table:table})).then(function(r){g.columns=r.columns||[];}).catch(function(){});
255
+ 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="";});
256
+ loadData(tab);
257
+ loadCount(tab);
258
+ }
259
+ function 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."}));});}
260
+ function 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(){});}
261
+ function 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);}
262
+ function 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;}
263
+ var fields=g.columns.length?g.columns.map(function(c){return c.name;}):(g.rows[0]?Object.keys(g.rows[0]):[]);
264
+ 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);
265
+ var tbody=el("tbody");
266
+ 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 ":"")});
267
+ var flag=edited?'<span class="rowflag d"></span>':(deleted?'<span class="rowflag del"></span>':"");
268
+ 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);
269
+ 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);});
270
+ if(err){var etr=tr;etr.title=err;}
271
+ tbody.appendChild(tr);});
272
+ 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);}}));
273
+ 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);});
274
+ if(ins.error){tr.classList.add("row-err");tr.title=ins.error;}
275
+ tbody.appendChild(tr);});
276
+ table.appendChild(tbody);box.appendChild(table);
277
+ updateDirtyButtons(tab);
278
+ }
279
+ function 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);}
280
+ function 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);}
281
+ function addInsertRow(tab){var g=tab.state.g;g.inserts.push({iseq:++g.iseq,values:{}});renderGrid(tab);}
282
+ function revertAll(tab){var g=tab.state.g;g.edits={};g.deletes={};g.inserts=[];g.errors={};renderGrid(tab);logMsg("Reverted pending changes.","ok");}
283
+ function 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);}
284
+ function 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();});}
285
+ function saveDataChanges(tab){var g=tab.state.g;if(S.readOnly)return logMsg("Read-only mode is on.","warn");
286
+ 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]};});
287
+ 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};});
288
+ var inserts=g.inserts.filter(function(i){return Object.keys(i.values).length;}).map(function(i){return {values:i.values,_ref:i};});
289
+ var total=updates.length+deletes.length+inserts.length;if(!total)return logMsg("No changes to save.","warn");
290
+ if(!confirm("Save changes?\\n\\nUpdates: "+updates.length+"\\nInserts: "+inserts.length+"\\nDeletes: "+deletes.length))return;
291
+ setConnStatus("Saving changes...","run");
292
+ 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");
293
+ if(resp.blocked){logMsg(resp.error||"Blocked by read-only.","err");return;}
294
+ var rr=resp.result.rowResults||[];var ui=0;
295
+ // updates first, then inserts, then deletes (server order)
296
+ var failedU=0,failedI=0,failedD=0;
297
+ 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";}});
298
+ 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++;}});
299
+ 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++;}});
300
+ var ok=resp.result.updated+resp.result.inserted+resp.result.deleted;var fail=failedU+failedI+failedD;
301
+ if(fail===0){logMsg(ok+" change(s) saved.","ok");loadData(tab);loadCount(tab);}
302
+ else{logMsg(ok+" saved, "+fail+" failed. Failed rows kept pending with error markers.","err");renderGrid(tab);}
303
+ updateDirtyButtons(tab);
304
+ }).catch(function(e){setConnStatus("Connected","ok");logMsg("Save failed: "+e.message,"err");});
305
+ }
306
+ function pkObj(g,row){var o={};g.pk.forEach(function(k){o[k]=row[k];});return o;}
307
+ function rowKeyFromKeyObj(g,keyObj){return g.pk.map(function(k){return String(keyObj[k]);}).join("\\u0001");}
308
+
309
+ /* ====================================================================
310
+ STRUCTURE / METADATA TAB
311
+ ==================================================================== */
312
+ function 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);}});}
313
+ function buildStructure(pane,tab,schema,table){
314
+ var subtabs=el("div",{class:"meta-tabs"});var body=el("div",{class:"pane-body"});
315
+ var defs=[["columns","Columns"],["indexes","Indexes"],["ddl","DDL"],["info","Info"]];
316
+ var active="columns";var data={};
317
+ 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);});}
318
+ defs.forEach(function(d){subtabs.appendChild(el("div",{class:"meta-tab",text:d[1],onclick:function(){active=d[0];render();}}));});
319
+ 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);}})]));
320
+ pane.appendChild(subtabs);pane.appendChild(body);
321
+ 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}));});}
322
+ 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}));});}
323
+ 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}));});}
324
+ 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}));});}
325
+ render();
326
+ }
327
+
328
+ /* ====================================================================
329
+ SAVED QUERIES
330
+ ==================================================================== */
331
+ function loadSavedQueries(){return api("GET","/api/queries").then(function(r){S.savedQueries=r.queries||[];renderSavedQueries();}).catch(function(){});}
332
+ function 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);});}
333
+ function 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);}}]);}
334
+
335
+ /* ====================================================================
336
+ DIRECT CONNECTION MODAL
337
+ ==================================================================== */
338
+ function newConnModal(){var typeSel=el("select",{class:"select"});typeSel.appendChild(el("option",{value:"postgresql",text:"PostgreSQL"}));typeSel.appendChild(el("option",{value:"hana",text:"SAP HANA"}));
339
+ 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;
340
+ typeSel.addEventListener("change",function(){f.port.value=typeSel.value==="hana"?"443":"5432";f.schema.value=typeSel.value==="hana"?"":"public";});
341
+ var msg=el("div",{class:"note"});
342
+ 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};}
343
+ 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);}
344
+
345
+ /* ====================================================================
346
+ BTP IMPORT WIZARD
347
+ ==================================================================== */
348
+ function openBtpWizard(){var stState={apps:[],services:[],app:"",svc:null,color:"",env:""};
349
+ var steps=el("div",{class:"steps"});["Target","App","Services","Save"].forEach(function(s){steps.appendChild(el("div",{class:"step",text:s}));});
350
+ var body=el("div");var msg=el("div",{class:"note"});
351
+ function setStep(i){Array.prototype.forEach.call(steps.children,function(ch,idx){ch.className="step"+(idx===i?" active":idx<i?" done":"");});}
352
+ 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}));});}
353
+ 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}));});}
354
+ 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}));});}
355
+ 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)"}));});
356
+ 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);});
357
+ var fav=el("input",{type:"checkbox"});
358
+ 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})]));
359
+ body.appendChild(el("div",{class:"field"},[el("label",{text:"Display name"}),nameI]));
360
+ body.appendChild(el("div",{class:"field"},[el("label",{text:"Environment"}),envSel]));
361
+ body.appendChild(el("div",{class:"field"},[el("label",{text:"Color"}),sw]));
362
+ body.appendChild(el("label",{class:"note"},[fav," Mark as favorite"]));
363
+ 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;});}})]));
364
+ body.appendChild(msg);
365
+ }
366
+ var d=el("div",{class:"dialog"},[el("h3",{text:"Import from BTP App"}),steps,body]);openModal(d);stepTarget();
367
+ }
368
+
369
+ /* ====================================================================
370
+ READ-ONLY + INIT
371
+ ==================================================================== */
372
+ function applyReadOnly(){var b=$("roBadge");b.className="badge ro"+(S.readOnly?" active":"");b.textContent=S.readOnly?"Read-only":"Read/Write";}
373
+ function toggleReadOnly(){S.readOnly=!S.readOnly;applyReadOnly();logMsg("Read-only mode: "+(S.readOnly?"ON":"OFF"),"ok");}
374
+
375
+ function initSidebarCollapse(){Array.prototype.forEach.call(document.querySelectorAll(".side-head"),function(h){h.addEventListener("click",function(){h.parentNode.classList.toggle("collapsed");});});}
376
+ function 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="";});}
377
+
378
+ window.addEventListener("load",function(){
379
+ applyReadOnly();
380
+ initSidebarCollapse();initResizer();
381
+ $("roBadge").addEventListener("click",toggleReadOnly);
382
+ $("btnImport").addEventListener("click",openBtpWizard);
383
+ $("btnImport2").addEventListener("click",openBtpWizard);
384
+ $("btnNewConn").addEventListener("click",newConnModal);
385
+ $("btnSettings").addEventListener("click",function(){logMsg("Read-only toggle and connection colors are in the top bar / connection menu.","ok");});
386
+ $("btnHome").addEventListener("click",openWelcome);
387
+ $("connSearch").addEventListener("input",debounce(renderConnections,200));
388
+ $("querySearch").addEventListener("input",debounce(renderSavedQueries,200));
389
+ $("topSearch").addEventListener("input",debounce(function(){$("connSearch").value=$("topSearch").value;renderConnections();},200));
390
+ $("btnNewQuery").addEventListener("click",function(){if(!S.activeConnId)return logMsg("Select a connection first.","warn");openSqlTab();});
391
+ setConnStatus("Ready","ok");
392
+ openWelcome();
393
+ loadConnections();
394
+ loadSavedQueries();
395
+ });
396
+ })();
397
+ `;