sap-adt-mcp 0.8.45 → 0.8.47

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/panel.js +396 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sap-adt-mcp",
3
- "version": "0.8.45",
3
+ "version": "0.8.47",
4
4
  "mcpName": "io.github.yzonur/sap-adt-mcp",
5
5
  "description": "MCP server giving Claude live access to SAP systems via ADT (ABAP Development Tools) REST. Read source, search, run syntax checks, and edit ABAP objects from any MCP-compatible client.",
6
6
  "type": "module",
package/src/panel.js CHANGED
@@ -33,6 +33,181 @@ const PANEL_TOOLS = [
33
33
  { name: "adt_get_dump", label: "Dump Detayı", cat: "Runtime" },
34
34
  ];
35
35
 
36
+ // --- Friendly form layer (panel-only) ---------------------------------------
37
+ // The MCP inputSchemas are written for an agent: terse keys, ADT type codes,
38
+ // regex. The panel re-presents them for a human. None of this touches the
39
+ // agent-facing schemas — it's an overlay shipped in /meta and consumed by the
40
+ // page. Per field: { label, help, advanced (collapse), default, placeholder,
41
+ // options:[{value,label}] (renders a dropdown) }. Object-type dropdowns use the
42
+ // tools' own friendly aliases (normalizeType accepts them), so they stay correct.
43
+ const OBJECT_TYPE_OPTIONS = [
44
+ { value: "", label: "(seçiniz)" },
45
+ { value: "class", label: "Sınıf (CLAS)" },
46
+ { value: "program", label: "Program (PROG)" },
47
+ { value: "interface", label: "Arayüz (INTF)" },
48
+ { value: "function", label: "Fonksiyon modülü (FUGR/FF)" },
49
+ { value: "functiongroup", label: "Fonksiyon grubu (FUGR)" },
50
+ { value: "include", label: "Include (INCL)" },
51
+ { value: "table", label: "Tablo (TABL)" },
52
+ { value: "structure", label: "Yapı / Structure (TABL)" },
53
+ { value: "dataelement", label: "Veri elemanı (DTEL)" },
54
+ { value: "domain", label: "Domain (DOMA)" },
55
+ { value: "cds", label: "CDS View (DDLS)" },
56
+ ];
57
+
58
+ // objectType filter on quick-search wants the ADT node-type code, not an alias.
59
+ const SEARCH_TYPE_OPTIONS = [
60
+ { value: "", label: "Tümü" },
61
+ { value: "CLAS/OC", label: "Sınıf" },
62
+ { value: "PROG/P", label: "Program" },
63
+ { value: "INTF/OI", label: "Arayüz" },
64
+ { value: "FUGR/F", label: "Fonksiyon grubu" },
65
+ { value: "TABL/DT", label: "Tablo" },
66
+ { value: "DTEL/DE", label: "Veri elemanı" },
67
+ { value: "DOMA/DD", label: "Domain" },
68
+ { value: "DDLS/DF", label: "CDS View" },
69
+ ];
70
+
71
+ const CLASS_INCLUDE_OPTIONS = [
72
+ { value: "", label: "main (varsayılan)" },
73
+ { value: "definitions", label: "definitions" },
74
+ { value: "implementations", label: "implementations" },
75
+ { value: "macros", label: "macros" },
76
+ { value: "testclasses", label: "testclasses" },
77
+ ];
78
+
79
+ // intro: a friendly, non-technical one-liner per tool (replaces the agent-facing
80
+ // description on the card). fields: per-field overlay.
81
+ const TOOL_HINTS = {
82
+ adt_list_systems: { intro: "Tanımlı SAP sistemlerini ve varsayılanı gösterir." },
83
+ adt_ping: { intro: "Seçili sisteme bağlantıyı ve oturumu test eder." },
84
+ adt_search_objects: {
85
+ intro: "Repository'de isme göre obje arar. İsmin bir kısmını ve '*' joker karakterini kullanın.",
86
+ fields: {
87
+ query: { label: "Aranacak isim", placeholder: "örn: ZCL_MUSTERI*", help: "'*' joker karakter olarak kullanılabilir." },
88
+ objectType: { label: "Obje türü", options: SEARCH_TYPE_OPTIONS, help: "Belirli bir türle sınırla; boş = tümü." },
89
+ maxResults: { label: "Kaç sonuç", default: 50, advanced: true },
90
+ },
91
+ },
92
+ adt_grep_source: {
93
+ intro: "Kaynak kod İÇİNDE metin arar (isimde değil). Bir paket, transport ya da obje listesi seçin.",
94
+ fields: {
95
+ pattern: { label: "Aranan metin", placeholder: "örn: CALL FUNCTION", help: "Düz metin yazın; ileri düzey için regex (Gelişmiş)." },
96
+ package: { label: "Paket", placeholder: "örn: ZFLEET" },
97
+ transport: { label: "Transport", advanced: true, placeholder: "örn: E4DK900123" },
98
+ objects: { label: "Obje listesi (JSON)", advanced: true },
99
+ recursive: { label: "Alt paketlere de in", advanced: true },
100
+ flags: { label: "Regex flag'leri", advanced: true, help: "Boş = harf duyarsız ('i'). '' yazıp kapatamazsınız; teknik." },
101
+ prefix: { label: "Alt paket öneki", advanced: true },
102
+ maxPackages: { label: "Maks. paket", advanced: true },
103
+ maxObjects: { label: "Maks. obje", advanced: true },
104
+ maxMatches: { label: "Maks. eşleşme", advanced: true },
105
+ },
106
+ },
107
+ adt_browse_package: {
108
+ intro: "Bir paketin doğrudan içeriğini (tek seviye) listeler.",
109
+ fields: { package: { label: "Paket", placeholder: "örn: ZLOCAL" } },
110
+ },
111
+ adt_where_used: {
112
+ intro: "Bir objenin nerelerde kullanıldığını listeler.",
113
+ fields: {
114
+ object: { label: "Obje adı", placeholder: "örn: ZCL_MUSTERI" },
115
+ type: { label: "Obje türü", options: OBJECT_TYPE_OPTIONS },
116
+ group: { label: "Fonksiyon grubu", advanced: true, help: "Sadece FM/FUGR include için." },
117
+ },
118
+ },
119
+ adt_get_source: {
120
+ intro: "Bir objenin kaynak kodunu getirir.",
121
+ fields: {
122
+ object: { label: "Obje adı", placeholder: "örn: ZCL_MUSTERI" },
123
+ type: { label: "Obje türü", options: OBJECT_TYPE_OPTIONS },
124
+ include: { label: "Sınıf include'u", options: CLASS_INCLUDE_OPTIONS, advanced: true },
125
+ group: { label: "Fonksiyon grubu", advanced: true },
126
+ onlyMethod: { label: "Sadece bu metod", advanced: true, placeholder: "örn: CONSTRUCTOR" },
127
+ firstLine: { label: "İlk satır", advanced: true },
128
+ lastLine: { label: "Son satır", advanced: true },
129
+ },
130
+ },
131
+ adt_read_table: {
132
+ intro: "Veritabanına salt-okunur SELECT çalıştırır (sadece SELECT).",
133
+ fields: {
134
+ query: { label: "SELECT sorgusu", placeholder: "SELECT matnr, matkl FROM mara WHERE matnr LIKE 'M%'" },
135
+ maxRows: { label: "Maks. satır", default: 100, advanced: true },
136
+ },
137
+ },
138
+ adt_run_atc: {
139
+ intro: "Seçili objelerde ABAP Test Cockpit (ATC) kontrolü çalıştırır.",
140
+ fields: {
141
+ objects: { label: "Objeler (JSON liste)", placeholder: '[{"name":"ZCL_X","type":"class"}]', help: 'Her öğe { "name": "...", "type": "..." }.' },
142
+ checkVariant: { label: "Kontrol varyantı", advanced: true, help: "Boş = DEFAULT." },
143
+ },
144
+ },
145
+ adt_list_inactive_objects: {
146
+ intro: "Düzenlenmiş ama henüz aktive edilmemiş objeleri listeler.",
147
+ },
148
+ adt_list_transports: {
149
+ intro: "Görünür transport isteklerini listeler.",
150
+ fields: {
151
+ user: { label: "Kullanıcı (sahibi)", placeholder: "boş = bağlantı kullanıcısı" },
152
+ status: {
153
+ label: "Durum",
154
+ options: [
155
+ { value: "", label: "Değiştirilebilir (varsayılan)" },
156
+ { value: "modifiable", label: "Değiştirilebilir" },
157
+ { value: "released", label: "Serbest bırakılmış" },
158
+ { value: "all", label: "Tümü" },
159
+ ],
160
+ },
161
+ targets: { label: "Hedef sistemler", advanced: true },
162
+ },
163
+ },
164
+ adt_list_dumps: {
165
+ intro: "ST22 kısa dökümlerini (short dumps) listeler.",
166
+ fields: {
167
+ user: { label: "Kullanıcı", placeholder: "örn: OYILMAZ" },
168
+ from: { label: "Başlangıç tarihi", placeholder: "YYYYMMDD, örn: 20260513" },
169
+ to: { label: "Bitiş tarihi", placeholder: "YYYYMMDD", advanced: true },
170
+ host: { label: "Sunucu (host)", advanced: true },
171
+ maxResults: { label: "Kaç sonuç", default: 20, advanced: true },
172
+ },
173
+ },
174
+ adt_get_dump: {
175
+ intro: "Tek bir kısa dökümün detayını id ile getirir.",
176
+ fields: {
177
+ dumpId: { label: "Dump id", help: "adt_list_dumps çıktısındaki id." },
178
+ chapters: { label: "Bölümler (JSON liste)", advanced: true },
179
+ full: { label: "Ham metni de ekle", advanced: true, help: "Büyük (100KB+)." },
180
+ },
181
+ },
182
+ };
183
+
184
+ // Turkish column/field labels for the result tables. Unknowns are humanized.
185
+ const COLUMN_LABELS = {
186
+ name: "İsim",
187
+ type: "Tür",
188
+ description: "Açıklama",
189
+ object: "Obje",
190
+ line: "Satır",
191
+ text: "Metin",
192
+ uri: "URI",
193
+ package: "Paket",
194
+ host: "Host",
195
+ user: "Kullanıcı",
196
+ client: "Mandant",
197
+ readOnly: "Salt-okunur",
198
+ isDefault: "Varsayılan",
199
+ priority: "Öncelik",
200
+ checkTitle: "Kontrol",
201
+ messageTitle: "Mesaj",
202
+ message: "Mesaj",
203
+ timestamp: "Zaman",
204
+ program: "Program",
205
+ runtimeError: "Hata",
206
+ id: "Id",
207
+ hits: "Eşleşme",
208
+ status: "Durum",
209
+ };
210
+
36
211
  function timingSafeEqualStr(a, b) {
37
212
  const ba = Buffer.from(String(a));
38
213
  const bb = Buffer.from(String(b));
@@ -82,11 +257,14 @@ function buildView({ tools, handlers, config, version }) {
82
257
  for (const item of PANEL_TOOLS) {
83
258
  const def = tools.find((t) => t.name === item.name);
84
259
  if (!def || typeof handlers[item.name] !== "function") continue; // tool not present
260
+ const hint = TOOL_HINTS[item.name] ?? {};
85
261
  descriptors.push({
86
262
  name: def.name,
87
263
  label: item.label,
88
264
  cat: item.cat,
89
265
  description: def.description ?? "",
266
+ intro: hint.intro ?? null,
267
+ fields: hint.fields ?? {},
90
268
  schema: def.inputSchema ?? { type: "object", properties: {}, required: [] },
91
269
  });
92
270
  }
@@ -96,6 +274,7 @@ function buildView({ tools, handlers, config, version }) {
96
274
  defaultSystem: config.defaultSystem ?? null,
97
275
  readOnly: Boolean(config.readOnly),
98
276
  tools: descriptors,
277
+ columnLabels: COLUMN_LABELS,
99
278
  };
100
279
  return { allow, meta, html: renderHtml() };
101
280
  }
@@ -319,6 +498,40 @@ function renderHtml() {
319
498
  pre.out:empty { display:none; }
320
499
  .muted { color:#717a90; }
321
500
  #boot { padding:40px 20px; color:#8b93a7; }
501
+ /* friendly form */
502
+ .field .help { display:block; color:#717a90; font-size:10.5px; margin-top:3px; }
503
+ details.adv { margin:4px 0 8px; border-top:1px dashed #2a3142; padding-top:6px; }
504
+ details.adv > summary { cursor:pointer; color:#8b93a7; font-size:11px; list-style:none; user-select:none; }
505
+ details.adv > summary::before { content:"▸ "; }
506
+ details.adv[open] > summary::before { content:"▾ "; }
507
+ /* rendered results */
508
+ .res { margin-top:10px; }
509
+ .res:empty { display:none; }
510
+ .summary { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:8px; }
511
+ .chip { font-size:10.5px; padding:2px 8px; border-radius:999px; background:#1b2030; color:#aeb6c9; }
512
+ .chip b { color:#e6e8ee; font-weight:600; }
513
+ .chip.ok { background:#13361f; color:#5fd38a; }
514
+ .chip.bad { background:#3a1d1d; color:#f0a3a3; }
515
+ .errbox { background:#2a1414; border:1px solid #5a2a2a; border-radius:7px; padding:10px;
516
+ color:#f0a3a3; font-size:12px; }
517
+ .errbox .st { font-weight:600; color:#ffd0d0; }
518
+ .blk { margin:8px 0; }
519
+ .blk > .blktitle { font-size:11px; color:#9aa3b8; margin-bottom:4px; text-transform:uppercase; letter-spacing:.05em; }
520
+ table.grid { width:100%; border-collapse:collapse; font-size:11.5px; display:block; overflow:auto; max-height:360px; }
521
+ table.grid th, table.grid td { border:1px solid #232838; padding:4px 7px; text-align:left;
522
+ vertical-align:top; white-space:pre-wrap; word-break:break-word; max-width:340px; }
523
+ table.grid th { background:#171c28; color:#9aa3b8; position:sticky; top:0; font-weight:600; }
524
+ table.grid tr:nth-child(even) td { background:#10131b; }
525
+ .code { background:#0b0d13; border:1px solid #202635; border-radius:7px; padding:10px;
526
+ max-height:340px; overflow:auto; white-space:pre-wrap; word-break:break-word;
527
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11.5px; color:#cdd3e0; }
528
+ .chapter { margin:8px 0; }
529
+ .chapter > .chtitle { font-size:11.5px; color:#cda; font-weight:600; margin-bottom:3px; }
530
+ details.raw { margin-top:10px; }
531
+ details.raw > summary { cursor:pointer; color:#717a90; font-size:11px; }
532
+ details.raw pre { margin:6px 0 0; background:#0b0d13; border:1px solid #202635; border-radius:7px;
533
+ padding:10px; max-height:300px; overflow:auto; white-space:pre-wrap; word-break:break-word;
534
+ font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11px; color:#8b93a7; }
322
535
  </style>
323
536
  </head>
324
537
  <body>
@@ -347,49 +560,64 @@ async function call(name, args){
347
560
  return r.json();
348
561
  }
349
562
 
350
- function fieldFor(key, prop, required){
351
- const wrap = document.createElement("div"); wrap.className="field";
352
- const lab = document.createElement("label");
353
- lab.innerHTML = key + (required?' <span class="req">*</span>':'') ;
354
- if(prop.description){ lab.title = prop.description; }
563
+ let COLUMN_LABELS = {};
564
+ const CODE_KEYS = new Set(["source","result","raw","rawtext","metadataxml","runresponse","resultxml"]);
565
+
566
+ function humanize(k){
567
+ return String(k).replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/[_-]+/g," ")
568
+ .replace(/^./,c=>c.toUpperCase());
569
+ }
570
+ function labelFor(k){ return COLUMN_LABELS[k] || humanize(k); }
571
+ function el(tag, cls, txt){ const e=document.createElement(tag); if(cls) e.className=cls; if(txt!=null) e.textContent=txt; return e; }
572
+ function short(v, n){ const s=String(v); return s.length>n ? s.slice(0,n)+"…" : s; }
573
+ function isPlainObject(v){ return v && typeof v==="object" && !Array.isArray(v); }
574
+
575
+ // ----- friendly form ---------------------------------------------------------
576
+ function fieldFor(key, prop, required, hint){
577
+ hint=hint||{};
578
+ const wrap=el("div","field");
579
+ const lab=el("label",null,hint.label||key);
580
+ if(required){ const s=el("span","req"," *"); lab.appendChild(s); }
355
581
  wrap.appendChild(lab);
356
582
  let input;
357
- const t = prop.type;
358
- if(t==="boolean"){
359
- input=document.createElement("input"); input.type="checkbox";
360
- input.style.width="auto";
583
+ const t=prop.type;
584
+ const options = hint.options || (Array.isArray(prop.enum) ? prop.enum.map(v=>({value:v,label:v})) : null);
585
+ if(options){
586
+ input=document.createElement("select");
587
+ if(!hint.options){ const b=el("option",null,"(varsayılan)"); b.value=""; input.appendChild(b); }
588
+ for(const o of options){ const op=el("option",null,o.label); op.value=o.value; input.appendChild(op); }
589
+ } else if(t==="boolean"){
590
+ input=document.createElement("input"); input.type="checkbox"; input.style.width="auto";
361
591
  } else if(t==="integer"||t==="number"){
362
592
  input=document.createElement("input"); input.type="number";
363
593
  if(prop.minimum!=null) input.min=prop.minimum;
364
594
  if(prop.maximum!=null) input.max=prop.maximum;
365
595
  } else if(t==="array"||t==="object"){
366
596
  input=document.createElement("textarea");
367
- input.placeholder="JSON, örn: "+(t==="array"?'[{"name":"...","type":"..."}]':'{}');
368
- } else if(Array.isArray(prop.enum)){
369
- input=document.createElement("select");
370
- const blank=document.createElement("option"); blank.value=""; blank.textContent="(varsayılan)";
371
- input.appendChild(blank);
372
- for(const v of prop.enum){ const o=document.createElement("option"); o.value=v; o.textContent=v; input.appendChild(o); }
597
+ input.placeholder=hint.placeholder || ("JSON, örn: "+(t==="array"?'[{"name":"...","type":"..."}]':'{}'));
373
598
  } else {
374
599
  input=document.createElement("input"); input.type="text";
600
+ if(hint.placeholder) input.placeholder=hint.placeholder;
375
601
  }
376
602
  input.dataset.key=key; input.dataset.jtype=t||"string";
377
- if(prop.description) input.title=prop.description;
603
+ if(hint.default!=null){ if(input.type==="checkbox") input.checked=!!hint.default; else input.value=String(hint.default); }
604
+ const tip=hint.help||prop.description; if(tip) input.title=tip;
378
605
  wrap.appendChild(input);
606
+ if(hint.help){ wrap.appendChild(el("span","help",hint.help)); }
379
607
  return wrap;
380
608
  }
381
609
 
382
610
  function collect(card){
383
611
  const args={};
384
- for(const el of card.querySelectorAll("[data-key]")){
385
- const k=el.dataset.key, t=el.dataset.jtype;
386
- if(el.type==="checkbox"){ if(el.checked) args[k]=true; continue; }
387
- const raw=el.value.trim();
612
+ for(const node of card.querySelectorAll("[data-key]")){
613
+ const k=node.dataset.key, t=node.dataset.jtype;
614
+ if(node.type==="checkbox"){ if(node.checked) args[k]=true; continue; }
615
+ const raw=node.value.trim();
388
616
  if(raw==="") continue;
389
617
  if(t==="integer"){ args[k]=parseInt(raw,10); }
390
618
  else if(t==="number"){ args[k]=Number(raw); }
391
619
  else if(t==="array"||t==="object"){
392
- try { args[k]=JSON.parse(raw); } catch(e){ throw new Error(k+": geçersiz JSON"); }
620
+ try { args[k]=JSON.parse(raw); } catch(e){ throw new Error((k)+": geçersiz JSON"); }
393
621
  }
394
622
  else args[k]=raw;
395
623
  }
@@ -397,37 +625,167 @@ function collect(card){
397
625
  }
398
626
 
399
627
  function buildCard(tool, getSystem){
400
- const card=document.createElement("section"); card.className="card";
401
- const cat=document.createElement("div"); cat.className="cat"; cat.textContent=tool.cat; card.appendChild(cat);
402
- const h=document.createElement("h3"); h.textContent=tool.label; card.appendChild(h);
403
- const d=document.createElement("div"); d.className="desc"; d.textContent=tool.description; card.appendChild(d);
628
+ const card=el("section","card");
629
+ card.appendChild(el("div","cat",tool.cat));
630
+ card.appendChild(el("h3",null,tool.label));
631
+ card.appendChild(el("div","desc",tool.intro||tool.description));
404
632
 
405
633
  const props=tool.schema.properties||{};
406
634
  const req=new Set(tool.schema.required||[]);
635
+ const hints=tool.fields||{};
636
+ const adv=el("details","adv"); adv.appendChild(el("summary",null,"Gelişmiş ayarlar"));
637
+ let advCount=0;
407
638
  for(const key of Object.keys(props)){
408
- if(key==="system") continue; // handled by the global system selector
409
- card.appendChild(fieldFor(key, props[key], req.has(key)));
639
+ if(key==="system") continue; // global system selector handles it
640
+ const hint=hints[key]||{};
641
+ const f=fieldFor(key, props[key], req.has(key), hint);
642
+ if(hint.advanced){ adv.appendChild(f); advCount++; } else card.appendChild(f);
410
643
  }
411
- const btn=document.createElement("button"); btn.className="run"; btn.textContent="Çalıştır"; card.appendChild(btn);
412
- const out=document.createElement("pre"); out.className="out"; card.appendChild(out);
644
+ if(advCount>0) card.appendChild(adv);
645
+
646
+ const btn=el("button","run","Çalıştır"); card.appendChild(btn);
647
+ const out=el("div","res"); card.appendChild(out);
413
648
 
414
649
  btn.addEventListener("click", async ()=>{
415
650
  let args;
416
- try { args=collect(card); } catch(e){ out.className="out err"; out.textContent=e.message; return; }
417
- const sys=getSystem();
418
- if(sys) args.system=sys;
651
+ try { args=collect(card); } catch(e){ showError(out, e.message); return; }
652
+ const sys=getSystem(); if(sys) args.system=sys;
419
653
  btn.disabled=true; const old=btn.textContent; btn.textContent="Çalışıyor…";
420
- out.className="out"; out.textContent="";
654
+ out.textContent="";
421
655
  try {
422
656
  const res=await call(tool.name, args);
423
- out.className="out"+(res.isError?" err":"");
424
- out.textContent=res.text||"(boş yanıt)";
425
- } catch(e){ out.className="out err"; out.textContent=String(e.message||e); }
657
+ renderResult(out, tool.name, res);
658
+ } catch(e){ showError(out, String(e.message||e)); }
426
659
  finally { btn.disabled=false; btn.textContent=old; }
427
660
  });
428
661
  return card;
429
662
  }
430
663
 
664
+ // ----- result rendering ------------------------------------------------------
665
+ function showError(out, msg){ out.innerHTML=""; const b=el("div","errbox"); b.appendChild(el("span","st","Hata")); b.appendChild(document.createTextNode(" — "+msg)); out.appendChild(b); }
666
+
667
+ function renderResult(out, tool, res){
668
+ out.innerHTML="";
669
+ const raw=(res&&res.text)||"";
670
+ let data;
671
+ try { data=JSON.parse(raw); }
672
+ catch(e){ out.appendChild(el("div","code",raw||"(boş yanıt)")); return; }
673
+
674
+ if((res&&res.isError) || (isPlainObject(data)&&data.ok===false)){
675
+ renderErrorObject(out, data);
676
+ appendRaw(out, data);
677
+ return;
678
+ }
679
+ if(!isPlainObject(data)){ out.appendChild(el("div","code",raw)); return; }
680
+ renderObject(out, data);
681
+ appendRaw(out, data);
682
+ }
683
+
684
+ function renderErrorObject(out, data){
685
+ const b=el("div","errbox");
686
+ const err=data.error||{};
687
+ const msg = err.message || err.localizedMessage || (typeof err==="string"?err:null)
688
+ || err.raw || data.parseError || "İstek başarısız.";
689
+ const st = data.status!=null ? ("HTTP "+data.status) : "Hata";
690
+ b.appendChild(el("span","st",st));
691
+ b.appendChild(document.createTextNode(" — "+short(msg, 1200)));
692
+ out.appendChild(b);
693
+ }
694
+
695
+ function renderObject(out, data){
696
+ const scalars=[], tables=[], scalarArrays=[], maps=[], codes=[];
697
+ for(const [k,v] of Object.entries(data)){
698
+ if(v==null) continue;
699
+ const lk=k.toLowerCase();
700
+ if(CODE_KEYS.has(lk) || (typeof v==="string" && (v.indexOf("\\n")>=0 || v.length>160))){ codes.push([k,v]); }
701
+ else if(Array.isArray(v)){
702
+ if(v.length && isPlainObject(v[0])) tables.push([k,v]);
703
+ else if(v.length) scalarArrays.push([k,v]);
704
+ }
705
+ else if(isPlainObject(v)) maps.push([k,v]);
706
+ else scalars.push([k,v]); // string/number/boolean
707
+ }
708
+
709
+ if(scalars.length){
710
+ const s=el("div","summary");
711
+ for(const [k,v] of scalars){
712
+ const c=el("span","chip"+(typeof v==="boolean"?(v?" ok":" bad"):""));
713
+ const b=el("b",null,labelFor(k)+": ");
714
+ c.appendChild(b);
715
+ c.appendChild(document.createTextNode(typeof v==="boolean"?(v?"evet":"hayır"):short(v,80)));
716
+ s.appendChild(c);
717
+ }
718
+ out.appendChild(s);
719
+ }
720
+
721
+ for(const [k,arr] of tables){ out.appendChild(blockTitle(labelFor(k)+" ("+arr.length+")")); out.appendChild(makeTable(arr)); }
722
+
723
+ for(const [k,arr] of scalarArrays){
724
+ const blk=el("div","blk"); blk.appendChild(el("div","blktitle",labelFor(k)+" ("+arr.length+")"));
725
+ blk.appendChild(el("div","code", arr.map(x=>String(x)).join(", "))); out.appendChild(blk);
726
+ }
727
+
728
+ for(const [k,obj] of maps){
729
+ const vals=Object.values(obj);
730
+ if(vals.length && vals.every(x=>typeof x==="string")){
731
+ // chapter-style sections (e.g. dump chapters)
732
+ const blk=el("div","blk"); blk.appendChild(el("div","blktitle",labelFor(k)));
733
+ for(const [sk,sv] of Object.entries(obj)){
734
+ const ch=el("div","chapter"); ch.appendChild(el("div","chtitle",labelFor(sk)));
735
+ ch.appendChild(el("div","code",short(sv,8000))); blk.appendChild(ch);
736
+ }
737
+ out.appendChild(blk);
738
+ } else if(vals.length && vals.every(x=>typeof x==="number")){
739
+ // histogram (e.g. byPriority)
740
+ const blk=el("div","blk"); blk.appendChild(el("div","blktitle",labelFor(k)));
741
+ const s=el("div","summary");
742
+ for(const [sk,sv] of Object.entries(obj)){ const c=el("span","chip"); c.appendChild(el("b",null,labelFor(sk)+": ")); c.appendChild(document.createTextNode(String(sv))); s.appendChild(c); }
743
+ blk.appendChild(s); out.appendChild(blk);
744
+ } else {
745
+ const d=el("details","raw"); d.appendChild(el("summary",null,labelFor(k)));
746
+ d.appendChild(el("pre",null,JSON.stringify(obj,null,2))); out.appendChild(d);
747
+ }
748
+ }
749
+
750
+ for(const [k,v] of codes){
751
+ const blk=el("div","blk"); blk.appendChild(el("div","blktitle",labelFor(k)));
752
+ blk.appendChild(el("div","code",short(v,20000))); out.appendChild(blk);
753
+ }
754
+
755
+ if(!scalars.length && !tables.length && !scalarArrays.length && !maps.length && !codes.length){
756
+ out.appendChild(el("div","code","(boş yanıt)"));
757
+ }
758
+ }
759
+
760
+ function blockTitle(t){ return el("div","blktitle",t); }
761
+
762
+ function makeTable(rows){
763
+ const cols=[]; const seen=new Set();
764
+ for(const r of rows){ for(const k of Object.keys(r)){ if(!seen.has(k)){ seen.add(k); cols.push(k); } } }
765
+ const table=el("table","grid");
766
+ const thead=document.createElement("thead"); const htr=document.createElement("tr");
767
+ for(const c of cols) htr.appendChild(el("th",null,labelFor(c)));
768
+ thead.appendChild(htr); table.appendChild(thead);
769
+ const tbody=document.createElement("tbody");
770
+ for(const r of rows){
771
+ const tr=document.createElement("tr");
772
+ for(const c of cols){
773
+ const v=r[c];
774
+ const cell = v==null ? "" : (typeof v==="object" ? JSON.stringify(v) : String(v));
775
+ tr.appendChild(el("td",null,short(cell,300)));
776
+ }
777
+ tbody.appendChild(tr);
778
+ }
779
+ table.appendChild(tbody);
780
+ return table;
781
+ }
782
+
783
+ function appendRaw(out, data){
784
+ const d=el("details","raw"); d.appendChild(el("summary",null,"Ham JSON"));
785
+ d.appendChild(el("pre",null,JSON.stringify(data,null,2)));
786
+ out.appendChild(d);
787
+ }
788
+
431
789
  (async function init(){
432
790
  let meta;
433
791
  try {
@@ -438,10 +796,11 @@ function buildCard(tool, getSystem){
438
796
  $("#boot").textContent="Panel'e bağlanılamadı: "+e.message+" — token geçersiz ya da session kapandı.";
439
797
  return;
440
798
  }
799
+ COLUMN_LABELS = meta.columnLabels || {};
441
800
  $("#ver").textContent="v"+meta.version;
442
801
  if(meta.readOnly) $("#globalro").style.display="";
443
802
  const sel=$("#system");
444
- for(const s of meta.systems){ const o=document.createElement("option"); o.value=s; o.textContent=s; sel.appendChild(o); }
803
+ for(const s of meta.systems){ const o=el("option",null,s); o.value=s; sel.appendChild(o); }
445
804
  if(meta.defaultSystem) sel.value=meta.defaultSystem;
446
805
  const getSystem=()=>sel.value||"";
447
806