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
@@ -0,0 +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 {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",\"\\\"\":\"&quot;\",\"'\":\"&#39;\"}[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";