simplemdg-dev-cli 2.0.4 → 2.4.4

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 (140) hide show
  1. package/README.md +63 -354
  2. package/USER_GUIDE.md +55 -378
  3. package/dist/commands/cds.command.js +69 -60
  4. package/dist/commands/cds.command.js.map +1 -1
  5. package/dist/commands/cf-db.command.d.ts +2 -0
  6. package/dist/commands/cf-db.command.js +606 -0
  7. package/dist/commands/cf-db.command.js.map +1 -0
  8. package/dist/commands/cf.command.js +291 -280
  9. package/dist/commands/cf.command.js.map +1 -1
  10. package/dist/commands/gitlab.command.d.ts +2 -0
  11. package/dist/commands/gitlab.command.js +351 -0
  12. package/dist/commands/gitlab.command.js.map +1 -0
  13. package/dist/commands/npmrc.command.js +50 -44
  14. package/dist/commands/npmrc.command.js.map +1 -1
  15. package/dist/core/cache.d.ts +1 -1
  16. package/dist/core/cache.js +58 -31
  17. package/dist/core/cache.js.map +1 -1
  18. package/dist/core/cds.js +32 -22
  19. package/dist/core/cds.js.map +1 -1
  20. package/dist/core/cf-env-parser.d.ts +1 -1
  21. package/dist/core/cf-env-parser.js +4 -1
  22. package/dist/core/cf-env-parser.js.map +1 -1
  23. package/dist/core/cf.d.ts +1 -1
  24. package/dist/core/cf.js +46 -31
  25. package/dist/core/cf.js.map +1 -1
  26. package/dist/core/db/db-btp.d.ts +48 -0
  27. package/dist/core/db/db-btp.js +162 -0
  28. package/dist/core/db/db-btp.js.map +1 -0
  29. package/dist/core/db/db-cache.d.ts +35 -0
  30. package/dist/core/db/db-cache.js +164 -0
  31. package/dist/core/db/db-cache.js.map +1 -0
  32. package/dist/core/db/db-connection.d.ts +22 -0
  33. package/dist/core/db/db-connection.js +73 -0
  34. package/dist/core/db/db-connection.js.map +1 -0
  35. package/dist/core/db/db-crypto.d.ts +3 -0
  36. package/dist/core/db/db-crypto.js +54 -0
  37. package/dist/core/db/db-crypto.js.map +1 -0
  38. package/dist/core/db/db-hana-adapter.d.ts +32 -0
  39. package/dist/core/db/db-hana-adapter.js +243 -0
  40. package/dist/core/db/db-hana-adapter.js.map +1 -0
  41. package/dist/core/db/db-metadata.d.ts +25 -0
  42. package/dist/core/db/db-metadata.js +150 -0
  43. package/dist/core/db/db-metadata.js.map +1 -0
  44. package/dist/core/db/db-postgres-adapter.d.ts +30 -0
  45. package/dist/core/db/db-postgres-adapter.js +245 -0
  46. package/dist/core/db/db-postgres-adapter.js.map +1 -0
  47. package/dist/core/db/db-query-files.d.ts +20 -0
  48. package/dist/core/db/db-query-files.js +106 -0
  49. package/dist/core/db/db-query-files.js.map +1 -0
  50. package/dist/core/db/db-query-history.d.ts +5 -0
  51. package/dist/core/db/db-query-history.js +49 -0
  52. package/dist/core/db/db-query-history.js.map +1 -0
  53. package/dist/core/db/db-row.d.ts +22 -0
  54. package/dist/core/db/db-row.js +70 -0
  55. package/dist/core/db/db-row.js.map +1 -0
  56. package/dist/core/db/db-studio-html.d.ts +4 -0
  57. package/dist/core/db/db-studio-html.js +437 -0
  58. package/dist/core/db/db-studio-html.js.map +1 -0
  59. package/dist/core/db/db-studio-server.d.ts +11 -0
  60. package/dist/core/db/db-studio-server.js +465 -0
  61. package/dist/core/db/db-studio-server.js.map +1 -0
  62. package/dist/core/db/db-types.d.ts +174 -0
  63. package/dist/core/db/db-types.js +3 -0
  64. package/dist/core/db/db-types.js.map +1 -0
  65. package/dist/core/db/db-vcap-parser.d.ts +7 -0
  66. package/dist/core/db/db-vcap-parser.js +137 -0
  67. package/dist/core/db/db-vcap-parser.js.map +1 -0
  68. package/dist/core/doctor.d.ts +1 -1
  69. package/dist/core/doctor.js +14 -8
  70. package/dist/core/doctor.js.map +1 -1
  71. package/dist/core/guide.js +31 -26
  72. package/dist/core/guide.js.map +1 -1
  73. package/dist/core/install.d.ts +1 -1
  74. package/dist/core/install.js +17 -11
  75. package/dist/core/install.js.map +1 -1
  76. package/dist/core/navigator.d.ts +17 -0
  77. package/dist/core/navigator.js +140 -0
  78. package/dist/core/navigator.js.map +1 -0
  79. package/dist/core/npmrc.js +29 -16
  80. package/dist/core/npmrc.js.map +1 -1
  81. package/dist/core/process.js +11 -6
  82. package/dist/core/process.js.map +1 -1
  83. package/dist/core/prompts.js +16 -8
  84. package/dist/core/prompts.js.map +1 -1
  85. package/dist/core/repository.d.ts +1 -1
  86. package/dist/core/repository.js +16 -9
  87. package/dist/core/repository.js.map +1 -1
  88. package/dist/core/scanner.d.ts +1 -1
  89. package/dist/core/scanner.js +13 -7
  90. package/dist/core/scanner.js.map +1 -1
  91. package/dist/core/tooling.d.ts +28 -0
  92. package/dist/core/tooling.js +168 -0
  93. package/dist/core/tooling.js.map +1 -0
  94. package/dist/core/types.js +2 -1
  95. package/dist/core/version-conflict.d.ts +2 -2
  96. package/dist/core/version-conflict.js +11 -6
  97. package/dist/core/version-conflict.js.map +1 -1
  98. package/dist/index.js +65 -48
  99. package/dist/index.js.map +1 -1
  100. package/dist/types-local.js +2 -1
  101. package/package.json +12 -6
  102. package/src/commands/cds.command.ts +529 -0
  103. package/src/commands/cf-db.command.ts +636 -0
  104. package/src/commands/cf.command.ts +3345 -0
  105. package/src/commands/gitlab.command.ts +373 -0
  106. package/src/commands/npmrc.command.ts +581 -0
  107. package/src/core/cache.ts +332 -0
  108. package/src/core/cds.ts +278 -0
  109. package/src/core/cf-env-parser.ts +131 -0
  110. package/src/core/cf.ts +271 -0
  111. package/src/core/db/db-btp.ts +207 -0
  112. package/src/core/db/db-cache.ts +215 -0
  113. package/src/core/db/db-connection.ts +79 -0
  114. package/src/core/db/db-crypto.ts +53 -0
  115. package/src/core/db/db-hana-adapter.ts +294 -0
  116. package/src/core/db/db-metadata.ts +174 -0
  117. package/src/core/db/db-postgres-adapter.ts +275 -0
  118. package/src/core/db/db-query-files.ts +130 -0
  119. package/src/core/db/db-query-history.ts +53 -0
  120. package/src/core/db/db-row.ts +93 -0
  121. package/src/core/db/db-studio-html.ts +439 -0
  122. package/src/core/db/db-studio-server.ts +559 -0
  123. package/src/core/db/db-types.ts +195 -0
  124. package/src/core/db/db-vcap-parser.ts +182 -0
  125. package/src/core/doctor.ts +70 -0
  126. package/src/core/guide.ts +261 -0
  127. package/src/core/install.ts +91 -0
  128. package/src/core/navigator.ts +164 -0
  129. package/src/core/npmrc.ts +171 -0
  130. package/src/core/process.ts +75 -0
  131. package/src/core/prompts.ts +225 -0
  132. package/src/core/repository.ts +36 -0
  133. package/src/core/scanner.ts +41 -0
  134. package/src/core/tooling.ts +207 -0
  135. package/src/core/types.ts +152 -0
  136. package/src/core/version-conflict.ts +46 -0
  137. package/src/index.ts +460 -0
  138. package/src/types/external.d.ts +3 -0
  139. package/src/types-local.ts +11 -0
  140. package/tsconfig.json +17 -0
@@ -0,0 +1,22 @@
1
+ import type { IDatabaseAdapter, TDatabaseQueryResult } from "./db-types";
2
+ export type TRowValues = Record<string, unknown>;
3
+ /**
4
+ * Build and run a parameterized UPDATE that targets exactly one row using its
5
+ * key columns. Values are passed as bind parameters, never string-interpolated.
6
+ */
7
+ export declare function updateRow(adapter: IDatabaseAdapter, options: {
8
+ schema: string;
9
+ table: string;
10
+ changes: TRowValues;
11
+ keys: TRowValues;
12
+ }): Promise<TDatabaseQueryResult>;
13
+ export declare function deleteRow(adapter: IDatabaseAdapter, options: {
14
+ schema: string;
15
+ table: string;
16
+ keys: TRowValues;
17
+ }): Promise<TDatabaseQueryResult>;
18
+ export declare function insertRow(adapter: IDatabaseAdapter, options: {
19
+ schema: string;
20
+ table: string;
21
+ values: TRowValues;
22
+ }): Promise<TDatabaseQueryResult>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateRow = updateRow;
4
+ exports.deleteRow = deleteRow;
5
+ exports.insertRow = insertRow;
6
+ function ensureKeyColumns(keys) {
7
+ const columns = Object.keys(keys);
8
+ if (columns.length === 0) {
9
+ throw new Error("Cannot identify the row: the table has no primary key. Edit it from the SQL Console instead.");
10
+ }
11
+ return columns;
12
+ }
13
+ /**
14
+ * Build and run a parameterized UPDATE that targets exactly one row using its
15
+ * key columns. Values are passed as bind parameters, never string-interpolated.
16
+ */
17
+ async function updateRow(adapter, options) {
18
+ const changeColumns = Object.keys(options.changes);
19
+ if (changeColumns.length === 0) {
20
+ throw new Error("No changes to save.");
21
+ }
22
+ const keyColumns = ensureKeyColumns(options.keys);
23
+ const params = [];
24
+ let position = 1;
25
+ const setClause = changeColumns
26
+ .map((column) => {
27
+ params.push(options.changes[column]);
28
+ return `${adapter.quoteIdentifier(column)} = ${adapter.placeholder(position++)}`;
29
+ })
30
+ .join(", ");
31
+ const whereClause = keyColumns
32
+ .map((column) => {
33
+ params.push(options.keys[column]);
34
+ return `${adapter.quoteIdentifier(column)} = ${adapter.placeholder(position++)}`;
35
+ })
36
+ .join(" AND ");
37
+ const sql = `UPDATE ${adapter.buildQualifiedName(options.schema, options.table)} SET ${setClause} WHERE ${whereClause}`;
38
+ return adapter.runParameterized(sql, params);
39
+ }
40
+ async function deleteRow(adapter, options) {
41
+ const keyColumns = ensureKeyColumns(options.keys);
42
+ const params = [];
43
+ let position = 1;
44
+ const whereClause = keyColumns
45
+ .map((column) => {
46
+ params.push(options.keys[column]);
47
+ return `${adapter.quoteIdentifier(column)} = ${adapter.placeholder(position++)}`;
48
+ })
49
+ .join(" AND ");
50
+ const sql = `DELETE FROM ${adapter.buildQualifiedName(options.schema, options.table)} WHERE ${whereClause}`;
51
+ return adapter.runParameterized(sql, params);
52
+ }
53
+ async function insertRow(adapter, options) {
54
+ const columns = Object.keys(options.values);
55
+ if (columns.length === 0) {
56
+ throw new Error("No values to insert.");
57
+ }
58
+ const params = [];
59
+ let position = 1;
60
+ const columnClause = columns.map((column) => adapter.quoteIdentifier(column)).join(", ");
61
+ const valueClause = columns
62
+ .map((column) => {
63
+ params.push(options.values[column]);
64
+ return adapter.placeholder(position++);
65
+ })
66
+ .join(", ");
67
+ const sql = `INSERT INTO ${adapter.buildQualifiedName(options.schema, options.table)} (${columnClause}) VALUES (${valueClause})`;
68
+ return adapter.runParameterized(sql, params);
69
+ }
70
+ //# sourceMappingURL=db-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-row.js","sourceRoot":"","sources":["../../../src/core/db/db-row.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export type TStudioHtmlOptions = {
2
+ readOnlyDefault: boolean;
3
+ };
4
+ export declare function renderStudioHtml(options: TStudioHtmlOptions): string;
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderStudioHtml = renderStudioHtml;
4
+ const STUDIO_STYLES = `
5
+ :root{
6
+ --bg:#0b0f17; --bg-2:#0f1622; --bg-3:#131c2b; --panel:#0e1521; --border:#21304a;
7
+ --text:#dce6f5; --muted:#8aa0c0; --accent:#3b82f6; --accent-2:#1d4ed8;
8
+ --green:#22c55e; --red:#ef4444; --yellow:#f59e0b; --chip:#1b2940;
9
+ }
10
+ *{box-sizing:border-box}
11
+ html,body{height:100%}
12
+ body{margin:0;background:var(--bg);color:var(--text);font:13px/1.4 "Segoe UI",Roboto,Arial,sans-serif}
13
+ button,input,select,textarea{font:inherit;color:inherit}
14
+ .app{display:flex;flex-direction:column;height:100vh}
15
+ .topbar{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg-2);border-bottom:1px solid var(--border);flex:0 0 auto}
16
+ .topbar .brand{font-weight:700;color:#cfe0ff;letter-spacing:.3px}
17
+ .topbar .spacer{flex:1}
18
+ .badge{padding:3px 9px;border-radius:999px;background:var(--chip);border:1px solid var(--border);color:var(--muted);font-size:12px}
19
+ .badge.active{color:#cfe0ff;border-color:var(--accent)}
20
+ .badge.warn{color:#fff;background:#7c2d12;border-color:#b45309}
21
+ .body{display:flex;flex:1;min-height:0}
22
+ .sidebar{width:320px;min-width:200px;max-width:560px;background:var(--panel);border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}
23
+ .resizer{width:5px;cursor:col-resize;background:transparent}
24
+ .resizer:hover{background:var(--accent)}
25
+ .main{flex:1;display:flex;flex-direction:column;min-width:0}
26
+ .section{padding:10px;border-bottom:1px solid var(--border)}
27
+ .section h3{margin:0 0 8px;font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted)}
28
+ .row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
29
+ .grow{flex:1}
30
+ .btn{background:var(--accent);border:1px solid var(--accent-2);color:#fff;border-radius:7px;padding:6px 10px;cursor:pointer}
31
+ .btn:hover{filter:brightness(1.08)}
32
+ .btn.sec{background:#22304a;border-color:var(--border);color:#cfe0ff}
33
+ .btn.ghost{background:transparent;border-color:var(--border);color:var(--muted)}
34
+ .btn.sm{padding:3px 7px;font-size:12px}
35
+ .btn:disabled{opacity:.5;cursor:not-allowed}
36
+ .input,.select,.search{width:100%;background:var(--bg-3);border:1px solid var(--border);border-radius:7px;padding:7px 9px;color:var(--text)}
37
+ .list{overflow:auto;flex:1}
38
+ .item{padding:7px 9px;border:1px solid transparent;border-radius:7px;cursor:pointer}
39
+ .item:hover{background:var(--bg-3)}
40
+ .item.active{background:#16243d;border-color:var(--accent)}
41
+ .item .title{font-weight:600}
42
+ .item .sub{color:var(--muted);font-size:12px}
43
+ .tree-group{margin-top:6px}
44
+ .tree-group .ghead{display:flex;justify-content:space-between;cursor:pointer;color:var(--muted);padding:4px 6px;border-radius:6px}
45
+ .tree-group .ghead:hover{background:var(--bg-3)}
46
+ .tree-item{padding:4px 6px 4px 18px;border-radius:6px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
47
+ .tree-item:hover{background:var(--bg-3)}
48
+ .tree-item.active{background:#16243d}
49
+ .tabs{display:flex;background:var(--bg-2);border-bottom:1px solid var(--border);flex:0 0 auto;overflow:auto}
50
+ .tab{padding:11px 15px;cursor:pointer;color:var(--muted);border-right:1px solid var(--border);white-space:nowrap}
51
+ .tab:hover{color:var(--text)}
52
+ .tab.active{color:#bcd4ff;background:var(--bg-3);box-shadow:inset 0 -2px 0 var(--accent)}
53
+ .panels{flex:1;min-height:0;position:relative}
54
+ .panel{display:none;position:absolute;inset:0;flex-direction:column;padding:12px;overflow:auto}
55
+ .panel.active{display:flex}
56
+ .toolbar{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:8px}
57
+ .editor{width:100%;height:200px;resize:vertical;background:#0a1018;border:1px solid var(--border);border-radius:8px;padding:10px;color:#e7eefc;font-family:Consolas,"Cascadia Code",monospace;font-size:13px}
58
+ .gridwrap{flex:1;min-height:120px;overflow:auto;border:1px solid var(--border);border-radius:8px;background:var(--bg-3)}
59
+ table.grid{border-collapse:collapse;width:100%;font-size:12.5px}
60
+ table.grid th,table.grid td{border-bottom:1px solid var(--border);border-right:1px solid var(--border);padding:6px 9px;text-align:left;white-space:nowrap;max-width:420px;overflow:hidden;text-overflow:ellipsis}
61
+ table.grid th{position:sticky;top:0;background:#16203250;backdrop-filter:blur(2px);background:#1a2740;color:#cfe0ff;cursor:pointer}
62
+ table.grid tr:hover td{background:#142036}
63
+ table.grid td.num{text-align:right;color:#a7f3d0}
64
+ .statusbar{display:flex;gap:14px;align-items:center;padding:6px 12px;background:var(--bg-2);border-top:1px solid var(--border);color:var(--muted);font-size:12px;flex:0 0 auto}
65
+ .statusbar .ok{color:var(--green)} .statusbar .err{color:var(--red)}
66
+ .msg{font-family:Consolas,monospace;white-space:pre-wrap;background:#0a1018;border:1px solid var(--border);border-radius:8px;padding:10px;color:#cbd5e1}
67
+ .spinner{width:14px;height:14px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;display:inline-block;animation:spin .8s linear infinite}
68
+ @keyframes spin{to{transform:rotate(360deg)}}
69
+ .hidden{display:none!important}
70
+ .kvs{display:grid;grid-template-columns:140px 1fr;gap:4px 12px}
71
+ .kvs div.k{color:var(--muted)}
72
+ .pill{display:inline-block;padding:1px 7px;border-radius:999px;background:var(--chip);border:1px solid var(--border);font-size:11px;color:var(--muted)}
73
+ .pill.pk{color:#fde68a;border-color:#a16207}
74
+ .note{color:var(--muted);font-size:12px}
75
+ .empty{color:var(--muted);padding:14px;text-align:center}
76
+ .modal{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:50}
77
+ .modal .dialog{background:var(--bg-2);border:1px solid var(--border);border-radius:12px;padding:18px;width:520px;max-width:94vw;max-height:92vh;overflow:auto}
78
+ .modal h3{margin:0 0 12px}
79
+ .field{display:flex;flex-direction:column;gap:4px;margin-bottom:9px}
80
+ .field label{color:var(--muted);font-size:12px}
81
+ table.grid th .sort{color:var(--accent);margin-left:4px}
82
+ table.grid th.rowhdr,table.grid td.rowhdr{width:48px;text-align:right;color:var(--muted);background:#16223a;cursor:pointer}
83
+ table.grid tr.selrow td{background:#1e3a5f}
84
+ .cellinput{width:100%;background:#0a1018;border:1px solid var(--accent);border-radius:4px;color:#fff;padding:3px 5px;font:inherit}
85
+ .sqltabs{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:6px}
86
+ .sqltab{padding:4px 10px;background:var(--bg-3);border:1px solid var(--border);border-radius:7px 7px 0 0;cursor:pointer;color:var(--muted);font-size:12px}
87
+ .sqltab.active{color:#cfe0ff;border-color:var(--accent);background:#16243d}
88
+ .sqltab .x{margin-left:7px}
89
+ .sqltab .x:hover{color:var(--red)}
90
+ .sqltab.add{font-weight:700;color:var(--accent)}
91
+ .flexcol{display:flex;flex-direction:column;gap:8px;min-height:0;flex:1}
92
+ a.link{color:#8fc6ff;cursor:pointer;text-decoration:underline}
93
+ `;
94
+ /**
95
+ * Browser-side application script. Intentionally written with plain string
96
+ * concatenation (no template literals) so it can live inside the outer
97
+ * template literal without escaping headaches.
98
+ */
99
+ const STUDIO_SCRIPT = `
100
+ "use strict";
101
+ var state={connections:[],activeConnectionId:"",connType:"",schemas:[],activeSchema:"",objects:[],selected:null,lastResult:null,apps:[],services:[],selectedApp:"",dataCtx:null,queries:[],sqlTabs:[],activeSqlTab:"",readOnly:!!window.SMDG_READONLY_DEFAULT};
102
+ function qs(id){return document.getElementById(id);}
103
+ function esc(v){return String(v==null?"":v).replace(/[&<>"']/g,function(s){return {"&":"&amp;","<":"&lt;",">":"&gt;","\\"":"&quot;","'":"&#39;"}[s];});}
104
+ function setStatus(text,kind){var el=qs("statusText");el.textContent=text;el.className=kind==="err"?"err":kind==="ok"?"ok":"";}
105
+ function log(text,kind){var box=qs("messages");var time=new Date().toLocaleTimeString();box.textContent="["+time+"] "+text+"\\n"+box.textContent;if(kind==="err")setStatus(text,"err");else if(kind==="ok")setStatus(text,"ok");}
106
+ function busy(on){qs("topSpinner").className=on?"spinner":"spinner hidden";}
107
+ function api(path,options){busy(true);return fetch(path,options).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(){busy(false);});}
108
+ function postJson(path,body){return api(path,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body||{})});}
109
+
110
+ /* ---- tabs ---- */
111
+ function showTab(name){var tabs=document.querySelectorAll(".tab");for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle("active",tabs[i].getAttribute("data-tab")===name);}var panels=document.querySelectorAll(".panel");for(var j=0;j<panels.length;j++){panels[j].classList.toggle("active",panels[j].id==="panel-"+name);}if(name==="files")loadQueries();if(name==="history")loadHistory();if(name==="import")loadTarget();}
112
+
113
+ /* ---- connections ---- */
114
+ function loadConnections(){return api("/api/connections").then(function(r){state.connections=r.connections||[];renderConnections();}).catch(function(e){log("Load connections failed: "+e.message,"err");});}
115
+ function renderConnections(){var q=(qs("connSearch").value||"").toLowerCase();var box=qs("connections");var rows=state.connections.filter(function(c){return (c.name+" "+c.type+" "+(c.host||"")).toLowerCase().indexOf(q)>=0;});if(!rows.length){box.innerHTML='<div class="empty">No connections. Use BTP Import.</div>';return;}box.innerHTML=rows.map(function(c){var sub=esc(c.type+" · "+(c.host||"")+(c.org?" · "+c.org:""));return '<div class="item '+(c.id===state.activeConnectionId?"active":"")+'" onclick="selectConnection(\\''+c.id+'\\')"><div class="title">'+esc(c.name)+'</div><div class="sub">'+sub+'</div></div>';}).join("");}
116
+ function activeConn(){return state.connections.filter(function(c){return c.id===state.activeConnectionId;})[0];}
117
+ function selectConnection(id){state.activeConnectionId=id;var c=activeConn();state.connType=c?c.type:"";qs("activeConnBadge").textContent=c?("Conn: "+c.name):"No connection";qs("activeConnBadge").className="badge active";if(c&&(/prod|production|prd|live/i.test((c.org||"")+" "+(c.app||"")+" "+(c.space||"")))){qs("prodBadge").classList.remove("hidden");}else{qs("prodBadge").classList.add("hidden");}renderConnections();state.schemas=[];state.objects=[];state.selected=null;renderTree();log("Selected connection: "+(c?c.name:id),"ok");loadSchemas();}
118
+ function testActiveConnection(){if(!state.activeConnectionId)return log("Select a connection first.","err");postJson("/api/connections/test",{connectionId:state.activeConnectionId}).then(function(r){if(r.success)log("Connection OK ("+(r.serverVersion||"")+") in "+r.durationMs+"ms","ok");else log("Connection failed: "+r.message,"err");}).catch(function(e){log(e.message,"err");});}
119
+ function renameActiveConnection(){var c=activeConn();if(!c)return log("Select a connection first.","err");var name=prompt("New connection name",c.name);if(!name)return;postJson("/api/connections/rename",{id:c.id,name:name}).then(function(){return loadConnections();}).then(function(){log("Renamed connection.","ok");}).catch(function(e){log(e.message,"err");});}
120
+ function duplicateActiveConnection(){var c=activeConn();if(!c)return log("Select a connection first.","err");postJson("/api/connections/duplicate",{id:c.id}).then(function(){return loadConnections();}).then(function(){log("Duplicated connection.","ok");}).catch(function(e){log(e.message,"err");});}
121
+ function removeActiveConnection(){var c=activeConn();if(!c)return log("Select a connection first.","err");if(!confirm("Remove connection '"+c.name+"'?"))return;postJson("/api/connections/remove",{id:c.id}).then(function(){state.activeConnectionId="";return loadConnections();}).then(function(){log("Removed connection.","ok");}).catch(function(e){log(e.message,"err");});}
122
+
123
+ /* ---- new direct connection ---- */
124
+ function openNewConnection(){qs("nc_msg").textContent="";qs("newConnModal").classList.remove("hidden");}
125
+ function closeNewConnection(){qs("newConnModal").classList.add("hidden");}
126
+ function onNewConnTypeChange(){var t=qs("nc_type").value;var p=qs("nc_port");if(!p.value||p.value==="5432"||p.value==="443"){p.value=t==="hana"?"443":"5432";}}
127
+ function newConnBody(){var type=qs("nc_type").value;return {name:qs("nc_name").value.trim(),type:type,host:qs("nc_host").value.trim(),port:parseInt(qs("nc_port").value,10)||(type==="hana"?443:5432),database:qs("nc_database").value.trim(),schema:qs("nc_schema").value.trim(),username:qs("nc_user").value.trim(),password:qs("nc_pass").value,ssl:qs("nc_ssl").checked,sslValidateCertificate:qs("nc_sslval").checked};}
128
+ function testNewConnection(){var b=newConnBody();if(!b.host||!b.username){qs("nc_msg").textContent="Host and username are required.";return;}qs("nc_msg").textContent="Testing...";postJson("/api/connections/test-draft",b).then(function(r){qs("nc_msg").textContent=r.success?("Connection OK "+(r.serverVersion||"")+" ("+r.durationMs+"ms)"):("Failed: "+r.message);}).catch(function(e){qs("nc_msg").textContent=e.message;});}
129
+ function saveNewConnection(){var b=newConnBody();if(!b.name||!b.host||!b.username){qs("nc_msg").textContent="Name, host and username are required.";return;}postJson("/api/connections/create",b).then(function(r){log("Created connection: "+r.connection.name,"ok");closeNewConnection();return loadConnections().then(function(){selectConnection(r.connection.id);});}).catch(function(e){qs("nc_msg").textContent=e.message;});}
130
+
131
+ /* ---- schemas + object tree ---- */
132
+ function loadSchemas(){if(!state.activeConnectionId)return;return api("/api/catalog/schemas?connectionId="+encodeURIComponent(state.activeConnectionId)).then(function(r){state.schemas=r.schemas||[];var sel=qs("schemaSelect");var c=activeConn();var preferred=c&&c.schema?c.schema:"";var ordered=state.schemas.slice().sort(function(a,b){return (a.isSystem?1:0)-(b.isSystem?1:0);});sel.innerHTML=ordered.map(function(s){return '<option value="'+esc(s.name)+'">'+esc(s.name)+(s.isSystem?" (system)":"")+'</option>';}).join("");state.activeSchema=preferred&&state.schemas.some(function(s){return s.name===preferred;})?preferred:(ordered[0]?ordered[0].name:"");sel.value=state.activeSchema;qs("schemaBadge").textContent="Schema: "+(state.activeSchema||"-");loadObjects();}).catch(function(e){log("Load schemas failed: "+e.message,"err");});}
133
+ function onSchemaChange(){state.activeSchema=qs("schemaSelect").value;qs("schemaBadge").textContent="Schema: "+(state.activeSchema||"-");loadObjects();}
134
+ function loadObjects(){if(!state.activeConnectionId||!state.activeSchema)return;var search=qs("objectSearch").value||"";return api("/api/catalog/objects?connectionId="+encodeURIComponent(state.activeConnectionId)+"&schema="+encodeURIComponent(state.activeSchema)+"&search="+encodeURIComponent(search)).then(function(r){state.objects=r.objects||[];renderTree();}).catch(function(e){log("Load objects failed: "+e.message,"err");});}
135
+ var TREE_GROUPS=[["table","Tables"],["view","Views"],["column-view","Column Views"],["procedure","Procedures"],["function","Functions"],["synonym","Synonyms"]];
136
+ function renderTree(){var box=qs("tree");if(!state.objects.length){box.innerHTML='<div class="empty">No objects. Select a connection and schema.</div>';return;}var html="";TREE_GROUPS.forEach(function(g){var kind=g[0];var items=state.objects.filter(function(o){return o.kind===kind;});if(!items.length)return;html+='<div class="tree-group"><div class="ghead" onclick="this.nextSibling.classList.toggle(\\'hidden\\')"><span>'+g[1]+'</span><span class="pill">'+items.length+'</span></div><div>';html+=items.map(function(o){var key=o.kind+"|"+o.schema+"|"+o.name;var act=state.selected&&(state.selected.kind+"|"+state.selected.schema+"|"+state.selected.name)===key;return '<div class="tree-item '+(act?"active":"")+'" title="'+esc(o.name)+'" ondblclick="openDataFor(\\''+esc(o.kind)+'\\',\\''+esc(o.name)+'\\')" onclick="selectObject(\\''+esc(o.kind)+'\\',\\''+esc(o.name)+'\\')">'+esc(o.name)+'</div>';}).join("");html+='</div></div>';});box.innerHTML=html;}
137
+ function selectObject(kind,name){state.selected={kind:kind,schema:state.activeSchema,name:name};renderTree();}
138
+ function quoted(schema,name){return '"'+schema+'"."'+name+'"';}
139
+ function requireSelected(){if(!state.selected){log("Select a table or view first.","err");return false;}return true;}
140
+ function copyTableName(){if(!requireSelected())return;var t=quoted(state.selected.schema,state.selected.name);navigator.clipboard.writeText(t).then(function(){log("Copied: "+t,"ok");});}
141
+ function genSelect(){if(!requireSelected())return;postJson("/api/table/sql",{connectionId:state.activeConnectionId,schema:state.selected.schema,table:state.selected.name,limit:getLimit()||100}).then(function(r){setEditor(r.select);showTab("sql");log("Generated SELECT.","ok");});}
142
+ function genCount(){if(!requireSelected())return;postJson("/api/table/sql",{connectionId:state.activeConnectionId,schema:state.selected.schema,table:state.selected.name}).then(function(r){setEditor(r.count);showTab("sql");log("Generated COUNT.","ok");});}
143
+ function genDdl(){if(!requireSelected())return;api("/api/catalog/ddl?connectionId="+encodeURIComponent(state.activeConnectionId)+"&schema="+encodeURIComponent(state.selected.schema)+"&table="+encodeURIComponent(state.selected.name)).then(function(r){setEditor(r.ddl);showTab("sql");log("Generated CREATE TABLE DDL.","ok");}).catch(function(e){log("DDL failed: "+e.message,"err");});}
144
+ function showColumns(){if(!requireSelected())return;loadMetadata(state.selected.schema,state.selected.name);}
145
+ function openData(){if(!requireSelected())return;openDataFor(state.selected.kind,state.selected.name);}
146
+
147
+ /* ---- SQL console ---- */
148
+ function getLimit(){var v=qs("limitSelect").value;return v==="0"?0:parseInt(v,10);}
149
+ function currentSql(){var ed=qs("editor");var s=ed.value;if(ed.selectionStart!=ed.selectionEnd){var sel=s.substring(ed.selectionStart,ed.selectionEnd).trim();if(sel)return sel;}return s.trim();}
150
+ function runSql(confirmDangerous){var sql=currentSql();if(!sql)return log("Editor is empty.","err");if(!state.activeConnectionId)return log("Select a connection first.","err");setStatus("Running...","");qs("runBtn").disabled=true;var body={connectionId:state.activeConnectionId,sql:sql,limit:getLimit(),readOnly:state.readOnly,confirmDangerous:!!confirmDangerous};postJson("/api/query/run",body).then(function(r){qs("runBtn").disabled=false;if(r.blocked){log("Blocked by read-only mode: "+(r.safety&&r.safety.matchedKeywords?r.safety.matchedKeywords.join(", "):""),"err");return;}if(r.needsConfirmation){var reason=(r.safety&&r.safety.reason)||"This statement may modify or drop data.";if(confirm("Dangerous SQL detected.\\n\\n"+reason+"\\n\\nRun anyway?")){runSql(true);}else{log("Cancelled dangerous statement.","err");}return;}if(!r.ok){log("Query error: "+r.error,"err");return;}state.lastResult=r.result;renderResult(r.result,"result");var info="Rows: "+r.result.rowCount+(r.result.affectedRows!=null?" · Affected: "+r.result.affectedRows:"")+" · "+r.result.durationMs+"ms"+(r.result.truncated?" · truncated":"");log(info,"ok");}).catch(function(e){qs("runBtn").disabled=false;log("Query failed: "+e.message,"err");});}
151
+ function explainSql(){if(state.connType!=="postgresql")return log("Explain currently supports PostgreSQL.","err");var ed=qs("editor");ed.value="EXPLAIN "+currentSql();runSql(false);}
152
+ function formatSql(){var ed=qs("editor");ed.value=ed.value.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)\\b/gi,function(m){return "\\n"+m.toUpperCase();}).trim();}
153
+ function clearSql(){qs("editor").value="";}
154
+ function renderResult(result,targetId){var box=qs(targetId);if(!result||!result.rows||!result.rows.length){box.innerHTML='<div class="empty">'+(result&&result.affectedRows!=null?("Affected rows: "+result.affectedRows):"No rows.")+'</div>';return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var html='<table class="grid"><thead><tr>'+fields.map(function(f){return '<th onclick="sortGrid(\\''+esc(f)+'\\')">'+esc(f)+'</th>';}).join("")+'</tr></thead><tbody>';html+=result.rows.map(function(row){return '<tr>'+fields.map(function(f){var v=row[f];var isNum=typeof v==="number";var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);return '<td class="'+(isNum?"num":"")+'" title="'+esc(disp)+'" ondblclick="copyCell(this)">'+esc(disp.length>400?disp.slice(0,400)+"…":disp)+'</td>';}).join("")+'</tr>';}).join("");html+="</tbody></table>";box.innerHTML=html;}
155
+ function copyCell(td){navigator.clipboard.writeText(td.getAttribute("title")||td.textContent).then(function(){setStatus("Cell copied","ok");});}
156
+ function sortGrid(field){if(!state.lastResult)return;var rows=state.lastResult.rows.slice();var dir=state.lastResult.__sortField===field&&state.lastResult.__sortDir==="asc"?"desc":"asc";rows.sort(function(a,b){var x=a[field],y=b[field];if(x==null)return 1;if(y==null)return -1;if(x<y)return dir==="asc"?-1:1;if(x>y)return dir==="asc"?1:-1;return 0;});state.lastResult.rows=rows;state.lastResult.__sortField=field;state.lastResult.__sortDir=dir;renderResult(state.lastResult,"result");}
157
+ function exportResult(format){if(!state.lastResult||!state.lastResult.rows.length)return log("No result to export.","err");var fields=state.lastResult.fields&&state.lastResult.fields.length?state.lastResult.fields:Object.keys(state.lastResult.rows[0]);var path=format==="csv"?"/api/export/csv":"/api/export/json";fetch(path,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({fields:fields,rows:state.lastResult.rows})}).then(function(r){return r.blob();}).then(function(b){var a=document.createElement("a");a.href=URL.createObjectURL(b);a.download=format==="csv"?"result.csv":"result.json";a.click();log("Exported "+format.toUpperCase(),"ok");});}
158
+ function copyResultJson(){if(!state.lastResult)return log("No result.","err");navigator.clipboard.writeText(JSON.stringify(state.lastResult.rows,null,2)).then(function(){log("Copied result JSON.","ok");});}
159
+ function toggleReadOnly(){state.readOnly=qs("readOnlyToggle").checked;qs("roBadge").className=state.readOnly?"badge warn":"badge";qs("roBadge").textContent=state.readOnly?"READ-ONLY":"Read/Write";log("Read-only mode: "+(state.readOnly?"ON":"OFF"),"ok");}
160
+
161
+ /* ---- SQL editor tabs ---- */
162
+ function activeTab(){return state.sqlTabs.filter(function(t){return t.id===state.activeSqlTab;})[0];}
163
+ function setEditor(sql){qs("editor").value=sql;var t=activeTab();if(t)t.sql=sql;}
164
+ function renderSqlTabs(){qs("sqlTabs").innerHTML=state.sqlTabs.map(function(t){return '<span class="sqltab '+(t.id===state.activeSqlTab?"active":"")+'" onclick="setActiveSqlTab(\\''+t.id+'\\')">'+esc(t.name)+' <span class="x" onclick="event.stopPropagation();closeSqlTab(\\''+t.id+'\\')">\\u00d7</span></span>';}).join("")+'<span class="sqltab add" title="New query tab" onclick="newSqlTab()">+</span>';}
165
+ function newSqlTab(name,sql){var id="t"+Date.now()+"_"+Math.floor(Math.random()*1000);state.sqlTabs.push({id:id,name:name||("Query "+(state.sqlTabs.length+1)),sql:sql||""});setActiveSqlTab(id);}
166
+ function setActiveSqlTab(id){var cur=activeTab();if(cur)cur.sql=qs("editor").value;state.activeSqlTab=id;var t=activeTab();if(t)qs("editor").value=t.sql;renderSqlTabs();}
167
+ function closeSqlTab(id){if(state.sqlTabs.length<=1)return;var idx=-1;for(var i=0;i<state.sqlTabs.length;i++){if(state.sqlTabs[i].id===id)idx=i;}if(idx<0)return;var wasActive=state.activeSqlTab===id;state.sqlTabs.splice(idx,1);if(wasActive){var next=state.sqlTabs[Math.max(0,idx-1)];state.activeSqlTab="";setActiveSqlTab(next.id);}else{renderSqlTabs();}}
168
+
169
+ /* ---- data grid ---- */
170
+ function openDataFor(kind,name){state.selected={kind:kind,schema:state.activeSchema,name:name};state.dataCtx={schema:state.activeSchema,table:name,offset:0,orderBy:"",orderDirection:"asc",columns:[],pk:[],selectedRow:null};showTab("data");qs("dataTitle").textContent=quoted(state.activeSchema,name);api("/api/catalog/columns?connectionId="+encodeURIComponent(state.activeConnectionId)+"&schema="+encodeURIComponent(state.activeSchema)+"&table="+encodeURIComponent(name)).then(function(r){if(!state.dataCtx)return;state.dataCtx.columns=r.columns||[];state.dataCtx.pk=(r.columns||[]).filter(function(c){return c.isPrimaryKey;}).map(function(c){return c.name;});updateDataEditHint();renderDataGrid(state.lastResult);}).catch(function(){});loadTableCount();loadTableData();}
171
+ function updateDataEditHint(){var ctx=state.dataCtx||{};var editable=ctx.pk&&ctx.pk.length>0;qs("dataEditHint").textContent=editable?("Editable · double-click a cell · key: "+ctx.pk.join(", ")):"Read-only (no primary key detected)";}
172
+ function pageSize(){return parseInt(qs("pageSize").value,10);}
173
+ function loadTableData(){if(!state.dataCtx)return;var body={connectionId:state.activeConnectionId,schema:state.dataCtx.schema,table:state.dataCtx.table,limit:pageSize(),offset:state.dataCtx.offset,where:qs("dataWhere").value||"",orderBy:state.dataCtx.orderBy||"",orderDirection:state.dataCtx.orderDirection||"asc"};postJson("/api/table/data",body).then(function(r){state.lastResult=r.result;state.dataCtx.selectedRow=null;renderDataGrid(r.result);qs("pageInfo").textContent="Offset "+state.dataCtx.offset+" · "+r.result.rowCount+" rows · "+r.result.durationMs+"ms";}).catch(function(e){log("Load data failed: "+e.message,"err");});}
174
+ function renderDataGrid(result){var box=qs("dataGrid");if(!result||!result.rows||!result.rows.length){box.innerHTML='<div class="empty">No rows.</div>';return;}var fields=result.fields&&result.fields.length?result.fields:Object.keys(result.rows[0]);var ctx=state.dataCtx||{};var editable=ctx.pk&&ctx.pk.length>0;var html='<table class="grid"><thead><tr><th class="rowhdr">#</th>'+fields.map(function(f){var arrow=ctx.orderBy===f?'<span class="sort">'+(ctx.orderDirection==="desc"?"\\u25BC":"\\u25B2")+'</span>':"";return '<th title="Click to sort" onclick="dataSort(\\''+esc(f)+'\\')">'+esc(f)+arrow+'</th>';}).join("")+'</tr></thead><tbody>';html+=result.rows.map(function(row,ri){var sel=ctx.selectedRow===ri?' class="selrow"':"";return '<tr'+sel+'><td class="rowhdr" onclick="selectDataRow('+ri+')">'+(ctx.offset+ri+1)+'</td>'+fields.map(function(f){var v=row[f];var isNum=typeof v==="number";var disp=v==null?"":typeof v==="object"?JSON.stringify(v):String(v);var handler=editable?' ondblclick="startCellEdit(this,'+ri+',\\''+esc(f)+'\\')"':' ondblclick="copyCell(this)"';return '<td class="'+(isNum?"num":"")+'" data-f="'+esc(f)+'" title="'+esc(disp)+'"'+handler+'>'+esc(disp.length>400?disp.slice(0,400)+"…":disp)+'</td>';}).join("")+'</tr>';}).join("");html+="</tbody></table>";box.innerHTML=html;}
175
+ function dataSort(field){if(!state.dataCtx)return;if(state.dataCtx.orderBy===field){state.dataCtx.orderDirection=state.dataCtx.orderDirection==="asc"?"desc":"asc";}else{state.dataCtx.orderBy=field;state.dataCtx.orderDirection="asc";}state.dataCtx.offset=0;loadTableData();}
176
+ function selectDataRow(ri){if(!state.dataCtx)return;state.dataCtx.selectedRow=state.dataCtx.selectedRow===ri?null:ri;renderDataGrid(state.lastResult);}
177
+ function startCellEdit(td,ri,field){if(!state.lastResult||td.querySelector("input"))return;var orig=state.lastResult.rows[ri][field];var cur=orig==null?"":typeof orig==="object"?JSON.stringify(orig):String(orig);var input=document.createElement("input");input.className="cellinput";input.value=cur;td.innerHTML="";td.appendChild(input);input.focus();input.select();var done=false;function commit(){if(done)return;done=true;commitCellEdit(ri,field,input.value,orig);}function cancel(){if(done)return;done=true;renderDataGrid(state.lastResult);}input.addEventListener("keydown",function(e){if(e.key==="Enter"){e.preventDefault();commit();}else if(e.key==="Escape"){e.preventDefault();cancel();}});input.addEventListener("blur",commit);}
178
+ function commitCellEdit(ri,field,newVal,orig){var origStr=orig==null?"":typeof orig==="object"?JSON.stringify(orig):String(orig);if(newVal===origStr){renderDataGrid(state.lastResult);return;}var keys={};state.dataCtx.pk.forEach(function(k){keys[k]=state.lastResult.rows[ri][k];});var changes={};changes[field]=newVal;postJson("/api/table/row/update",{connectionId:state.activeConnectionId,schema:state.dataCtx.schema,table:state.dataCtx.table,changes:changes,keys:keys,readOnly:state.readOnly}).then(function(r){if(r.ok){state.lastResult.rows[ri][field]=newVal;log("Row updated.","ok");}else{log("Update failed: "+(r.error||"blocked"),"err");}renderDataGrid(state.lastResult);}).catch(function(e){log("Update failed: "+e.message,"err");renderDataGrid(state.lastResult);});}
179
+ function deleteSelectedRow(){var ctx=state.dataCtx;if(!ctx)return;if(ctx.selectedRow==null)return log("Click a row number to select it first.","err");if(!ctx.pk||!ctx.pk.length)return log("Cannot delete: table has no primary key.","err");if(!confirm("Delete the selected row?"))return;var keys={};ctx.pk.forEach(function(k){keys[k]=state.lastResult.rows[ctx.selectedRow][k];});postJson("/api/table/row/delete",{connectionId:state.activeConnectionId,schema:ctx.schema,table:ctx.table,keys:keys,readOnly:state.readOnly}).then(function(r){if(r.ok){log("Row deleted.","ok");ctx.selectedRow=null;loadTableCount();loadTableData();}else log("Delete failed: "+(r.error||"blocked"),"err");}).catch(function(e){log("Delete failed: "+e.message,"err");});}
180
+ function openInsertRow(){var ctx=state.dataCtx;if(!ctx||!ctx.columns||!ctx.columns.length)return log("Open a table in the Data Grid first.","err");qs("ir_title").textContent="Insert row into "+quoted(ctx.schema,ctx.table);qs("ir_fields").innerHTML=ctx.columns.map(function(c){var meta=c.dataType+(c.isPrimaryKey?" · PK":"")+(c.nullable?"":" · NOT NULL");return '<div class="field"><label>'+esc(c.name)+' <span class="note">'+esc(meta)+'</span></label><input class="input ir-in" data-col="'+esc(c.name)+'"></div>';}).join("");qs("ir_msg").textContent="";qs("insertRowModal").classList.remove("hidden");}
181
+ function closeInsertRow(){qs("insertRowModal").classList.add("hidden");}
182
+ function saveInsertRow(){var inputs=document.querySelectorAll("#ir_fields .ir-in");var values={};inputs.forEach(function(inp){if(inp.value!=="")values[inp.getAttribute("data-col")]=inp.value;});if(!Object.keys(values).length){qs("ir_msg").textContent="Enter at least one column value.";return;}postJson("/api/table/row/insert",{connectionId:state.activeConnectionId,schema:state.dataCtx.schema,table:state.dataCtx.table,values:values,readOnly:state.readOnly}).then(function(r){if(r.ok){log("Row inserted.","ok");closeInsertRow();loadTableCount();loadTableData();}else qs("ir_msg").textContent=r.error||"Insert blocked (read-only mode).";}).catch(function(e){qs("ir_msg").textContent=e.message;});}
183
+ function loadTableCount(){if(!state.dataCtx)return;postJson("/api/table/count",{connectionId:state.activeConnectionId,schema:state.dataCtx.schema,table:state.dataCtx.table}).then(function(r){qs("rowCount").textContent="Total rows: "+r.count;}).catch(function(){qs("rowCount").textContent="";});}
184
+ function dataNext(){if(!state.dataCtx)return;state.dataCtx.offset+=pageSize();loadTableData();}
185
+ function dataPrev(){if(!state.dataCtx)return;state.dataCtx.offset=Math.max(0,state.dataCtx.offset-pageSize());loadTableData();}
186
+ function dataExport(format){exportResult(format);}
187
+
188
+ /* ---- metadata ---- */
189
+ function loadMetadata(schema,table){showTab("meta");qs("metaTitle").textContent=quoted(schema,table);api("/api/catalog/columns?connectionId="+encodeURIComponent(state.activeConnectionId)+"&schema="+encodeURIComponent(schema)+"&table="+encodeURIComponent(table)).then(function(r){var cols=r.columns||[];var idx=r.indexes||[];var html='<table class="grid"><thead><tr><th>Column</th><th>Type</th><th>Length</th><th>Scale</th><th>Nullable</th><th>Default</th><th>Key</th><th>Comment</th></tr></thead><tbody>';html+=cols.map(function(c){return "<tr><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>'+esc(c.defaultValue==null?"":c.defaultValue)+'</td><td>'+(c.isPrimaryKey?'<span class="pill pk">PK</span>':"")+'</td><td>'+esc(c.comment==null?"":c.comment)+'</td></tr>';}).join("");html+="</tbody></table>";var idxHtml="";if(idx.length){idxHtml='<h3 style="margin-top:12px">Indexes</h3><table class="grid"><thead><tr><th>Index</th><th>Columns</th><th>Unique</th><th>Primary</th></tr></thead><tbody>'+idx.map(function(i){return "<tr><td>"+esc(i.name)+"</td><td>"+esc((i.columns||[]).join(", "))+"</td><td>"+(i.isUnique?"YES":"NO")+"</td><td>"+(i.isPrimaryKey?"YES":"NO")+"</td></tr>";}).join("")+"</tbody></table>";}qs("metaBody").innerHTML=html+idxHtml;log("Loaded metadata for "+table,"ok");}).catch(function(e){log("Metadata failed: "+e.message,"err");});}
190
+
191
+ /* ---- BTP import ---- */
192
+ function loadTarget(){return api("/api/btp/current-target").then(function(r){var t=r.target||{};qs("btpTarget").innerHTML='<div class="kvs"><div class="k">Logged in</div><div>'+(r.loggedIn?'<span class="pill">yes</span>':'<span class="pill">no</span>')+'</div><div class="k">Region</div><div>'+esc(t.region||"-")+'</div><div class="k">Org</div><div>'+esc(t.org||"-")+'</div><div class="k">Space</div><div>'+esc(t.space||"-")+'</div><div class="k">User</div><div>'+esc(t.user||"-")+'</div></div>'+(r.productionWarning?'<div class="badge warn" style="margin-top:8px">Production-like target</div>':"")+(r.loggedIn?"":'<div class="note" style="margin-top:8px">'+esc(r.message||"Run: smdg cf login")+'</div>');}).catch(function(e){qs("btpTarget").innerHTML='<div class="note">'+esc(e.message)+'</div>';});}
193
+ function loadApps(refresh){qs("apps").innerHTML='<div class="note">Loading apps...</div>';api("/api/btp/apps"+(refresh?"?refresh=true":"")).then(function(r){if(!r.loggedIn){qs("apps").innerHTML='<div class="note">'+esc(r.message||"Not logged in. Run: smdg cf login")+'</div>';return;}state.apps=r.apps||[];renderApps();}).catch(function(e){qs("apps").innerHTML='<div class="note">'+esc(e.message)+'</div>';});}
194
+ function renderApps(){var q=(qs("appSearch").value||"").toLowerCase();var rows=state.apps.filter(function(a){return JSON.stringify(a).toLowerCase().indexOf(q)>=0;});if(!rows.length){qs("apps").innerHTML='<div class="note">No apps.</div>';return;}qs("apps").innerHTML=rows.map(function(a){return '<div class="item '+(a.name===state.selectedApp?"active":"")+'" onclick="selectApp(\\''+esc(a.name)+'\\')"><div class="title">'+esc(a.name)+'</div><div class="sub">'+esc((a.requestedState||"")+(a.routes?" · "+a.routes:""))+'</div></div>';}).join("");}
195
+ function selectApp(name){state.selectedApp=name;renderApps();readAppEnv();}
196
+ function readAppEnv(){if(!state.selectedApp)return log("Select an app first.","err");qs("services").innerHTML='<div class="note">Reading cf env '+esc(state.selectedApp)+'...</div>';postJson("/api/btp/env",{app:state.selectedApp}).then(function(r){state.services=r.services||[];renderServices();log("Detected "+state.services.length+" database service(s) in "+state.selectedApp,"ok");}).catch(function(e){state.services=[];qs("services").innerHTML='<div class="note">'+esc(e.message)+'</div>';log("Read env failed: "+e.message,"err");});}
197
+ function renderServices(){if(!state.services.length){qs("services").innerHTML='<div class="note">No database service detected.</div>';return;}qs("services").innerHTML='<table class="grid"><thead><tr><th>Service</th><th>Type</th><th>Host</th><th>Schema/DB</th><th>Plan</th><th></th></tr></thead><tbody>'+state.services.map(function(s,i){return "<tr><td>"+esc(s.serviceName)+"</td><td>"+esc(s.type)+"</td><td>"+esc(s.host)+"</td><td>"+esc(s.schema||s.database||"")+"</td><td>"+esc(s.servicePlan||"")+'</td><td><button class="btn sm" onclick="importService('+i+',false)">Save</button> <button class="btn sm sec" onclick="importService('+i+',true)">Save+Use</button></td></tr>';}).join("")+"</tbody></table>";}
198
+ function importService(index,activate){var svc=state.services[index];if(!svc)return;postJson("/api/connections/import-from-app",{app:state.selectedApp,serviceName:svc.serviceName,type:svc.type}).then(function(r){log("Imported connection: "+r.connection.name,"ok");return loadConnections().then(function(){if(activate){selectConnection(r.connection.id);testActiveConnection();}});}).catch(function(e){log("Import failed: "+e.message,"err");});}
199
+
200
+ /* ---- query files ---- */
201
+ function loadQueries(){return api("/api/queries").then(function(r){state.queries=r.queries||[];renderQueries();}).catch(function(e){log(e.message,"err");});}
202
+ function renderQueries(){var q=(qs("querySearch").value||"").toLowerCase();var rows=(state.queries||[]).filter(function(x){return (x.name+" "+(x.tags||[]).join(" ")).toLowerCase().indexOf(q)>=0;});if(!rows.length){qs("queries").innerHTML='<div class="empty">No saved queries.</div>';return;}qs("queries").innerHTML=rows.map(function(x){return '<div class="item"><div class="title">'+esc(x.name)+'</div><div class="sub">'+esc((x.connectionType||"")+" · "+new Date(x.updatedAt).toLocaleString())+'</div><div class="row" style="margin-top:6px"><button class="btn sm" onclick="loadQueryIntoEditor(\\''+x.id+'\\')">Load</button> <button class="btn sm sec" onclick="runSavedQuery(\\''+x.id+'\\')">Run</button> <button class="btn sm ghost" onclick="renameQuery(\\''+x.id+'\\')">Rename</button> <button class="btn sm ghost" onclick="deleteQuery(\\''+x.id+'\\')">Delete</button></div></div>';}).join("");}
203
+ function findQuery(id){return (state.queries||[]).filter(function(x){return x.id===id;})[0];}
204
+ function loadQueryIntoEditor(id){var q=findQuery(id);if(!q)return;newSqlTab(q.name,q.sql);showTab("sql");log("Loaded query: "+q.name,"ok");}
205
+ function runSavedQuery(id){loadQueryIntoEditor(id);runSql(false);}
206
+ function saveCurrentQuery(){var sql=qs("editor").value.trim();if(!sql)return log("Editor is empty.","err");var name=prompt("Query name","Query "+new Date().toLocaleString());if(!name)return;postJson("/api/queries",{name:name,sql:sql,connectionId:state.activeConnectionId,connectionType:state.connType}).then(function(){return loadQueries();}).then(function(){log("Saved query.","ok");}).catch(function(e){log(e.message,"err");});}
207
+ function renameQuery(id){var q=findQuery(id);if(!q)return;var name=prompt("New name",q.name);if(!name)return;api("/api/queries/"+encodeURIComponent(id),{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({name:name})}).then(function(){return loadQueries();}).then(function(){log("Renamed query.","ok");}).catch(function(e){log(e.message,"err");});}
208
+ function deleteQuery(id){if(!confirm("Delete this saved query?"))return;api("/api/queries/"+encodeURIComponent(id),{method:"DELETE"}).then(function(){return loadQueries();}).then(function(){log("Deleted query.","ok");}).catch(function(e){log(e.message,"err");});}
209
+
210
+ /* ---- history ---- */
211
+ function loadHistory(){return api("/api/history").then(function(r){var items=r.history||[];if(!items.length){qs("historyList").innerHTML='<div class="empty">No history.</div>';return;}qs("historyList").innerHTML=items.map(function(h){return '<div class="item" onclick="qs(\\'editor\\').value='+JSON.stringify(h.sql).replace(/"/g,"&quot;")+';showTab(\\'sql\\');"><div class="sub">'+esc(new Date(h.timestamp).toLocaleString()+" · "+(h.connectionName||"")+" · "+(h.success?"ok":"fail")+(h.rowCount!=null?" · "+h.rowCount+" rows":"")+" · "+h.durationMs+"ms")+'</div><div style="font-family:Consolas,monospace;white-space:pre-wrap">'+esc(h.sql.length>300?h.sql.slice(0,300)+"…":h.sql)+'</div>'+(h.error?'<div class="sub" style="color:#fca5a5">'+esc(h.error)+'</div>':"")+'</div>';}).join("");}).catch(function(e){log(e.message,"err");});}
212
+
213
+ /* ---- sidebar resizer ---- */
214
+ function initResizer(){var resizer=qs("resizer");var sidebar=qs("sidebar");var dragging=false;resizer.addEventListener("mousedown",function(){dragging=true;document.body.style.userSelect="none";});window.addEventListener("mousemove",function(e){if(!dragging)return;var w=Math.min(560,Math.max(200,e.clientX));sidebar.style.width=w+"px";});window.addEventListener("mouseup",function(){dragging=false;document.body.style.userSelect="";});}
215
+
216
+ /* ---- init ---- */
217
+ window.addEventListener("load",function(){
218
+ initResizer();
219
+ qs("readOnlyToggle").checked=state.readOnly;toggleReadOnly();
220
+ qs("editor").addEventListener("keydown",function(e){if((e.ctrlKey||e.metaKey)&&e.key==="Enter"){e.preventDefault();runSql(false);}});
221
+ qs("editor").addEventListener("input",function(){var t=activeTab();if(t)t.sql=qs("editor").value;});
222
+ newSqlTab("Query 1",qs("editor").value);
223
+ loadConnections();
224
+ loadTarget();
225
+ log("SimpleMDG CF DB Studio ready.","ok");
226
+ });
227
+ `;
228
+ function renderStudioHtml(options) {
229
+ return `<!doctype html>
230
+ <html lang="en">
231
+ <head>
232
+ <meta charset="utf-8" />
233
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
234
+ <title>SimpleMDG CF DB Studio</title>
235
+ <style>${STUDIO_STYLES}</style>
236
+ </head>
237
+ <body>
238
+ <div class="app">
239
+ <div class="topbar">
240
+ <span class="brand">SimpleMDG · CF DB Studio</span>
241
+ <span id="activeConnBadge" class="badge">No connection</span>
242
+ <span id="schemaBadge" class="badge">Schema: -</span>
243
+ <span id="roBadge" class="badge">Read/Write</span>
244
+ <span id="prodBadge" class="badge warn hidden">Production-like</span>
245
+ <span class="spacer"></span>
246
+ <label class="note"><input type="checkbox" id="readOnlyToggle" onchange="toggleReadOnly()" /> Read-only</label>
247
+ <span id="topSpinner" class="spinner hidden"></span>
248
+ </div>
249
+ <div class="body">
250
+ <aside class="sidebar" id="sidebar">
251
+ <div class="section">
252
+ <h3>Connections</h3>
253
+ <div class="row"><input id="connSearch" class="search" placeholder="Search connections..." oninput="renderConnections()" /></div>
254
+ <div class="row" style="margin-top:6px">
255
+ <button class="btn sm" onclick="openNewConnection()">+ New</button>
256
+ <button class="btn sm sec" onclick="loadConnections()">Refresh</button>
257
+ <button class="btn sm sec" onclick="testActiveConnection()">Test</button>
258
+ <button class="btn sm ghost" onclick="renameActiveConnection()">Rename</button>
259
+ <button class="btn sm ghost" onclick="duplicateActiveConnection()">Dup</button>
260
+ <button class="btn sm ghost" onclick="removeActiveConnection()">Remove</button>
261
+ </div>
262
+ <div id="connections" class="list" style="max-height:170px;margin-top:6px"></div>
263
+ </div>
264
+ <div class="section">
265
+ <h3>Schema</h3>
266
+ <select id="schemaSelect" class="select" onchange="onSchemaChange()"></select>
267
+ </div>
268
+ <div class="section flexcol">
269
+ <h3>Object Explorer</h3>
270
+ <input id="objectSearch" class="search" placeholder="Search tables/views..." oninput="loadObjects()" />
271
+ <div class="row">
272
+ <button class="btn sm" onclick="openData()">Open data</button>
273
+ <button class="btn sm sec" onclick="showColumns()">Columns</button>
274
+ <button class="btn sm ghost" onclick="copyTableName()">Copy name</button>
275
+ </div>
276
+ <div class="row">
277
+ <button class="btn sm ghost" onclick="genSelect()">SELECT</button>
278
+ <button class="btn sm ghost" onclick="genCount()">COUNT</button>
279
+ <button class="btn sm ghost" onclick="genDdl()">DDL</button>
280
+ </div>
281
+ <div id="tree" class="list"></div>
282
+ </div>
283
+ </aside>
284
+ <div class="resizer" id="resizer"></div>
285
+ <main class="main">
286
+ <div class="tabs">
287
+ <div class="tab active" data-tab="sql" onclick="showTab('sql')">SQL Console</div>
288
+ <div class="tab" data-tab="data" onclick="showTab('data')">Data Grid</div>
289
+ <div class="tab" data-tab="meta" onclick="showTab('meta')">Metadata</div>
290
+ <div class="tab" data-tab="import" onclick="showTab('import')">BTP Import</div>
291
+ <div class="tab" data-tab="files" onclick="showTab('files')">Query Files</div>
292
+ <div class="tab" data-tab="history" onclick="showTab('history')">History</div>
293
+ <div class="tab" data-tab="logs" onclick="showTab('logs')">Messages</div>
294
+ </div>
295
+ <div class="panels">
296
+ <section id="panel-sql" class="panel active">
297
+ <div id="sqlTabs" class="sqltabs"></div>
298
+ <div class="toolbar">
299
+ <button id="runBtn" class="btn" onclick="runSql(false)">Run (Ctrl+Enter)</button>
300
+ <button class="btn sec" onclick="explainSql()">Explain</button>
301
+ <button class="btn ghost" onclick="formatSql()">Format</button>
302
+ <button class="btn ghost" onclick="clearSql()">Clear</button>
303
+ <span class="note">Limit</span>
304
+ <select id="limitSelect" class="select" style="width:auto">
305
+ <option value="100">100</option><option value="500">500</option>
306
+ <option value="1000">1000</option><option value="5000">5000</option>
307
+ <option value="0">No limit</option>
308
+ </select>
309
+ <button class="btn ghost" onclick="saveCurrentQuery()">Save</button>
310
+ <button class="btn ghost" onclick="exportResult('csv')">CSV</button>
311
+ <button class="btn ghost" onclick="exportResult('json')">JSON</button>
312
+ <button class="btn ghost" onclick="copyResultJson()">Copy JSON</button>
313
+ </div>
314
+ <textarea id="editor" class="editor" spellcheck="false">select * from DUMMY</textarea>
315
+ <h3 class="note" style="margin:10px 0 4px">Result</h3>
316
+ <div id="result" class="gridwrap"></div>
317
+ </section>
318
+ <section id="panel-data" class="panel">
319
+ <div class="toolbar">
320
+ <strong id="dataTitle">-</strong>
321
+ <span id="rowCount" class="note"></span>
322
+ </div>
323
+ <div class="toolbar">
324
+ <input id="dataWhere" class="input" style="flex:1" placeholder="WHERE clause (optional), e.g. status = 'A'" />
325
+ <button class="btn" onclick="loadTableData()">Apply</button>
326
+ <span class="note">Page</span>
327
+ <select id="pageSize" class="select" style="width:auto"><option>100</option><option>500</option><option>1000</option></select>
328
+ <button class="btn sec" onclick="dataPrev()">Prev</button>
329
+ <button class="btn sec" onclick="dataNext()">Next</button>
330
+ <span id="pageInfo" class="note"></span>
331
+ <button class="btn ghost" onclick="dataExport('csv')">CSV</button>
332
+ <button class="btn ghost" onclick="dataExport('json')">JSON</button>
333
+ </div>
334
+ <div class="toolbar">
335
+ <button class="btn sec" onclick="openInsertRow()">+ Insert row</button>
336
+ <button class="btn ghost" onclick="deleteSelectedRow()">Delete selected row</button>
337
+ <span id="dataEditHint" class="note"></span>
338
+ </div>
339
+ <div id="dataGrid" class="gridwrap"></div>
340
+ </section>
341
+ <section id="panel-meta" class="panel">
342
+ <div class="toolbar"><strong id="metaTitle">-</strong></div>
343
+ <div id="metaBody" class="flexcol"><div class="empty">Select a table and click Columns.</div></div>
344
+ </section>
345
+ <section id="panel-import" class="panel">
346
+ <div class="flexcol">
347
+ <h3 class="note">Current CF target</h3>
348
+ <div id="btpTarget" class="msg"></div>
349
+ <div class="toolbar">
350
+ <button class="btn" onclick="loadApps(false)">Load CF apps</button>
351
+ <button class="btn sec" onclick="loadApps(true)">Refresh apps</button>
352
+ <button class="btn ghost" onclick="loadTarget()">Refresh target</button>
353
+ <span class="note">Switch org/space with: smdg cf org</span>
354
+ </div>
355
+ <input id="appSearch" class="search" placeholder="Search apps..." oninput="renderApps()" />
356
+ <div id="apps" class="list" style="max-height:170px"></div>
357
+ <h3 class="note">Detected database services</h3>
358
+ <div id="services"><div class="note">Select an app to read its env.</div></div>
359
+ </div>
360
+ </section>
361
+ <section id="panel-files" class="panel">
362
+ <div class="toolbar">
363
+ <input id="querySearch" class="search" style="flex:1" placeholder="Search saved queries..." oninput="renderQueries()" />
364
+ <button class="btn" onclick="saveCurrentQuery()">Save current</button>
365
+ <button class="btn sec" onclick="loadQueries()">Refresh</button>
366
+ </div>
367
+ <div id="queries" class="list"></div>
368
+ </section>
369
+ <section id="panel-history" class="panel">
370
+ <div class="toolbar"><button class="btn sec" onclick="loadHistory()">Refresh</button></div>
371
+ <div id="historyList" class="list"></div>
372
+ </section>
373
+ <section id="panel-logs" class="panel">
374
+ <h3 class="note">Messages / Logs</h3>
375
+ <div id="messages" class="msg" style="flex:1">Ready.</div>
376
+ </section>
377
+ </div>
378
+ <div class="statusbar">
379
+ <span>Status: <span id="statusText" class="ok">Ready</span></span>
380
+ <span class="spacer" style="flex:1"></span>
381
+ <span class="note">Local only · 127.0.0.1</span>
382
+ </div>
383
+ </main>
384
+ </div>
385
+ <div id="newConnModal" class="modal hidden">
386
+ <div class="dialog">
387
+ <h3>New direct connection</h3>
388
+ <div class="field"><label>Name</label><input id="nc_name" class="input" placeholder="My database" /></div>
389
+ <div class="field"><label>Type</label>
390
+ <select id="nc_type" class="select" onchange="onNewConnTypeChange()">
391
+ <option value="postgresql">PostgreSQL</option>
392
+ <option value="hana">SAP HANA</option>
393
+ </select>
394
+ </div>
395
+ <div class="row">
396
+ <div class="field grow"><label>Host</label><input id="nc_host" class="input" placeholder="host.example.com" /></div>
397
+ <div class="field" style="width:120px"><label>Port</label><input id="nc_port" class="input" value="5432" /></div>
398
+ </div>
399
+ <div class="row">
400
+ <div class="field grow"><label>Database</label><input id="nc_database" class="input" /></div>
401
+ <div class="field grow"><label>Schema</label><input id="nc_schema" class="input" placeholder="public" /></div>
402
+ </div>
403
+ <div class="row">
404
+ <div class="field grow"><label>Username</label><input id="nc_user" class="input" /></div>
405
+ <div class="field grow"><label>Password</label><input id="nc_pass" class="input" type="password" /></div>
406
+ </div>
407
+ <div class="row" style="gap:18px;margin:4px 0 8px">
408
+ <label class="note"><input type="checkbox" id="nc_ssl" checked /> Use SSL</label>
409
+ <label class="note"><input type="checkbox" id="nc_sslval" /> Validate certificate</label>
410
+ </div>
411
+ <div class="row" style="justify-content:flex-end">
412
+ <button class="btn ghost" onclick="closeNewConnection()">Cancel</button>
413
+ <button class="btn sec" onclick="testNewConnection()">Test</button>
414
+ <button class="btn" onclick="saveNewConnection()">Save &amp; use</button>
415
+ </div>
416
+ <div id="nc_msg" class="note" style="margin-top:8px"></div>
417
+ </div>
418
+ </div>
419
+ <div id="insertRowModal" class="modal hidden">
420
+ <div class="dialog">
421
+ <h3 id="ir_title">Insert row</h3>
422
+ <div id="ir_fields"></div>
423
+ <div class="note">Leave a field empty to use its column default / NULL.</div>
424
+ <div class="row" style="justify-content:flex-end;margin-top:10px">
425
+ <button class="btn ghost" onclick="closeInsertRow()">Cancel</button>
426
+ <button class="btn" onclick="saveInsertRow()">Insert</button>
427
+ </div>
428
+ <div id="ir_msg" class="note" style="margin-top:8px"></div>
429
+ </div>
430
+ </div>
431
+ </div>
432
+ <script>window.SMDG_READONLY_DEFAULT=${options.readOnlyDefault ? "true" : "false"};</script>
433
+ <script>${STUDIO_SCRIPT}</script>
434
+ </body>
435
+ </html>`;
436
+ }
437
+ //# sourceMappingURL=db-studio-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-studio-html.js","sourceRoot":"","sources":["../../../src/core/db/db-studio-html.ts"],"names":[],"mappings":";;AAsOA,4CAgNC;AAlbD,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgIrB,CAAC;AAEF,SAAgB,gBAAgB,CAAC,OAA2B;IAC1D,OAAO;;;;;;SAMA,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAqMiB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;UACvE,aAAa;;QAEf,CAAC;AACT,CAAC"}