IMSAPI_REST_Client/index.html
chris b023bf2c81 Dateien nach "/" hochladen
Need to add on a lokal webserver, dont work as lokal file
2026-03-14 13:08:03 +01:00

1348 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<title>IMSAPI Workbench Keys/Filters/Values (iOS Style)</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
/* ======================================
iOS-STYLE COLORS & TYPO
====================================== */
:root{
--bg:#0f1115;
--panel:#151822;
--panel2:#191d28;
--text:#f2f5f9;
--muted:#a6b0c2;
--accent:#34c759; /* iOS Grün */
--accent-2:#0a84ff; /* iOS Blau */
--warn:#ff9f0a;
--error:#ff453a;
--ok:#30d158;
--chip:#1f2430;
--border:#232836;
--shadow: 0 8px 24px rgba(0,0,0,0.35), 0 2px 6px rgba(0,0,0,0.25);
/* Required */
--req-bg:#2f2812;
--req-border:#cda33a;
--req-text:#ffe8a3;
--radius: 14px;
--radius-sm: 10px;
--radius-pill: 999px;
}
*{box-sizing:border-box}
html, body { height:100%; }
body{
margin:0;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", Roboto, Arial, "Helvetica Neue", sans-serif;
background:linear-gradient(180deg, #0f1115 0%, #0c0e13 100%);
color:var(--text);
display:flex;
height:100vh;
}
/* SIDEBAR */
#sidebar{
width:320px;
background:linear-gradient(180deg, #121522 0%, #0f1220 100%);
padding:16px;
border-right:1px solid var(--border);
overflow:auto;
box-shadow: var(--shadow);
}
h1,h2{margin:0 0 10px}
h1{font-weight:700; letter-spacing:.2px;}
.item{
cursor:pointer;
padding:10px 12px;
margin:6px 0;
border-radius:var(--radius-sm);
color:#ccd3e0;
transition: all .18s ease;
}
.item:hover{
background:#1a2130;
color:#fff;
}
.item.group{
font-weight:700;
background:#131827;
border:1px solid var(--border);
margin-top:12px;
color:#e7ecf7;
box-shadow: var(--shadow);
}
/* CONTENT */
#content{
flex:1;
padding:24px;
overflow:auto;
}
.card {
background:linear-gradient(180deg, #161a27 0%, #121725 100%);
border:1px solid var(--border);
border-radius:var(--radius);
box-shadow: var(--shadow);
}
.param{
margin:12px 0;
padding:12px;
background:linear-gradient(180deg, #191f2e 0%, #171c2a 100%);
border:1px solid var(--border);
border-radius:var(--radius-sm);
}
.param label{
display:block;
font-size:12px;
color:var(--muted);
margin-bottom:6px;
}
/* INPUT */
.param-input{
width:100%;
padding:12px 14px;
border-radius:12px;
border:1px solid #2a3040;
background:#0f1420;
color:var(--text);
outline:none;
transition: border .2s ease, box-shadow .2s;
}
.param-input:focus{
border-color:#3856ff66;
box-shadow:0 0 0 6px rgba(10,132,255,0.08);
}
/* BUTTONS */
.btn{
display:inline-flex;
align-items:center;
gap:8px;
padding:10px 14px;
border:1px solid #2a3040;
background:linear-gradient(180deg, #1b2232 0%, #181f2f 100%);
color:var(--text);
border-radius:12px;
cursor:pointer;
transition: all .16s ease;
}
.btn:hover{
border-color:#3856ff66;
color:#eaf2ff;
transform: translateY(-1px);
}
.btn-primary{
background:linear-gradient(180deg, #36d36f 0%, #2fbf63 100%);
color:#061108;
border-color:transparent;
box-shadow: 0 6px 16px rgba(52,199,89,0.25);
}
.btn-primary:hover{filter:brightness(1.06)}
.btn-small{padding:8px 12px; font-size:13px}
/* CHIPS */
.pill{
display:inline-flex;
align-items:center;
padding:6px 10px;
border-radius:var(--radius-pill);
background:#1a2232;
border:1px solid #2a3146;
color:#e1e7f5;
font-size:12px;
cursor:pointer;
user-select:none;
transition: all .12s ease;
}
.pill:hover{ filter:brightness(1.06); }
.pill.active{
background:#0f2c27;
border-color:#2c7a69;
color:#b7ffec;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.15);
}
.pill.req{
background:var(--req-bg);
border-color:var(--req-border);
color:var(--req-text);
}
/* MODAL (iOS Sheet-Style) */
.modal-backdrop{
position:fixed;
inset:0;
background:rgba(0,0,0,0.45);
display:none;
align-items:flex-end; /* iOS sheet von unten */
justify-content:center;
padding:20px;
z-index:30;
backdrop-filter: blur(2px);
}
.modal{
width:min(940px,100vw);
max-height:85vh;
overflow:auto;
background:linear-gradient(180deg, #141a28 0%, #101624 100%);
border:1px solid #273049;
border-radius:22px 22px 0 0; /* Sheet oben rund */
box-shadow: 0 -8px 30px rgba(0,0,0,0.45);
}
.modal header{
display:flex;
justify-content:space-between;
align-items:center;
padding:16px 18px;
border-bottom:1px solid #273049;
font-weight:700;
letter-spacing:.2px;
}
.modal .content{padding:16px 18px}
.modal .actions{
padding:14px 18px;
border-top:1px solid #273049;
display:flex;
justify-content:flex-end;
gap:10px;
}
.grid{
display:grid;
grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
gap:10px;
}
.sep{height:1px;background:#273049;margin:12px 0}
#result{
background:#0e1220;
border:1px solid #262d44;
padding:14px;
border-radius:12px;
white-space:pre-wrap;
margin-top:12px;
box-shadow: var(--shadow);
}
/* LOGIN PANEL */
#loginPanel{
background:linear-gradient(180deg, #161c2b 0%, #121826 100%);
padding:12px;
border:1px solid var(--border);
border-radius:var(--radius);
margin-bottom:12px;
box-shadow: var(--shadow);
}
h3.section {
margin: 18px 0 10px;
font-size: 13px;
color: var(--muted);
font-weight: 600;
letter-spacing: .2px;
}
</style>
</head>
<body>
<!-- SIDEBAR -->
<div id="sidebar">
<h1>IMSAPI</h1>
<div id="loginPanel">
<label>Server URL:</label>
<input id="serverUrl" class="param-input" placeholder="http://192.168.21.55">
<label>Port:</label>
<input id="serverPort" class="param-input" placeholder="8080">
<label>Station Number:</label>
<input id="stationNumber" class="param-input" placeholder="RVZ01">
<label>Client:</label>
<input id="client" class="param-input" placeholder="01">
<label>Reg Type:</label>
<input id="regType" class="param-input" placeholder="S">
<script>
document.getElementById("regType").value = "S" || "";
document.getElementById("client").value = "01" || "";
</script>
<button id="btnLogin" class="btn btn-primary" style="margin-top:10px;">Login</button>
<div id="loginStatus" class="hint" style="margin-top:8px; color:#c9d3e5;">Nicht eingeloggt</div>
</div>
<div id="apiTree"></div>
</div>
<!-- MAIN -->
<div id="content">
<div class="card" style="padding:18px;">
<h1 style="margin-bottom:12px;">API Details</h1>
<div id="form"></div>
<h3 class="section">Payload</h3>
<pre id="result"></pre>
</div>
</div>
<!-- MODAL -->
<div id="backdrop" class="modal-backdrop">
<div class="modal">
<header>
<span id="modalTitle">Titel</span>
<button id="modalClose" class="btn btn-small" style="background:#101624;border-color:#273049;"></button>
</header>
<div id="modalContent" class="content"></div>
<div class="actions">
<button id="modalCancel" class="btn btn-small" style="background:#101624;border-color:#273049;">Abbrechen</button>
<button id="modalOk" class="btn btn-small btn-primary">OK</button>
</div>
</div>
</div>
<script>
/* ============================================================
DEDUPE HELPERS (optional, falls benötigt)
============================================================ */
function uniqueOptionsByKeyName(options) {
const seen = new Set();
const out = [];
for (const o of options || []) {
const key = String(o?.keyName || o?.name || "").toLowerCase();
if (!key || seen.has(key)) continue;
seen.add(key);
out.push(o);
}
return out;
}
function uniqueFiltersByName(filters) {
const seen = new Set();
const out = [];
for (const f of filters || []) {
// Support plain strings and objects
const key = typeof f === "string" ? f.toLowerCase() : String(f?.filterName || f?.name || "").toLowerCase();
if (!key || seen.has(key)) continue;
seen.add(key);
out.push(f);
}
return out;
}
function uniqueParamsByName(params) {
const seen = new Set();
const out = [];
for (const p of params || []) {
const key = String(p?.name || "").toLowerCase();
if (!seen.has(key)) {
seen.add(key);
out.push(p);
}
}
return out;
}
/* ============================================================
GLOBAL STATE
============================================================ */
let globalLogin = null;
const state = {
currentFn: null,
selections: {}
};
function ensureFnState(fn){
if(!state.selections[fn]){
state.selections[fn]={keys:{},filters:{},values:{},filterValues:{}};
}
if(!state.selections[fn].filterValues){
state.selections[fn].filterValues={};
}
return state.selections[fn];
}
/* ============================================================
HELPERS
============================================================ */
function $(id){return document.getElementById(id);}
function extractPrefix(fnName){
let prefix="";
for(let i=0;i<fnName.length;i++){
const c=fnName[i];
if(c.toUpperCase()===c && c.toLowerCase()!==c) break;
prefix+=c;
}
return prefix.toLowerCase();
}
function chip(label,active=false,className=""){
const el=document.createElement("div");
el.className=`pill ${className} ${active?"active":""}`;
el.textContent=label;
return el;
}
function toggleSet(set, value) {
if (set.has(value)) {
set.delete(value);
} else {
set.add(value);
}
}
/* ============================================================
MODAL SYSTEM (iOS Sheet)
============================================================ */
const backdrop=$("backdrop");
const modalTitle=$("modalTitle");
const modalContent=$("modalContent");
const modalOk=$("modalOk");
const modalCancel=$("modalCancel");
const modalClose=$("modalClose");
function openModal({title,contentNode,onOk}){
modalTitle.textContent=title;
modalContent.innerHTML="";
if(contentNode) modalContent.appendChild(contentNode);
backdrop.style.display="flex";
function close(){
backdrop.style.display="none";
modalOk.onclick=null;
modalCancel.onclick=null;
modalClose.onclick=null;
}
modalOk.onclick=async()=>{
await onOk?.();
close();
};
modalCancel.onclick=close;
modalClose.onclick=close;
}
/* ============================================================
KEY/FILTER CHIPS
============================================================ */
function createKeyChip(option,active){
const required = option.isRequired==="true";
const name = option.keyName;
const el=chip(name + (required?" *":""), active, required?"req":"");
return {el,required};
}
function createFilterChip(option,active){
const required = option.isRequired==="true";
const name = option.filterName;
const el=chip(name + (required?" *":""), active, required?"req":"");
return {el,required};
}
/* ============================================================
LOGIN (regLogin)
============================================================ */
async function doLogin(){
$("loginStatus").textContent="Verbinde...";
const server = $("serverUrl").value.replace(/\/$/,"");
const port = $("serverPort").value;
const baseUrl = `${server}:${port}`;
const payload ={
sessionValidationStruct:{
stationNumber:$("stationNumber").value,
stationPassword:"",
user:"",
password:"",
client:$("client").value,
registrationType:$("regType").value,
systemIdentifier:"Webclient"
}
};
try{
const resp=await fetch(`${baseUrl}/mes/imsapi/rest/actions/regLogin`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify(payload)
});
if(!resp.ok) throw new Error("HTTP "+resp.status);
const data=await resp.json();
if(data.result.return_value!==0){
$("loginStatus").textContent="Login fehlgeschlagen";
return;
}
globalLogin = data;
$("loginStatus").textContent="Session OK: "+data.result.sessionContext.sessionId;
}catch(e){
$("loginStatus").textContent="Fehler: "+e;
}
}
$("btnLogin").onclick=doLogin;
/* ============================================================
LOAD DATA
============================================================ */
let templates={};
let paramInfo={};
async function loadData(){
templates = await (await fetch("./rest_api_templates.json")).json();
paramInfo = await (await fetch("./api_params.json")).json();
// Metadaten säubern
Object.keys(paramInfo).forEach(fn => {
paramInfo[fn] = uniqueParamsByName(paramInfo[fn]).map(p => {
const q = { ...p };
if (Array.isArray(q.options)) q.options = uniqueOptionsByKeyName(q.options);
if (Array.isArray(q.filters)) q.filters = uniqueFiltersByName(q.filters);
return q;
});
});
buildTree();
}
loadData();
/* ============================================================
BUILD API TREE
============================================================ */
function buildTree(){
const tree = $("apiTree");
tree.innerHTML="";
const groups={};
Object.keys(templates).forEach(fn=>{
const prefix=extractPrefix(fn);
if(!groups[prefix]) groups[prefix]=[];
groups[prefix].push(fn);
});
Object.keys(groups)
.sort()
.forEach(prefix=>{
const g=document.createElement("div");
g.className="item group";
g.textContent=prefix;
const sub=document.createElement("div");
sub.style.marginLeft="12px";
sub.style.display="none";
g.onclick=()=>{
sub.style.display=sub.style.display==="none"?"block":"none";
};
groups[prefix].sort().forEach(fn=>{
const e=document.createElement("div");
e.className="item";
e.textContent=fn;
e.style.marginLeft="18px";
e.onclick=()=>loadForm(fn);
sub.appendChild(e);
});
tree.appendChild(g);
tree.appendChild(sub);
});
}
/* ============================================================
FORM BUILDER (Size-Felder werden ausgelassen)
============================================================ */
function loadForm(fn){
state.currentFn=fn;
const fnState=ensureFnState(fn);
const form=$("form");
form.innerHTML="";
const tpl=templates[fn].bodyTemplate;
const params=paramInfo[fn]||[];
const title=document.createElement("h2");
title.textContent=fn;
form.appendChild(title);
// PARAM LIST (skip *.Size)
const ordered = Object.keys(tpl).filter(p=>!p.toLowerCase().endsWith("size"));
ordered.forEach(paramName=>{
const info=params.find(p=>p.name===paramName);
const isArray=Array.isArray(tpl[paramName]);
// **Auto-Preselect Required Keys/Filters in State**, damit Values sofort möglich sind
if (info?.options?.length) {
autoPreselectRequiredKeys(fn, paramName, info);
form.appendChild(renderKeysParam(fn, paramName, info, tpl));
return;
}
// 2) Filters-Param
if (info?.filters?.length) {
autoPreselectRequiredFilters(fn, paramName, info);
form.appendChild(renderFiltersParam(fn, paramName, info));
return;
}
// 3) ResultValues -> unabhängig, eigenes Modal (KEINE Key-Abhängigkeit)
if (paramName.toLowerCase().endsWith("resultvalues")) {
form.appendChild(renderResultValuesParam(fn, paramName));
return;
}
// 4) UploadValues -> abhängig von UploadKeys (wie bisher)
if (paramName.toLowerCase().endsWith("values")) {
form.appendChild(renderValuesParam(fn, paramName, info, tpl));
return;
}
// 5) Fallbacks
if (isArray) form.appendChild(renderArrayFallback(paramName));
else form.appendChild(renderSingleFallback(paramName, info));
});
const row=document.createElement("div");
row.className="row";
row.style.marginTop="12px";
const gen=document.createElement("button");
gen.className="btn btn-primary";
gen.textContent="JSON erzeugen";
gen.onclick=()=>generatePayload(fn);
row.appendChild(gen);
const run=document.createElement("button");
run.className="btn";
run.textContent="API ausführen";
run.onclick=()=>executeCall(fn);
row.appendChild(run);
form.appendChild(row);
// DOKU
const doc=document.createElement("div");
doc.className="card";
doc.style.padding="12px";
doc.style.marginTop="12px";
doc.innerHTML="<h3 class='section' style='margin-top:0'>Dokumentation</h3>";
params.forEach(p=>{
const line=document.createElement("div");
const extra=[];
if(p.options?.length) extra.push(`options:${p.options.length}`);
if(p.filters?.length) extra.push(`filters:${p.filters.length}`);
line.textContent=`${p.name}${p.type}${extra.length?" ("+extra.join(", ")+")":""}`;
doc.appendChild(line);
});
form.appendChild(doc);
}
function renderResultValuesParam(fn, paramName) {
const block = document.createElement("div");
block.className = "param";
const label = document.createElement("label");
label.innerHTML = `<b>${paramName}</b> (ResultValues)`;
block.appendChild(label);
const btn = document.createElement("button");
btn.className = "btn btn-small";
btn.textContent = "ResultValues eingeben";
btn.onclick = () => {
openResultValuesModal(fn, paramName, btn);
};
block.appendChild(btn);
return block;
}
function openResultValuesModal(fn, paramName, btn) {
const fnState = ensureFnState(fn);
const content = document.createElement("div");
// KEINE Key-Abhängigkeit frei eingeben/strukturieren:
const table = document.createElement("table");
table.style.width = "100%";
table.style.borderCollapse = "collapse";
const tbody = document.createElement("tbody");
// Bestehende Werte laden (falls vorhanden)
const current = fnState.values[paramName] || [];
// Helfer zum Hinzufügen einer Zeile
const addRow = (value = "") => {
const tr = document.createElement("tr");
tr.style.borderBottom = "1px solid #273049";
const tdIdx = document.createElement("td");
tdIdx.style.padding = "8px";
tdIdx.style.width = "48px";
tdIdx.style.textAlign = "right";
const tdVal = document.createElement("td");
tdVal.style.padding = "8px";
const tdActions = document.createElement("td");
tdActions.style.padding = "8px";
tdActions.style.width = "80px";
tdActions.style.textAlign = "right";
const inp = document.createElement("input");
inp.className = "param-input";
inp.value = value;
const del = document.createElement("button");
del.className = "btn btn-small";
del.style.background = "#101624";
del.style.borderColor = "#273049";
del.textContent = "";
del.title = "Zeile löschen";
del.onclick = () => {
tbody.removeChild(tr);
reindexRows();
};
tdVal.appendChild(inp);
tdActions.appendChild(del);
tr.appendChild(tdIdx);
tr.appendChild(tdVal);
tr.appendChild(tdActions);
tbody.appendChild(tr);
reindexRows();
};
const reindexRows = () => {
Array.from(tbody.children).forEach((tr, i) => {
tr.firstChild.textContent = String(i + 1);
});
};
// Bestehende Werte in Zeilen darstellen
if (current.length) {
current.forEach(v => addRow(v));
} else {
addRow(""); // mindestens eine Zeile
}
table.appendChild(tbody);
content.appendChild(table);
// Toolbar: + hinzufügen
const toolbar = document.createElement("div");
toolbar.className = "row";
toolbar.style.marginTop = "10px";
const addBtn = document.createElement("button");
addBtn.className = "btn btn-small";
addBtn.textContent = "+ hinzufügen";
addBtn.onclick = () => addRow("");
toolbar.appendChild(addBtn);
content.appendChild(toolbar);
openModal({
title: `${fn}.${paramName} ResultValues`,
contentNode: content,
onOk: () => {
const vals = [];
tbody.querySelectorAll("input").forEach(i => vals.push(i.value));
fnState.values[paramName] = vals;
btn.textContent = `ResultValues (${vals.length})`;
}
});
}
/* === Auto-Preselect Required === */
function autoPreselectRequiredKeys(fn, paramName, info){
const fnState=ensureFnState(fn);
const current = new Set(fnState.keys[paramName]||[]);
const required = (info.options||[]).filter(o=>o.isRequired==="true").map(o=>o.keyName);
if (required.length){
required.forEach(k=>current.add(k));
fnState.keys[paramName] = [...current];
}
}
function normalizeFilters(filters){
return (filters||[]).map(f=>{
if(typeof f === "string") return {filterName:f, isRequired:"false"};
return {filterName: f.filterName||f.name||String(f), isRequired: f.isRequired||"false"};
});
}
function autoPreselectRequiredFilters(fn, paramName, info){
const fnState=ensureFnState(fn);
const current = new Set(fnState.filters[paramName]||[]);
// filters can be plain strings (no isRequired) or objects
const normalized = normalizeFilters(info.filters||[]);
const required = normalized.filter(o=>o.isRequired==="true").map(o=>o.filterName);
if (required.length){
required.forEach(k=>current.add(k));
fnState.filters[paramName] = [...current];
}
}
/* ============================================================
RENDER KEYS
============================================================ */
function renderKeysParam(fn,paramName,info,tpl){
const fnState=ensureFnState(fn);
const selected=new Set(fnState.keys[paramName]||[]);
const block=document.createElement("div");
block.className="param";
const label=document.createElement("label");
label.innerHTML=`<b>${paramName}</b> (Keys)`;
block.appendChild(label);
const btn=document.createElement("button");
btn.className="btn btn-small";
btn.textContent=selected.size
? `Keys (${selected.size})`
: `Keys auswählen`;
btn.onclick=()=>{
openKeysModal(fn,paramName,info.options,selected,(finalSel)=>{
fnState.keys[paramName]=finalSel;
btn.textContent=finalSel.length
? `Keys (${finalSel.length})`
: `Keys auswählen`;
});
};
block.appendChild(btn);
return block;
}
function openKeysModal(fn,paramName,options,selectedSet,onApply){
const content=document.createElement("div");
const grid=document.createElement("div");
grid.className="grid";
// Arbeitsmenge: vorbesetzen mit bisherigen Selektionen
const working=new Set([...selectedSet]);
// **Automatisch Required-Keys hinzufügen**, falls noch nicht drin:
options.forEach(opt=>{
if (opt.isRequired==="true") working.add(opt.keyName);
});
options.forEach(opt=>{
const active = working.has(opt.keyName);
const {el}=createKeyChip(opt, active);
el.onclick=()=>{
// Required bleibt immer gesetzt
if (opt.isRequired==="true") return;
toggleSet(working,opt.keyName);
el.classList.toggle("active");
};
grid.appendChild(el);
});
content.appendChild(grid);
openModal({
title:`${fn}.${paramName} Keys`,
contentNode:content,
onOk:()=>onApply([...working])
});
}
/* ============================================================
RENDER FILTERS
============================================================ */
function renderFiltersParam(fn,paramName,info){
const fnState=ensureFnState(fn);
const selected=new Set(fnState.filters[paramName]||[]);
const block=document.createElement("div");
block.className="param";
const label=document.createElement("label");
label.innerHTML=`<b>${paramName}</b> (Filters)`;
block.appendChild(label);
const row=document.createElement("div");
row.style.display="flex";
row.style.gap="8px";
row.style.alignItems="center";
row.style.flexWrap="wrap";
const filterBtn=document.createElement("button");
filterBtn.className="btn btn-small";
filterBtn.textContent=selected.size
? `Filters (${selected.size})`
: `Filters auswählen`;
// Values-Button (zeigt wieviele befüllte Values existieren)
const valBtn=document.createElement("button");
valBtn.className="btn btn-small";
const existingVals=fnState.filterValues[paramName]||{};
const filledCount=Object.values(existingVals).filter(v=>v&&v.trim()).length;
valBtn.textContent=filledCount?`Filter-Values (${filledCount})`:`Filter-Values eingeben`;
valBtn.style.borderColor=filledCount?"#34c759":"";
valBtn.style.color=filledCount?"#34c759":"";
filterBtn.onclick=()=>{
openFiltersModal(fn,paramName,info.filters,selected,(finalSel)=>{
finalSel.forEach(k=>selected.add(k));
// Remove deselected from selected set
[...selected].forEach(k=>{ if(!finalSel.includes(k)) selected.delete(k); });
fnState.filters[paramName]=finalSel;
filterBtn.textContent=finalSel.length?`Filters (${finalSel.length})`:`Filters auswählen`;
// Update valBtn
const vals=fnState.filterValues[paramName]||{};
const fc=Object.values(vals).filter(v=>v&&v.trim()).length;
valBtn.textContent=fc?`Filter-Values (${fc})`:`Filter-Values eingeben`;
});
};
valBtn.onclick=()=>{
openFilterKeyValuesModal(fn,paramName,valBtn);
};
row.appendChild(filterBtn);
row.appendChild(valBtn);
block.appendChild(row);
return block;
}
function openFiltersModal(fn,paramName,filters,selectedSet,onApply){
const normalized = normalizeFilters(filters);
const content=document.createElement("div");
const grid=document.createElement("div");
grid.className="grid";
const working=new Set([...selectedSet]);
// Automatisch Required-Filter hinzufügen
normalized.forEach(f=>{
if (f.isRequired==="true") working.add(f.filterName);
});
normalized.forEach(f=>{
const active = working.has(f.filterName);
const {el}=createFilterChip(f, active);
el.onclick=()=>{
if (f.isRequired==="true") return;
toggleSet(working,f.filterName);
el.classList.toggle("active");
};
grid.appendChild(el);
});
content.appendChild(grid);
openModal({
title:`${fn}.${paramName} Filters`,
contentNode:content,
onOk:()=>onApply([...working])
});
}
/* ============================================================
FILTER KEY-VALUE MODAL
Zeigt alle gewählten Filter als Zeile mit Textfeld für den Value.
Im JSON werden nur Einträge ausgegeben, die einen nicht-leeren Value haben.
============================================================ */
function openFilterKeyValuesModal(fn, paramName, btn){
const fnState=ensureFnState(fn);
const chosenFilters=fnState.filters[paramName]||[];
const current=fnState.filterValues[paramName]||{};
const content=document.createElement("div");
if(!chosenFilters.length){
const warn=document.createElement("div");
warn.style.color="var(--warn)";
warn.style.padding="8px 0";
warn.textContent="Keine Filter ausgewählt. Bitte zuerst Filter auswählen.";
content.appendChild(warn);
openModal({title:`${fn}.${paramName} Filter Values`,contentNode:content,onOk:()=>{}});
return;
}
const hint=document.createElement("div");
hint.style.color="var(--muted)";
hint.style.fontSize="12px";
hint.style.marginBottom="10px";
hint.textContent="Nur Felder mit eingetragenem Wert werden im JSON ausgegeben.";
content.appendChild(hint);
const table=document.createElement("table");
table.style.width="100%";
table.style.borderCollapse="collapse";
const thead=document.createElement("thead");
thead.innerHTML=`<tr>
<th style="text-align:left;padding:6px 8px;color:var(--muted);font-size:12px;border-bottom:1px solid #273049;">#</th>
<th style="text-align:left;padding:6px 8px;color:var(--muted);font-size:12px;border-bottom:1px solid #273049;">Filter Key</th>
<th style="text-align:left;padding:6px 8px;color:var(--muted);font-size:12px;border-bottom:1px solid #273049;">Value</th>
</tr>`;
table.appendChild(thead);
const tbody=document.createElement("tbody");
const inputMap={};
chosenFilters.forEach((filterKey,i)=>{
const tr=document.createElement("tr");
tr.style.borderBottom="1px solid #1e2436";
const c1=document.createElement("td");
c1.style.padding="8px";
c1.style.color="var(--muted)";
c1.style.fontSize="13px";
c1.style.width="36px";
c1.textContent=i+1;
const c2=document.createElement("td");
c2.style.padding="8px";
c2.style.fontWeight="600";
c2.style.fontSize="13px";
c2.textContent=filterKey;
const c3=document.createElement("td");
c3.style.padding="8px";
const inp=document.createElement("input");
inp.className="param-input";
inp.placeholder=`Wert für ${filterKey}`;
inp.value=current[filterKey]||"";
inputMap[filterKey]=inp;
c3.appendChild(inp);
tr.appendChild(c1);
tr.appendChild(c2);
tr.appendChild(c3);
tbody.appendChild(tr);
});
table.appendChild(tbody);
content.appendChild(table);
openModal({
title:`${fn}.${paramName} Filter Values`,
contentNode:content,
onOk:()=>{
const vals={};
Object.entries(inputMap).forEach(([k,inp])=>{
vals[k]=inp.value; // leere Werte bleiben, werden beim Payload-Build gefiltert
});
fnState.filterValues[paramName]=vals;
const filledCount=Object.values(vals).filter(v=>v&&v.trim()).length;
btn.textContent=filledCount?`Filter-Values (${filledCount})`:`Filter-Values eingeben`;
btn.style.borderColor=filledCount?"#34c759":"";
btn.style.color=filledCount?"#34c759":"";
}
});
}
/* ============================================================
RENDER VALUES
============================================================ */
function renderValuesParam(fn,paramName,info,tpl){
const block=document.createElement("div");
block.className="param";
const label=document.createElement("label");
label.innerHTML=`<b>${paramName}</b> (Values)`;
block.appendChild(label);
const btn=document.createElement("button");
btn.className="btn btn-small";
btn.textContent="Values eingeben";
btn.onclick=()=>{
openValuesModal(fn,paramName,btn);
};
block.appendChild(btn);
return block;
}
function openValuesModal(fn,paramName,btn){
const fnState=ensureFnState(fn);
const keysParamName = paramName.replace(/Values$/i,"Keys");
const keys = fnState.keys[keysParamName]||[];
const content=document.createElement("div");
if(!keys.length){
const warn=document.createElement("div");
warn.style.color="var(--warn)";
warn.textContent="Keine Keys gewählt. (Required-Keys werden automatisch selektiert, bitte Keys prüfen.)";
content.appendChild(warn);
openModal({
title:`${fn}.${paramName} Values`,
contentNode:content,
onOk:()=>{}
});
return;
}
const table=document.createElement("table");
table.style.width="100%";
table.style.borderCollapse="collapse";
const tbody=document.createElement("tbody");
const current=fnState.values[paramName]||[];
keys.forEach((k,idx)=>{
const tr=document.createElement("tr");
tr.style.borderBottom="1px solid #273049";
const c1=document.createElement("td");
c1.style.padding="8px";
c1.textContent=idx+1;
const c2=document.createElement("td");
c2.style.padding="8px";
c2.textContent=k;
const c3=document.createElement("td");
c3.style.padding="8px";
const inp=document.createElement("input");
inp.className="param-input";
inp.value=current[idx]||"";
inp.placeholder=k;
c3.appendChild(inp);
tr.appendChild(c1);
tr.appendChild(c2);
tr.appendChild(c3);
tbody.appendChild(tr);
});
table.appendChild(tbody);
content.appendChild(table);
openModal({
title:`${fn}.${paramName} Values`,
contentNode:content,
onOk:()=>{
const vals=[];
tbody.querySelectorAll("input").forEach(i=>vals.push(i.value));
fnState.values[paramName]=vals;
btn.textContent=`Values (${vals.length})`;
}
});
}
/* ============================================================
FALLBACK FORM FIELDS
============================================================ */
function renderArrayFallback(paramName){
const block=document.createElement("div");
block.className="param";
const label=document.createElement("label");
label.innerHTML=`<b>${paramName}</b> (Array)`;
block.appendChild(label);
const list=document.createElement("div");
block.appendChild(list);
const add=document.createElement("button");
add.className="btn btn-small";
add.textContent="+ hinzufügen";
add.onclick=()=>{
const inp=document.createElement("input");
inp.className="param-input";
inp.dataset.paramName=paramName+"[]";
list.appendChild(inp);
};
block.appendChild(add);
return block;
}
function renderSingleFallback(paramName,info){
const block=document.createElement("div");
block.className="param";
const label=document.createElement("label");
label.innerHTML=`<b>${paramName}</b>`;
block.appendChild(label);
const inp=document.createElement("input");
inp.className="param-input";
inp.dataset.paramName=paramName;
block.appendChild(inp);
return block;
}
/* ============================================================
PAYLOAD BUILDER (sessionContext + ohne Size)
============================================================ */
function generatePayload(fn){
const payload={};
const fnState=ensureFnState(fn);
// Single / Array inputs
document.querySelectorAll("#form [data-param-name]").forEach(inp=>{
const name=inp.dataset.paramName;
if(name.endsWith("[]")){
const base=name.slice(0,-2);
if(!payload[base]) payload[base]=[];
if(inp.value.trim()) payload[base].push(inp.value.trim());
} else {
if(inp.value.trim()) payload[name]=inp.value.trim();
}
});
// Keys
Object.entries(fnState.keys).forEach(([key,arr])=>{
payload[key]=[...arr];
});
// Filters: als [{key, value}] Array nur Einträge mit befülltem Value
Object.entries(fnState.filters).forEach(([paramName,arr])=>{
const filterVals=fnState.filterValues[paramName]||{};
const hasAnyValue=arr.some(k=>filterVals[k]&&filterVals[k].trim());
if(hasAnyValue){
payload[paramName]=arr
.filter(k=>filterVals[k]&&filterVals[k].trim())
.map(k=>({key:k,value:filterVals[k].trim()}));
} else {
// Keine Values nur die Keys als Array ausgeben (wie bisher)
payload[paramName]=[...arr];
}
});
// Values
Object.entries(fnState.values).forEach(([key,arr])=>{
payload[key]=[...arr];
});
// Size-Parameter entfernen (Sicherheit)
Object.keys(payload).forEach(k=>{
if (k.toLowerCase().endsWith("size")) delete payload[k];
});
// sessionContext
if(globalLogin){
payload.sessionContext={
sessionId:globalLogin.result.sessionContext.sessionId,
persId:globalLogin.result.sessionContext.persId,
locale:"en_EN"
};
}
$("result").textContent=JSON.stringify(payload,null,2);
return payload;
}
/* ============================================================
API EXECUTION
============================================================ */
async function executeCall(fn){
const payload=generatePayload(fn);
if(!globalLogin){
alert("Bitte zuerst Login durchführen.");
return;
}
const server=$("serverUrl").value.replace(/\/$/,"");
const port=$("serverPort").value;
const baseUrl=`${server}:${port}`;
try{
console.log("Payload"+fn);
console.log(payload);
const resp=await fetch(`${baseUrl}/mes/imsapi/rest/actions/${fn}`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify(payload)
});
if(!resp.ok) throw new Error("HTTP "+resp.status);
const data=await resp.json();
console.log(data);
$("result").textContent="Ergebnis:\n\n"+JSON.stringify(data,null,2);
}
catch(e){
$("result").textContent="Error:\n"+e;
}
}
// === FilterValues (Integrated) ===
function renderFilterValuesParam(fn, paramName, info){
const block=document.createElement('div');
block.className='param';
const label=document.createElement('label');
label.innerHTML=`<b>${paramName}</b> (FilterValues)`;
block.appendChild(label);
const btn=document.createElement('button');
btn.className='btn btn-small';
btn.textContent='FilterValues eingeben';
btn.onclick=()=>openFilterValuesModal(fn,paramName,btn);
block.appendChild(btn);
return block;
}
function openFilterValuesModal(fn,paramName,btn){
const fnState=ensureFnState(fn);
const filtersParamName = paramName.replace(/Values$/i,"");
const filters = fnState.filters[filtersParamName] || [];
const current = fnState.values[paramName] || [];
const content=document.createElement('div');
const table=document.createElement('table');
table.style.width='100%';
const tbody=document.createElement('tbody');
if(!filters.length){
const warn=document.createElement('div');
warn.style.color='var(--warn)';
warn.textContent='Keine Filter gewählt.';
content.appendChild(warn);
openModal({title:`${fn}.${paramName}`,contentNode:content,onOk:()=>{}});
return;
}
filters.forEach((f,i)=>{
const tr=document.createElement('tr');
const c1=document.createElement('td'); c1.textContent=i+1;
const c2=document.createElement('td'); c2.textContent=f;
const c3=document.createElement('td');
const inp=document.createElement('input'); inp.className='param-input'; inp.placeholder=f; inp.value=current[i]||'';
c3.appendChild(inp);
tr.appendChild(c1); tr.appendChild(c2); tr.appendChild(c3);
tbody.appendChild(tr);
});
table.appendChild(tbody);
content.appendChild(table);
openModal({
title:`${fn}.${paramName} FilterValues`,
contentNode:content,
onOk:()=>{
const vals=[];
tbody.querySelectorAll('input').forEach(i=>vals.push(i.value));
fnState.values[paramName]=vals;
btn.textContent=`FilterValues (${vals.length})`;
}
});
}
</script>
</body>
</html>