1348 lines
37 KiB
HTML
1348 lines
37 KiB
HTML
<!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> |