Browse Source

Komponentenverwaltung und Repository JSP

master
Andre Knieschewski 1 month ago
parent
commit
5e83685688
  1. 345
      superx/edit/kern/superx_komponentenverwaltung.jsp
  2. 32
      superx/edit/kern/sx_repository_list.jsp

345
superx/edit/kern/superx_komponentenverwaltung.jsp

@ -416,6 +416,7 @@ boolean jobLogHasStatus = false; @@ -416,6 +416,7 @@ boolean jobLogHasStatus = false;
String dbVersionTableTag = tag("unbekannt", "warn");
String dbVersionRowsHtml = "";
String dbVersionFallbackJson = "{}";
String dbSpecialJobsJson = "[]";
String availableVersionFallbackJson = "{}";
String availableVersionRowsHtml = "";
Map availableVersionMap = new LinkedHashMap();
@ -537,6 +538,47 @@ try { @@ -537,6 +538,47 @@ try {
versionJson.append("}");
dbVersionFallbackJson = versionJson.toString();
}
// Sonderladeroutinen direkt aus etl_job lesen. Dadurch bleiben sie auch sichtbar,
// wenn /etl/list das zugehörige Modul nicht liefert.
if (tableExistsByRegclass(diagCon, "etl_job") || tableExistsByRegclass(diagCon, "public.etl_job")) {
ps = null;
rs = null;
StringBuilder specialJson = new StringBuilder();
specialJson.append("[");
boolean firstSpecial = true;
try {
ps = diagCon.prepareStatement(
"select uniquename, caption, systeminfo_id from etl_job " +
"where right(lower(uniquename),8) = '_special' " +
"order by systeminfo_id, uniquename"
);
rs = ps.executeQuery();
while (rs.next()) {
String jobId = rs.getString(1);
String caption = rs.getString(2);
Object sidObj = rs.getObject(3);
if (jobId == null || jobId.trim().length() == 0) continue;
if (!firstSpecial) specialJson.append(",");
firstSpecial = false;
specialJson.append("{\"id\":\"").append(jsString(jobId.trim())).append("\"");
specialJson.append(",\"name\":\"").append(jsString(caption == null || caption.trim().length() == 0 ? jobId.trim() : caption.trim())).append("\"");
specialJson.append(",\"description\":\"\"");
specialJson.append(",\"sourceDatabase\":\"\"");
specialJson.append(",\"jobType\":\"special\"");
specialJson.append(",\"fromDatabase\":true");
if (sidObj != null) specialJson.append(",\"systeminfoId\":").append(String.valueOf(sidObj));
specialJson.append("}");
}
} catch (Exception e) {
specialJson = new StringBuilder("[]");
} finally {
try { if (rs != null) rs.close(); } catch (Exception e) { }
try { if (ps != null) ps.close(); } catch (Exception e) { }
}
if (specialJson.charAt(specialJson.length() - 1) != ']') specialJson.append("]");
dbSpecialJobsJson = specialJson.toString();
}
} catch (Exception e) {
dbDiagnosticMessage = e.getMessage();
dbInfoHtml = "<tr><td>Direkte DB-Prüfung</td><td>" + tag("nicht verfügbar", "warn") + " " + html(e.getMessage()) + "</td></tr>";
@ -567,6 +609,16 @@ body{background:#f5f7fa} @@ -567,6 +609,16 @@ body{background:#f5f7fa}
.code{font-family:Consolas,Monaco,"Courier New",monospace;font-size:.78rem;background:#eef2f6;border:1px solid #dfe5ec;border-radius:5px;padding:.1rem .35rem;color:#273647;word-break:break-all}
.action-cell{min-width:470px}
.action-row{display:flex;flex-wrap:wrap;gap:.4rem}
.special-toggle-arrow{display:inline-block;width:.9rem;text-align:center;margin-right:.2rem;font-size:.82rem}
.special-routines-row>td{padding:.15rem .75rem .9rem .75rem!important;border-top:0!important;background:#f8fafc}
.special-routines-panel{border:1px solid #d9e5ef;border-left:4px solid #3c88b8;border-radius:8px;background:#fff;padding:.9rem 1rem;box-shadow:0 .25em .65em -.2em rgba(10,10,10,.08)}
.special-routines-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.65rem}
.special-routines-list{display:grid;gap:.55rem}
.special-routine-item{display:grid;grid-template-columns:minmax(240px,1.25fr) minmax(280px,1.75fr) auto;gap:1rem;align-items:center;padding:.7rem .8rem;border:1px solid #e7edf3;border-radius:7px;background:#fbfcfd}
.special-routine-name{font-weight:700;margin-bottom:.2rem}
.special-routine-description{font-size:.8rem;color:#697386;line-height:1.35}
.special-routine-meta{font-size:.78rem;color:#697386;line-height:1.55}
.special-routine-meta .code{margin-left:.25rem}
.panel-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:1rem}
.panel-head h2{margin:0;font-size:1.15rem;font-weight:700}
.panel-body{padding:0}
@ -586,7 +638,7 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p @@ -586,7 +638,7 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p
.message{margin-bottom:1rem}
.small,.muted{color:#697386}.small{font-size:.8rem;line-height:1.35}
.table td,.table th{vertical-align:top}
@media(max-width:1150px){.details-grid,.kv,.plan-grid,.run-grid{grid-template-columns:1fr}.action-cell{min-width:320px}}
@media(max-width:1150px){.details-grid,.kv,.plan-grid,.run-grid,.special-routine-item{grid-template-columns:1fr}.action-cell{min-width:320px}}
.diagnostics-panel{border-left:5px solid #3c88b8}
.diagnostics-panel .panel-head{margin-bottom:.75rem}
@ -630,7 +682,7 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p @@ -630,7 +682,7 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p
</footer>
</div>
</div>
<div class="toolbar"><input id="searchInput" type="search" placeholder="Module suchen nach Name, Kürzel, Systeminfo-ID oder Datenquelle"><select id="filterSelect"><option value="all">Alle Module</option><option value="installed">Installiert</option><option value="complete">Unload &amp; Update möglich</option><option value="unload">Unload möglich</option><option value="update">Update möglich</option><option value="dbok">DB verbunden</option><option value="dberror">DB nicht verbunden</option></select><button id="reloadBtn" class="button is-primary" type="button">Aktualisieren</button><span id="moduleCount" class="small muted"></span><span class="spacer"></span><button id="diagnosticsBtn" class="button is-info is-light" type="button">Diagnose</button></div>
<div class="toolbar"><input id="searchInput" type="search" placeholder="Module suchen nach Name, Kürzel, Systeminfo-ID oder Datenquelle"><select id="filterSelect"><option value="all">Alle Module</option><option value="installed">Installiert</option><option value="complete">Unload &amp; Update möglich</option><option value="unload">Unload möglich</option><option value="update">Update möglich</option><option value="special">Sonderladeroutinen vorhanden</option><option value="dbok">DB verbunden</option><option value="dberror">DB nicht verbunden</option></select><button id="reloadModulesBtn" class="button is-warning is-light" type="button" title="Modul- und ETL-Jobdefinitionen aus dem Dateisystem neu in die Datenbank einlesen">Module neu einlesen</button><span id="moduleCount" class="small muted"></span><span class="spacer"></span><button id="diagnosticsBtn" class="button is-info is-light" type="button">Diagnose</button></div>
<div id="diagnosticsPanel" class="box diagnostics-panel hidden">
<div class="panel-head"><h2>Systemdiagnose</h2><div><button id="refreshDiagnosticsBtn" class="button is-light" type="button">Diagnose aktualisieren</button> <button id="closeDiagnosticsBtn" class="button is-light" type="button">Schließen</button></div></div>
@ -678,8 +730,13 @@ find <%= html(servletRealPath == null ? "/home/superx/webapps/superx" : servletR @@ -678,8 +730,13 @@ find <%= html(servletRealPath == null ? "/home/superx/webapps/superx" : servletR
</div>
<div class="box p-0"><table class="table is-fullwidth is-hoverable"><thead><tr><th>Modul</th><th>Status</th><th>Versionen</th><th>Letztes Update</th><th>Datenquelle</th><th>DB-Verbindung</th><th>Aktionen</th></tr></thead><tbody id="moduleTbody"><tr><td colspan="7" class="muted">Module werden geladen...</td></tr></tbody></table></div>
<details id="unassignedSpecialsBox" class="box hidden">
<summary>Nicht zugeordnete Sonderladeroutinen <span id="unassignedSpecialsCount" class="tag is-info is-light" style="margin-left:.4rem;">0</span></summary>
<div class="small muted" style="margin:.65rem 0 .8rem 0;">Diese Sonderladeroutinen sind in der ETL-Jobdatenbank vorhanden, konnten aber keinem aktuell gelieferten Modul aus der Komponentenliste zugeordnet werden.</div>
<div id="unassignedSpecialsBody" class="special-routines-list"></div>
</details>
<div id="detailsPanel" class="box hidden"><div class="panel-head"><h2 id="detailsTitle">Details</h2><button id="closeDetailsBtn" class="button is-light" type="button">Schließen</button></div><div id="detailsBody" class="panel-body"></div></div>
<div class="box"><div class="panel-head"><h2>Aktueller Lauf</h2><div><button id="refreshRunBtn" class="button is-light" type="button">Status aktualisieren</button> <button id="stopRunBtn" class="button is-danger is-light" type="button" disabled>Stoppen</button></div></div><div class="panel-body"><div class="run-grid"><div class="muted">JobExecutionId</div><div id="runId">-</div><div class="muted">Job</div><div id="runJob">-</div><div class="muted">Status</div><div id="runStatus">Kein aktiver Lauf bekannt.</div><div class="muted">Fortschritt</div><div><div class="progress-wrap"><div id="progressBar" class="progress-bar"></div></div><div id="progressText" class="small muted" style="margin-top:6px;">-</div></div></div></div></div>
<div id="runPanel" class="box"><div class="panel-head"><h2>Aktueller Lauf</h2><div><button id="refreshRunBtn" class="button is-light" type="button">Status aktualisieren</button> <button id="stopRunBtn" class="button is-danger is-light" type="button" disabled>Stoppen</button></div></div><div class="panel-body"><div class="run-grid"><div class="muted">JobExecutionId</div><div id="runId">-</div><div class="muted">Job</div><div id="runJob">-</div><div class="muted">Status</div><div id="runStatus">Kein aktiver Lauf bekannt.</div><div class="muted">Fortschritt</div><div><div class="progress-wrap"><div id="progressBar" class="progress-bar"></div></div><div id="progressText" class="small muted" style="margin-top:6px;">-</div></div></div></div></div>
<div class="box"><div class="panel-head"><h2>Log-Ausgabe</h2><span id="logHint" class="small muted">Noch kein Log geladen.</span></div><div class="panel-body"><div class="log-actions"><button id="loadLogBtn" class="button is-light" type="button" disabled>Log laden</button><button id="clearLogBtn" class="button is-light" type="button">Log leeren</button></div><pre id="logOutput" class="log">Noch kein Lauf gestartet.</pre></div></div>
</div>
<script>
@ -691,9 +748,14 @@ var TOOLTIP_UPDATE='Laden der Rohdaten (Hauptladeroutine)'; @@ -691,9 +748,14 @@ var TOOLTIP_UPDATE='Laden der Rohdaten (Hauptladeroutine)';
var TOOLTIP_UNLOAD_UPDATE='Unload und Update hintereinander';
var TOOLTIP_UPGRADE='Modulaktualisierung (Upgrade der Datenstrukturen auf den ausgelieferten Stand)';
var DB_VERSION_FALLBACK=<%= dbVersionFallbackJson %>;
var DB_SPECIAL_JOBS=<%= dbSpecialJobsJson %>;
var AVAILABLE_VERSION_FALLBACK=<%= availableVersionFallbackJson %>;
var modules=[];
var jobsByComponent={};
var expandedSpecials={};
var moduleLoadGeneration=0;
var SPECIAL_CACHE_KEY='superx.komponentenverwaltung.specials.v01';
var specialConnectorCache=loadSpecialConnectorCache();
var jobErrorsByComponent={};
var currentExecutionId=null;
var currentLogId=null;
@ -701,7 +763,7 @@ var lastStatusPayload=null; @@ -701,7 +763,7 @@ var lastStatusPayload=null;
var currentJobLabel=null;
var pollTimer=null;
var logFailureByExecutionId={};
var el={message:document.getElementById('message'),confirmModal:document.getElementById('confirmModal'),confirmTitle:document.getElementById('confirmTitle'),confirmBody:document.getElementById('confirmBody'),confirmCloseBtn:document.getElementById('confirmCloseBtn'),confirmCancelBtn:document.getElementById('confirmCancelBtn'),confirmOkBtn:document.getElementById('confirmOkBtn'),searchInput:document.getElementById('searchInput'),filterSelect:document.getElementById('filterSelect'),reloadBtn:document.getElementById('reloadBtn'),moduleCount:document.getElementById('moduleCount'),moduleTbody:document.getElementById('moduleTbody'),diagnosticsBtn:document.getElementById('diagnosticsBtn'),diagnosticsPanel:document.getElementById('diagnosticsPanel'),refreshDiagnosticsBtn:document.getElementById('refreshDiagnosticsBtn'),closeDiagnosticsBtn:document.getElementById('closeDiagnosticsBtn'),diagnosticsRest:document.getElementById('diagnosticsRest'),diagnosticsVersions:document.getElementById('diagnosticsVersions'),detailsPanel:document.getElementById('detailsPanel'),detailsTitle:document.getElementById('detailsTitle'),detailsBody:document.getElementById('detailsBody'),closeDetailsBtn:document.getElementById('closeDetailsBtn'),refreshRunBtn:document.getElementById('refreshRunBtn'),stopRunBtn:document.getElementById('stopRunBtn'),runId:document.getElementById('runId'),runJob:document.getElementById('runJob'),runStatus:document.getElementById('runStatus'),progressBar:document.getElementById('progressBar'),progressText:document.getElementById('progressText'),loadLogBtn:document.getElementById('loadLogBtn'),clearLogBtn:document.getElementById('clearLogBtn'),logOutput:document.getElementById('logOutput'),logHint:document.getElementById('logHint')};
var el={message:document.getElementById('message'),confirmModal:document.getElementById('confirmModal'),confirmTitle:document.getElementById('confirmTitle'),confirmBody:document.getElementById('confirmBody'),confirmCloseBtn:document.getElementById('confirmCloseBtn'),confirmCancelBtn:document.getElementById('confirmCancelBtn'),confirmOkBtn:document.getElementById('confirmOkBtn'),searchInput:document.getElementById('searchInput'),filterSelect:document.getElementById('filterSelect'),reloadModulesBtn:document.getElementById('reloadModulesBtn'),moduleCount:document.getElementById('moduleCount'),moduleTbody:document.getElementById('moduleTbody'),unassignedSpecialsBox:document.getElementById('unassignedSpecialsBox'),unassignedSpecialsCount:document.getElementById('unassignedSpecialsCount'),unassignedSpecialsBody:document.getElementById('unassignedSpecialsBody'),diagnosticsBtn:document.getElementById('diagnosticsBtn'),diagnosticsPanel:document.getElementById('diagnosticsPanel'),refreshDiagnosticsBtn:document.getElementById('refreshDiagnosticsBtn'),closeDiagnosticsBtn:document.getElementById('closeDiagnosticsBtn'),diagnosticsRest:document.getElementById('diagnosticsRest'),diagnosticsVersions:document.getElementById('diagnosticsVersions'),detailsPanel:document.getElementById('detailsPanel'),detailsTitle:document.getElementById('detailsTitle'),detailsBody:document.getElementById('detailsBody'),closeDetailsBtn:document.getElementById('closeDetailsBtn'),runPanel:document.getElementById('runPanel'),refreshRunBtn:document.getElementById('refreshRunBtn'),stopRunBtn:document.getElementById('stopRunBtn'),runId:document.getElementById('runId'),runJob:document.getElementById('runJob'),runStatus:document.getElementById('runStatus'),progressBar:document.getElementById('progressBar'),progressText:document.getElementById('progressText'),loadLogBtn:document.getElementById('loadLogBtn'),clearLogBtn:document.getElementById('clearLogBtn'),logOutput:document.getElementById('logOutput'),logHint:document.getElementById('logHint')};
function esc(v){if(v===null||v===undefined){return '';}return String(v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}
function attr(v){return esc(v).replace(/`/g,'&#96;');}
function code(v){return '<span class="code">'+esc(v)+'</span>';}
@ -718,6 +780,7 @@ function askConfirm(title,bodyHtml,okLabel){ @@ -718,6 +780,7 @@ function askConfirm(title,bodyHtml,okLabel){
done=true;
el.confirmModal.classList.remove('is-active');
el.confirmModal.setAttribute('aria-hidden','true');
if(document.activeElement===el.confirmOkBtn||document.activeElement===el.confirmCancelBtn||document.activeElement===el.confirmCloseBtn){document.activeElement.blur();}
el.confirmOkBtn.onclick=null;
el.confirmCancelBtn.onclick=null;
el.confirmCloseBtn.onclick=null;
@ -751,6 +814,9 @@ function actionConfirmBody(actionLabel,moduleName,abbr,hint,extraHtml){ @@ -751,6 +814,9 @@ function actionConfirmBody(actionLabel,moduleName,abbr,hint,extraHtml){
}
function setLog(t,h){el.logOutput.textContent=t||'';el.logHint.textContent=h||'';}
function appendLog(t){var old=el.logOutput.textContent||'';if(old==='Noch kein Lauf gestartet.'){old='';}el.logOutput.textContent=old+(old?'\n':'')+t;}
function scrollToRunArea(){if(!el.runPanel){return;}window.requestAnimationFrame(function(){el.runPanel.scrollIntoView({behavior:'smooth',block:'start'});});}
function runViewportAnchor(){return el.runPanel?el.runPanel.getBoundingClientRect().top:null;}
function restoreRunViewport(anchor){if(anchor===null||anchor===undefined||!el.runPanel){return;}var delta=el.runPanel.getBoundingClientRect().top-anchor;if(Math.abs(delta)>0.5){window.scrollBy(0,delta);}}
function extractLogText(data){if(data===null||data===undefined){return '';}if(typeof data==='string'){return data;}if(typeof data==='object'){if(data.log!==undefined&&data.log!==null){return String(data.log);}if(data.content!==undefined&&data.content!==null){return String(data.content);}if(data.message!==undefined&&data.message!==null){return String(data.message);}return stringify(data);}return String(data);}function extractLogLabel(data,id){if(data&&typeof data==='object'&&data.jobAbbreviation){return 'Log für JobExecutionId '+id+' ('+data.jobAbbreviation+')';}return 'Log für JobExecutionId '+id;}function fetchText(url,opt){var o=opt||{};o.credentials='same-origin';return fetch(url,o).then(function(r){return r.text().then(function(t){if(!r.ok){throw new Error('HTTP '+r.status+' bei '+url+(t?': '+t.substring(0,500):''));}return t;});});}
function fetchJson(url,opt){return fetchText(url,opt).then(function(t){if(!t||!t.trim()){return null;}try{return JSON.parse(t);}catch(e){return t;}});}
function key(m){return String(m.abbreviation||m.id||m.name||'');}
@ -770,6 +836,99 @@ function findJobById(arr,id){for(var i=0;i<arr.length;i++){if(String(arr[i].id|| @@ -770,6 +836,99 @@ function findJobById(arr,id){for(var i=0;i<arr.length;i++){if(String(arr[i].id||
function findJobBySuffix(arr,suffix){for(var i=0;i<arr.length;i++){var id=String(arr[i].id||'');if(id.length>=suffix.length&&id.substring(id.length-suffix.length)===suffix){return arr[i];}}return null;}
function findJobContains(arr,part){for(var i=0;i<arr.length;i++){if(String(arr[i].id||'').indexOf(part)>=0){return arr[i];}}return null;}
function sourceDb(m,j){var id=String(j&&j.id?j.id:'');var c=m.connectors||[];for(var i=0;i<c.length;i++){if(String(c[i].id||'')===id&&c[i].sourceDatabase){return c[i].sourceDatabase;}}if(m.database){return m.database;}return 'eduetl';}
function loadSpecialConnectorCache(){try{var raw=window.sessionStorage?sessionStorage.getItem(SPECIAL_CACHE_KEY):null;if(!raw){return {};}var parsed=JSON.parse(raw);return parsed&&typeof parsed==='object'?parsed:{};}catch(e){return {};}}
function saveSpecialConnectorCache(){try{if(window.sessionStorage){sessionStorage.setItem(SPECIAL_CACHE_KEY,JSON.stringify(specialConnectorCache));}}catch(e){}}
function clearSpecialConnectorCache(){specialConnectorCache={};try{if(window.sessionStorage){sessionStorage.removeItem(SPECIAL_CACHE_KEY);}}catch(e){}}
function rawSpecialConnectors(m){var c=(m&&Array.isArray(m.connectors))?m.connectors:[],out=[];for(var i=0;i<c.length;i++){var x=c[i]||{},id=String(x.id||x.jobId||x.uniquename||''),type=String(x.jobType||x.type||'').toLowerCase();if(id&&(type==='special'||/_special$/.test(id))){out.push(x);}}return out;}
function inferSpecialOwnerKey(x,parentKey){
if(!x){return cleanModuleKey(parentKey||'');}
var explicit=['moduleAbbreviation','module_abbreviation','moduleId','module_id','componentId','component_id','component','module'];
for(var i=0;i<explicit.length;i++){var v=cleanModuleKey(x[explicit[i]]);if(v){return v;}}
var parent=cleanModuleKey(parentKey||'');if(parent){return parent;}
var id=String(x.id||x.jobId||x.uniquename||'').toLowerCase();
var p=id.indexOf('_');return p>0?cleanModuleKey(id.substring(0,p)):'';
}
function rememberSpecialConnectors(list){
var changed=false;if(!Array.isArray(list)){return;}
for(var i=0;i<list.length;i++){
var m=list[i],parent=cleanModuleKey(key(m)),items=rawSpecialConnectors(m);
for(var j=0;j<items.length;j++){
var x=items[j],owner=inferSpecialOwnerKey(x,parent)||'__unassigned__',copy={};
for(var k in x){if(Object.prototype.hasOwnProperty.call(x,k)){copy[k]=x[k];}}
copy._ownerKey=owner;
if(!Array.isArray(specialConnectorCache[owner])){specialConnectorCache[owner]=[];}
var replaced=false;
for(var z=0;z<specialConnectorCache[owner].length;z++){
if(String(specialConnectorCache[owner][z].id||specialConnectorCache[owner][z].jobId||'')===String(copy.id||copy.jobId||'')){specialConnectorCache[owner][z]=copy;replaced=true;break;}
}
if(!replaced){specialConnectorCache[owner].push(copy);}
changed=true;
}
}
if(changed){saveSpecialConnectorCache();}
}
function specialBelongsToModule(m,x){
if(!m||!x){return false;}
var abbr=cleanModuleKey(key(m)),owner=inferSpecialOwnerKey(x,x._ownerKey||'');
if(abbr&&owner&&abbr===owner){return true;}
if(m.systeminfoId!==undefined&&m.systeminfoId!==null&&x.systeminfoId!==undefined&&x.systeminfoId!==null&&String(m.systeminfoId)===String(x.systeminfoId)){return true;}
var id=String(x.id||x.jobId||x.uniquename||'').toLowerCase();
return !!abbr&&id.indexOf(abbr+'_')===0;
}
function allKnownSpecialConnectors(){
var out=[],byId={};
function add(x,owner){
if(!x){return;}var id=String(x.id||x.jobId||x.uniquename||''),type=String(x.jobType||x.type||'').toLowerCase();
if(!id||!(type==='special'||/_special$/.test(id))){return;}
var existing=byId[id];
if(!existing){existing={id:id,name:x.name||x.caption||id,description:x.description||'',sourceDatabase:x.sourceDatabase||x.database||'',jobType:'special',systeminfoId:x.systeminfoId,_ownerKey:inferSpecialOwnerKey(x,owner||'')};byId[id]=existing;out.push(existing);}
else{if((!existing.name||existing.name===id)&&(x.name||x.caption)){existing.name=x.name||x.caption;}if(!existing.description&&x.description){existing.description=x.description;}if(!existing.sourceDatabase&&(x.sourceDatabase||x.database)){existing.sourceDatabase=x.sourceDatabase||x.database;}if((existing.systeminfoId===undefined||existing.systeminfoId===null)&&x.systeminfoId!==undefined){existing.systeminfoId=x.systeminfoId;}if(!existing._ownerKey){existing._ownerKey=inferSpecialOwnerKey(x,owner||'');}}
}
if(Array.isArray(DB_SPECIAL_JOBS)){for(var d=0;d<DB_SPECIAL_JOBS.length;d++){add(DB_SPECIAL_JOBS[d],'');}}
for(var owner in specialConnectorCache){if(Object.prototype.hasOwnProperty.call(specialConnectorCache,owner)&&Array.isArray(specialConnectorCache[owner])){for(var c=0;c<specialConnectorCache[owner].length;c++){add(specialConnectorCache[owner][c],owner);}}}
for(var mi=0;mi<modules.length;mi++){var raw=rawSpecialConnectors(modules[mi]);for(var r=0;r<raw.length;r++){add(raw[r],key(modules[mi]));}}
return out;
}
function unassignedSpecialConnectors(){
var all=allKnownSpecialConnectors(),out=[];
for(var i=0;i<all.length;i++){var assigned=false;for(var j=0;j<modules.length;j++){if(specialBelongsToModule(modules[j],all[i])){assigned=true;break;}}if(!assigned){out.push(all[i]);}}
return out;
}
function specialConnectors(m){
var out=[],byId={},c=(m&&Array.isArray(m.connectors))?m.connectors:[];
function add(x,fromJobList){
if(!x){return;}
var id=String(x.id||x.jobId||x.uniquename||''),type=String(x.jobType||x.type||'').toLowerCase();
if(!id||!(type==='special'||/_special$/.test(id))){return;}
var existing=byId[id];
if(!existing){
existing={
id:id,
name:x.name||x.caption||id,
description:x.description||'',
sourceDatabase:x.sourceDatabase||x.database||'',
jobType:'special',
_fromJobList:!!fromJobList
};
byId[id]=existing;
out.push(existing);
}else{
if((!existing.name||existing.name===id)&&(x.name||x.caption)){existing.name=x.name||x.caption;}
if(!existing.description&&x.description){existing.description=x.description;}
if(!existing.sourceDatabase&&(x.sourceDatabase||x.database)){existing.sourceDatabase=x.sourceDatabase||x.database;}
if(!fromJobList){existing._fromJobList=false;}
}
}
for(var i=0;i<c.length;i++){add(c[i],false);}
if(Array.isArray(DB_SPECIAL_JOBS)){for(var di=0;di<DB_SPECIAL_JOBS.length;di++){if(specialBelongsToModule(m,DB_SPECIAL_JOBS[di])){add(DB_SPECIAL_JOBS[di],false);}}}
var cached=specialConnectorCache[cleanModuleKey(key(m))]||[];
for(var ci=0;ci<cached.length;ci++){add(cached[ci],false);}
var technical=jobs(m);
for(var j=0;j<technical.length;j++){add(technical[j],true);}
return out;
}
function specialSourceDb(m,c){if(c&&c.sourceDatabase!==undefined&&c.sourceDatabase!==null&&String(c.sourceDatabase).trim()!==''){return String(c.sourceDatabase).trim();}if(c&&c.database!==undefined&&c.database!==null&&String(c.database).trim()!==''){return String(c.database).trim();}if(m&&m.database!==undefined&&m.database!==null&&String(m.database).trim()!==''){return String(m.database).trim();}return 'eduetl';}
function findSpecialConnector(m,id){var c=specialConnectors(m),wanted=String(id||'');for(var i=0;i<c.length;i++){if(String(c[i].id||'')===wanted){return c[i];}}return null;}
function displayDataSource(m){var raw=(m&&m.database!==undefined&&m.database!==null&&String(m.database).trim()!=='')?String(m.database):'eduetl / keine Datenquelle gesetzt';var normalized=raw.toLowerCase().replace(/[\s_\-\/]/g,'');if(normalized.indexOf('eduetl')>=0){return 'SuperX';}return raw;}
function isDeliveredVersion(v){if(v===null||v===undefined){return false;}var s=String(v).trim();if(!s){return false;}var n=s.toLowerCase();return n!=='nicht geliefert'&&n!=='null'&&n!=='undefined';}
function versionInfo(m){var installedKeys=['installedVersion','currentVersion','dbVersion','databaseVersion','componentVersion','moduleVersion','version','installed_version','current_version','db_version'];var targetKeys=['upgradeVersion','targetVersion','availableVersion','latestVersion','packageVersion','fileVersion','newVersion','upgrade_version','target_version','available_version','latest_version','package_version','file_version','new_version'];function first(keys){for(var i=0;i<keys.length;i++){if(isDeliveredVersion(m[keys[i]])){return String(m[keys[i]]).trim();}}return null;}function dbFallback(){var abbr=key(m).toLowerCase();if(abbr&&DB_VERSION_FALLBACK&&isDeliveredVersion(DB_VERSION_FALLBACK[abbr])){return String(DB_VERSION_FALLBACK[abbr]).trim();}if(m.systeminfoId!==undefined&&m.systeminfoId!==null&&DB_VERSION_FALLBACK&&isDeliveredVersion(DB_VERSION_FALLBACK['sid:'+m.systeminfoId])){return String(DB_VERSION_FALLBACK['sid:'+m.systeminfoId]).trim();}return null;}function availableInfo(){var abbr=key(m).toLowerCase();if(abbr&&AVAILABLE_VERSION_FALLBACK&&AVAILABLE_VERSION_FALLBACK[abbr]){return AVAILABLE_VERSION_FALLBACK[abbr];}return {};}
@ -783,14 +942,148 @@ function unloadPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unloa @@ -783,14 +942,148 @@ function unloadPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unloa
function updateOnlyPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_load_and_transform';if(m.installed!==true){return notInstalledPlan(m,id);}var j=findJobById(arr,id)||findJobBySuffix(arr,'_load_and_transform')||findJobContains(arr,'load_and_transform');if(j){return {available:true,job:j,sourceDatabase:sourceDb(m,j),endpoint:'/job/execute/'+sourceDb(m,j)+'/'+j.id,reason:'Gefunden über /job/list/'+abbr+'. Dieser Job führt Load und Transform aus und setzt in der Regel bereits vorhandene oder zuvor entladene Rohdaten voraus.',searched:id};}if(flagTrue(m.load)){return inferredPlan(m,id,'Über /job/list/'+abbr+' wurde kein passender Update-Job gefunden. /etl/list meldet jedoch load=true; deshalb wird der Standardjob '+id+' als Fallback angeboten.',id);}return {available:false,job:null,sourceDatabase:null,endpoint:null,reason:reasonForMissing(m,err,arr,'kein einzelner Update-Job gefunden. Gesucht wurde '+id+' beziehungsweise *_load_and_transform.'),searched:id};}
function upgradePlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),installUpgrade=findJobById(arr,abbr+'_install_upgrade')||findJobBySuffix(arr,'_install_upgrade'),vi=versionInfo(m),isInstall=m.installed!==true,endpoint='/job/upgrade/'+abbr,ds=null;var r=isInstall?'Das Modul ist noch nicht installiert. Wenn der technische *_install_upgrade-Job verfügbar ist, wird dieser direkt über /job/execute gestartet.':(m.needsUpgrade===true?'Die Komponentenliste meldet needsUpgrade=true. Ein Upgradebedarf wurde erkannt.':'Die Komponentenliste meldet keinen Upgradebedarf. Das Upgrade kann trotzdem bewusst erneut ausgeführt werden.');if(err){r+=' Hinweis: Die technische Jobliste konnte nicht geladen werden: '+err;}else if(installUpgrade&&installUpgrade.id){ds=sourceDb(m,installUpgrade);endpoint='/job/execute/'+ds+'/'+installUpgrade.id;r+=' In der technischen Jobliste wurde '+installUpgrade.id+' gefunden. Dieser technische Install-/Upgrade-Job wird direkt gestartet; das ist robuster als der allgemeine Komponenten-Endpunkt /job/upgrade/{modul}.';}else{r+=' In der technischen Jobliste wurde kein *_install_upgrade-Job gefunden; gestartet wird weiterhin der Komponenten-Endpunkt.';}return {available:!!abbr,endpoint:endpoint,job:installUpgrade,sourceDatabase:ds,reason:r,versionInfo:vi,actionLabel:isInstall?'Installieren':'Upgrade'};}
function fmtDate(v){if(v===null||v===undefined||v===''||v===0){return '<span class="muted">-</span>';}var n=Number(v);if(isNaN(n)){return esc(v);}var d=new Date(n);if(!isNaN(d.getTime())){if(d.getFullYear()<1971){return '<span class="muted">nicht gesetzt</span>';}return esc(d.toLocaleString('de-DE'));}return esc(v);}
function renderModules(){var q=(el.searchInput.value||'').toLowerCase(),filter=el.filterSelect.value,rows=[],shown=0;for(var i=0;i<modules.length;i++){var m=modules[i],abbr=key(m),vi=versionInfo(m),displayDb=displayDataSource(m),hay=[m.name||'',abbr,m.database||'',displayDb,m.systeminfoId||'',vi.installed||'',vi.target||''].join(' ').toLowerCase();if(q&&hay.indexOf(q)<0){continue;}var cp=completePlan(m),ul=unloadPlan(m),uo=updateOnlyPlan(m),ug=upgradePlan(m);if(filter==='installed'&&m.installed!==true){continue;}if(filter==='complete'&&!cp.available){continue;}if(filter==='unload'&&!ul.available){continue;}if(filter==='update'&&!uo.available){continue;}if(filter==='dbok'&&m.databaseConnected!==true){continue;}if(filter==='dberror'&&m.databaseConnected===true){continue;}shown++;var moduleHtml='<div class="module-name">'+esc(m.name||abbr||'-')+'</div><div class="module-line">Kürzel: '+code(abbr||'-')+'</div><div class="module-line">Systeminfo-ID: '+esc(m.systeminfoId||'-')+'</div>';var status=m.installed===true?badge('installiert','ok'):badge('nicht installiert','warn');var versionHtml='<div class="version-line">installiert: '+versionValue(vi.installed)+'</div><div class="version-line">verfügbar: '+versionValue(vi.target)+'</div>';var db=m.databaseConnected===true?badge('verbunden','ok'):(m.databaseConnected===false?badge('nicht verbunden','warn'):badge('unbekannt','neutral'));var ds=esc(displayDb);var actions='<div class="action-row">';actions+=ul.available?'<button class="button is-light is-small js-unload" type="button" title="'+attr(TOOLTIP_UNLOAD)+'" aria-label="Unload: '+attr(TOOLTIP_UNLOAD)+'" data-component="'+attr(abbr)+'">Unload</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UNLOAD)+'" aria-label="Unload: '+attr(TOOLTIP_UNLOAD)+'" disabled>Unload</button>';actions+=uo.available?'<button class="button is-light is-small js-updateonly" type="button" title="'+attr(TOOLTIP_UPDATE)+'" aria-label="Update: '+attr(TOOLTIP_UPDATE)+'" data-component="'+attr(abbr)+'">Update</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UPDATE)+'" aria-label="Update: '+attr(TOOLTIP_UPDATE)+'" disabled>Update</button>';actions+=cp.available?'<button class="button is-primary is-small js-complete" type="button" title="'+attr(TOOLTIP_UNLOAD_UPDATE)+'" aria-label="Unload & Update: '+attr(TOOLTIP_UNLOAD_UPDATE)+'" data-component="'+attr(abbr)+'">Unload &amp; Update</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UNLOAD_UPDATE)+'" aria-label="Unload & Update: '+attr(TOOLTIP_UNLOAD_UPDATE)+'" disabled>Unload &amp; Update</button>';var installUpgradeLabel=m.installed===true?'Upgrade':'Installieren';var installUpgradeTooltip=m.installed===true?TOOLTIP_UPGRADE:'Modulinstallation durchführen';actions+=ug.available?'<button class="button is-warning is-light is-small js-upgrade" type="button" title="'+attr(installUpgradeTooltip)+'" aria-label="'+attr(installUpgradeLabel)+': '+attr(installUpgradeTooltip)+'" data-component="'+attr(abbr)+'">'+esc(installUpgradeLabel)+'</button>':'<button class="button is-light is-small" type="button" title="'+attr(installUpgradeTooltip)+'" aria-label="'+attr(installUpgradeLabel)+': '+attr(installUpgradeTooltip)+'" disabled>'+esc(installUpgradeLabel)+'</button>';actions+='<button class="button is-info is-light is-small js-details" type="button" data-component="'+attr(abbr)+'">Details</button></div>';rows.push('<tr><td>'+moduleHtml+'</td><td>'+status+'</td><td>'+versionHtml+'</td><td>'+fmtDate(m.lastUpdate)+'</td><td>'+ds+'</td><td>'+db+'</td><td class="action-cell">'+actions+'</td></tr>');}el.moduleCount.textContent=shown+' von '+modules.length+' Modulen angezeigt';el.moduleTbody.innerHTML=rows.length?rows.join(''):'<tr><td colspan="7" class="muted">Keine Module gefunden.</td></tr>';bindButtons();}
function bindButtons(){var bs=document.querySelectorAll('.js-complete');for(var i=0;i<bs.length;i++){bs[i].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),completePlan,'Unload & Update');};}bs=document.querySelectorAll('.js-unload');for(var j=0;j<bs.length;j++){bs[j].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),unloadPlan,'Unload');};}bs=document.querySelectorAll('.js-updateonly');for(var k=0;k<bs.length;k++){bs[k].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),updateOnlyPlan,'Update');};}bs=document.querySelectorAll('.js-upgrade');for(var l=0;l<bs.length;l++){bs[l].onclick=function(){startUpgrade(findModule(this.getAttribute('data-component')));};}bs=document.querySelectorAll('.js-details');for(var m=0;m<bs.length;m++){bs[m].onclick=function(){showDetails(findModule(this.getAttribute('data-component')));};}}
function renderSpecialRoutinesRow(m,specials){
var abbr=key(m),h='<tr class="special-routines-row"><td colspan="7"><div class="special-routines-panel">';
h+='<div class="special-routines-head"><div><strong>Sonderladeroutinen</strong><div class="small muted">Werden unabhängig von der normalen Modul-Laderoutine gestartet.</div></div><span class="tag is-info is-light">'+specials.length+'</span></div>';
h+='<div class="special-routines-list">';
for(var i=0;i<specials.length;i++){
var sc=specials[i],sid=String(sc.id||''),sdb=specialSourceDb(m,sc),desc=sc.description||'Keine Beschreibung hinterlegt.';
h+='<div class="special-routine-item">';
h+='<div><div class="special-routine-name">'+esc(sc.name||sid||'-')+'</div><div class="special-routine-description">'+esc(desc)+'</div></div>';
h+='<div class="special-routine-meta"><div>Datenquelle: <span class="code">'+esc(sdb)+'</span></div><div>Job-ID: <span class="code">'+esc(sid||'-')+'</span></div></div>';
h+='<div><button class="button is-link is-light is-small js-special-start" type="button" data-component="'+attr(abbr)+'" data-special-id="'+attr(sid)+'">Starten</button></div>';
h+='</div>';
}
h+='</div></div></td></tr>';
return h;
}
function unassignedExecutionDb(c){
if(c&&c.sourceDatabase!==undefined&&c.sourceDatabase!==null&&String(c.sourceDatabase).trim()!==''){return String(c.sourceDatabase).trim();}
var owner=inferSpecialOwnerKey(c,c&&c._ownerKey?c._ownerKey:'');
return owner||'eduetl';
}
function renderUnassignedSpecials(){
if(!el.unassignedSpecialsBox||!el.unassignedSpecialsBody){return 0;}
var q=(el.searchInput.value||'').toLowerCase(),filter=el.filterSelect.value,all=unassignedSpecialConnectors(),shown=[];
if(filter!=='all'&&filter!=='special'){el.unassignedSpecialsBox.classList.add('hidden');return 0;}
for(var i=0;i<all.length;i++){var sc=all[i],owner=inferSpecialOwnerKey(sc,sc._ownerKey||''),hay=[sc.name||'',sc.description||'',sc.id||'',sc.sourceDatabase||'',owner||'',sc.systeminfoId||''].join(' ').toLowerCase();if(!q||hay.indexOf(q)>=0){shown.push(sc);}}
if(shown.length===0){el.unassignedSpecialsBox.classList.add('hidden');el.unassignedSpecialsBody.innerHTML='';if(el.unassignedSpecialsCount){el.unassignedSpecialsCount.textContent='0';}return 0;}
var h='';
for(var j=0;j<shown.length;j++){
var x=shown[j],sid=String(x.id||''),ownerKey=inferSpecialOwnerKey(x,x._ownerKey||''),sdb=unassignedExecutionDb(x);
h+='<div class="special-routine-item">';
h+='<div><div class="special-routine-name">'+esc(x.name||sid||'-')+'</div><div class="special-routine-description">'+esc(x.description||'Keine Beschreibung hinterlegt.')+'</div></div>';
h+='<div class="special-routine-meta"><div>Vermutetes Modul: <span class="code">'+esc(ownerKey||'nicht ermittelbar')+'</span></div>'+(x.systeminfoId!==undefined&&x.systeminfoId!==null?'<div>Systeminfo-ID: <span class="code">'+esc(x.systeminfoId)+'</span></div>':'')+'<div>Ausführungs-Datenbank: <span class="code">'+esc(sdb)+'</span></div><div>Job-ID: <span class="code">'+esc(sid||'-')+'</span></div></div>';
h+='<div><button class="button is-link is-light is-small js-unassigned-special-start" type="button" data-special-id="'+attr(sid)+'">Starten</button></div>';
h+='</div>';
}
el.unassignedSpecialsBody.innerHTML=h;
if(el.unassignedSpecialsCount){el.unassignedSpecialsCount.textContent=String(shown.length);}
el.unassignedSpecialsBox.classList.remove('hidden');
return shown.length;
}
function renderModules(){
var q=(el.searchInput.value||'').toLowerCase(),filter=el.filterSelect.value,rows=[],shown=0;
for(var i=0;i<modules.length;i++){
var m=modules[i],abbr=key(m),vi=versionInfo(m),displayDb=displayDataSource(m),specials=specialConnectors(m),specialSearch=[];
for(var si=0;si<specials.length;si++){specialSearch.push(specials[si].name||'',specials[si].description||'',specials[si].id||'',specials[si].sourceDatabase||'');}
var hay=[m.name||'',abbr,m.database||'',displayDb,m.systeminfoId||'',vi.installed||'',vi.target||''].concat(specialSearch).join(' ').toLowerCase();
if(q&&hay.indexOf(q)<0){continue;}
var cp=completePlan(m),ul=unloadPlan(m),uo=updateOnlyPlan(m),ug=upgradePlan(m);
if(filter==='installed'&&m.installed!==true){continue;}
if(filter==='complete'&&!cp.available){continue;}
if(filter==='unload'&&!ul.available){continue;}
if(filter==='update'&&!uo.available){continue;}
if(filter==='special'&&specials.length===0){continue;}
if(filter==='dbok'&&m.databaseConnected!==true){continue;}
if(filter==='dberror'&&m.databaseConnected===true){continue;}
shown++;
var moduleHtml='<div class="module-name">'+esc(m.name||abbr||'-')+'</div><div class="module-line">Kürzel: '+code(abbr||'-')+'</div><div class="module-line">Systeminfo-ID: '+esc(m.systeminfoId||'-')+'</div>';
var status=m.installed===true?badge('installiert','ok'):badge('nicht installiert','warn');
var versionHtml='<div class="version-line">installiert: '+versionValue(vi.installed)+'</div><div class="version-line">verfügbar: '+versionValue(vi.target)+'</div>';
var db=m.databaseConnected===true?badge('verbunden','ok'):(m.databaseConnected===false?badge('nicht verbunden','warn'):badge('unbekannt','neutral'));
var ds=esc(displayDb),actions='<div class="action-row">';
actions+=ul.available?'<button class="button is-light is-small js-unload" type="button" title="'+attr(TOOLTIP_UNLOAD)+'" aria-label="Unload: '+attr(TOOLTIP_UNLOAD)+'" data-component="'+attr(abbr)+'">Unload</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UNLOAD)+'" aria-label="Unload: '+attr(TOOLTIP_UNLOAD)+'" disabled>Unload</button>';
actions+=uo.available?'<button class="button is-light is-small js-updateonly" type="button" title="'+attr(TOOLTIP_UPDATE)+'" aria-label="Update: '+attr(TOOLTIP_UPDATE)+'" data-component="'+attr(abbr)+'">Update</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UPDATE)+'" aria-label="Update: '+attr(TOOLTIP_UPDATE)+'" disabled>Update</button>';
actions+=cp.available?'<button class="button is-primary is-small js-complete" type="button" title="'+attr(TOOLTIP_UNLOAD_UPDATE)+'" aria-label="Unload & Update: '+attr(TOOLTIP_UNLOAD_UPDATE)+'" data-component="'+attr(abbr)+'">Unload &amp; Update</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UNLOAD_UPDATE)+'" aria-label="Unload & Update: '+attr(TOOLTIP_UNLOAD_UPDATE)+'" disabled>Unload &amp; Update</button>';
var installUpgradeLabel=m.installed===true?'Upgrade':'Installieren',installUpgradeTooltip=m.installed===true?TOOLTIP_UPGRADE:'Modulinstallation durchführen';
actions+=ug.available?'<button class="button is-warning is-light is-small js-upgrade" type="button" title="'+attr(installUpgradeTooltip)+'" aria-label="'+attr(installUpgradeLabel)+': '+attr(installUpgradeTooltip)+'" data-component="'+attr(abbr)+'">'+esc(installUpgradeLabel)+'</button>':'<button class="button is-light is-small" type="button" title="'+attr(installUpgradeTooltip)+'" aria-label="'+attr(installUpgradeLabel)+': '+attr(installUpgradeTooltip)+'" disabled>'+esc(installUpgradeLabel)+'</button>';
if(specials.length){
var isOpen=expandedSpecials[abbr]===true;
actions+='<button class="button is-link is-light is-small js-specials" type="button" title="Sonderladeroutinen '+(isOpen?'einklappen':'aufklappen')+'" aria-expanded="'+(isOpen?'true':'false')+'" data-component="'+attr(abbr)+'"><span class="special-toggle-arrow" aria-hidden="true">'+(isOpen?'&#9652;':'&#9662;')+'</span>Sonderladeroutinen ('+specials.length+')</button>';
}
actions+='<button class="button is-info is-light is-small js-details" type="button" data-component="'+attr(abbr)+'">Details</button></div>';
rows.push('<tr class="module-row"><td>'+moduleHtml+'</td><td>'+status+'</td><td>'+versionHtml+'</td><td>'+fmtDate(m.lastUpdate)+'</td><td>'+ds+'</td><td>'+db+'</td><td class="action-cell">'+actions+'</td></tr>');
if(specials.length&&expandedSpecials[abbr]===true){rows.push(renderSpecialRoutinesRow(m,specials));}
}
el.moduleTbody.innerHTML=rows.length?rows.join(''):'<tr><td colspan="7" class="muted">Keine Module gefunden.</td></tr>';
var unassignedCount=renderUnassignedSpecials();
el.moduleCount.textContent=shown+' von '+modules.length+' Modulen angezeigt'+(unassignedCount?' · '+unassignedCount+' nicht zugeordnete Sonderladeroutine'+(unassignedCount===1?'':'n'):'');
bindButtons();
}
function bindButtons(){
var bs=document.querySelectorAll('.js-complete');for(var i=0;i<bs.length;i++){bs[i].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),completePlan,'Unload & Update');};}
bs=document.querySelectorAll('.js-unload');for(var j=0;j<bs.length;j++){bs[j].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),unloadPlan,'Unload');};}
bs=document.querySelectorAll('.js-updateonly');for(var k=0;k<bs.length;k++){bs[k].onclick=function(){startPlan(findModule(this.getAttribute('data-component')),updateOnlyPlan,'Update');};}
bs=document.querySelectorAll('.js-upgrade');for(var l=0;l<bs.length;l++){bs[l].onclick=function(){startUpgrade(findModule(this.getAttribute('data-component')));};}
bs=document.querySelectorAll('.js-specials');for(var n=0;n<bs.length;n++){bs[n].onclick=function(){var abbr=this.getAttribute('data-component');expandedSpecials[abbr]=expandedSpecials[abbr]!==true;renderModules();};}
bs=document.querySelectorAll('.js-special-start');for(var p=0;p<bs.length;p++){bs[p].onclick=function(){startSpecialConnector(findModule(this.getAttribute('data-component')),this.getAttribute('data-special-id'));};}
bs=document.querySelectorAll('.js-unassigned-special-start');for(var u=0;u<bs.length;u++){bs[u].onclick=function(){startUnassignedSpecialConnector(this.getAttribute('data-special-id'));};}
bs=document.querySelectorAll('.js-details');for(var m=0;m<bs.length;m++){bs[m].onclick=function(){showDetails(findModule(this.getAttribute('data-component')));};}
}
function findModule(abbr){for(var i=0;i<modules.length;i++){if(key(modules[i])===abbr){return modules[i];}}return null;}
function renderPlan(title,p){var h='<div class="box"><h3>'+esc(title)+'</h3><div class="plan-grid">';h+='<div class="muted">verfügbar</div><div>'+(p.available?badge('ja','ok'):badge('nein','warn'))+'</div>';h+='<div class="muted">gesucht</div><div>'+code(p.searched||'-')+'</div>';h+='<div class="muted">Job</div><div>'+(p.job&&p.job.id?code(p.job.id):'<span class="muted">-</span>')+'</div>';h+='<div class="muted">Start</div><div>'+(p.endpoint?code(p.endpoint):'<span class="muted">-</span>')+'</div>';h+='<div class="muted">Erklärung</div><div>'+esc(p.reason||'-')+'</div></div></div>';return h;}
function showDetails(m){if(!m){return;}var abbr=key(m),arr=jobs(m),vi=versionInfo(m),cp=completePlan(m),ul=unloadPlan(m),uo=updateOnlyPlan(m),ug=upgradePlan(m),h='';el.detailsTitle.textContent='Details: '+(m.name||abbr);h+='<div class="details-grid"><div class="box"><h3>Modul</h3><div class="kv"><div class="k">Name</div><div>'+esc(m.name||'-')+'</div><div class="k">Kürzel</div><div>'+code(abbr||'-')+'</div><div class="k">Systeminfo-ID</div><div>'+esc(m.systeminfoId||'-')+'</div><div class="k">Installiert</div><div>'+(m.installed===true?'ja':'nein')+'</div><div class="k">Installierte Version</div><div>'+versionValue(vi.installed)+'</div><div class="k">Verfügbare Version</div><div>'+versionValue(vi.target)+'</div><div class="k">Datenquelle</div><div>'+esc(displayDataSource(m))+'</div><div class="k">DB verbunden</div><div>'+(m.databaseConnected===true?'ja':(m.databaseConnected===false?'nein':'unbekannt'))+'</div><div class="k">Letztes Update</div><div>'+fmtDate(m.lastUpdate)+'</div></div></div><div>';h+=renderPlan('Unload',ul);h+=renderPlan('Update',uo);h+=renderPlan('Unload & Update',cp);var iuLabel=ug.actionLabel||'Upgrade';h+='<div class="box"><h3>'+esc(iuLabel)+'</h3><div class="plan-grid"><div class="muted">Bezeichnung</div><div>'+(m.installed===true?'Modulaktualisierung':'Modulinstallation')+'</div><div class="muted">verfügbar</div><div>'+(ug.available?badge('ja','ok'):badge('nein','warn'))+'</div><div class="muted">Upgradebedarf</div><div>'+(m.installed===true?(m.needsUpgrade===true?badge('laut /etl/list ja','warn'):badge('laut /etl/list nein','neutral')):'<span class="muted">nicht installiert</span>')+'</div><div class="muted">Start</div><div>'+code(ug.endpoint)+'</div><div class="muted">technischer Job</div><div>'+(ug.job&&ug.job.id?code(ug.job.id):'<span class="muted">nicht gefunden / nicht benötigt</span>')+'</div><div class="muted">Erklärung</div><div>'+esc(ug.reason)+'</div></div></div></div></div>';h+='<div class="box"><h3>Technische Jobs laut REST-Schnittstelle</h3>';if(jobErr(m)){h+='<div class="message error">Jobliste konnte nicht geladen werden: '+esc(jobErr(m))+'</div>';}else if(arr.length===0){h+='<div class="muted">Für dieses Modul wurden keine technischen Jobs zurückgeliefert.</div>';}else{h+='<table class="table is-fullwidth is-striped"><thead><tr><th>Job-ID</th><th>Name</th><th>Typ</th></tr></thead><tbody>';for(var i=0;i<arr.length;i++){h+='<tr><td>'+code(arr[i].id||'-')+'</td><td>'+esc(arr[i].name||'-')+'</td><td>'+esc(arr[i].type||'-')+'</td></tr>';}h+='</tbody></table>';}h+='</div><details class="box"><summary>JSON dieses Moduls anzeigen</summary><pre class="json">'+esc(JSON.stringify(m,null,2))+'</pre></details><details class="box"><summary>Komplette JSON-Liste aus /etl/list anzeigen</summary><pre class="json">'+esc(JSON.stringify(modules,null,2))+'</pre></details><details class="box"><summary>JSON der technischen Jobs dieses Moduls anzeigen</summary><pre class="json">'+esc(JSON.stringify(arr,null,2))+'</pre></details>';el.detailsBody.innerHTML=h;el.detailsPanel.classList.remove('hidden');el.detailsPanel.scrollIntoView({behavior:'smooth',block:'start'});}
function loadModules(){hideMsg();el.moduleTbody.innerHTML='<tr><td colspan="7" class="muted">Module werden geladen...</td></tr>';el.moduleCount.textContent='';var installedPromise=fetchJson(API_BASE+'/list');var installablesPromise=fetchJson(API_BASE+'/installables/list').catch(function(e){appendLog('Hinweis: /installables/list konnte nicht geladen werden: '+(e.message||String(e)));return [];});return Promise.all([installedPromise,installablesPromise]).then(function(results){var data=results[0],installables=results[1];if(!Array.isArray(data)){throw new Error('Unerwartete Antwort von /etl/list');}if(!Array.isArray(installables)){installables=[];}modules=mergeModuleLists(data,installables);jobsByComponent={};jobErrorsByComponent={};renderModules();renderVersionDiagnostics();return loadAllJobs().then(function(){renderModules();renderVersionDiagnostics();msg('Module und Jobinformationen wurden geladen.','ok');});}).catch(function(e){modules=[];jobsByComponent={};jobErrorsByComponent={};el.moduleTbody.innerHTML='<tr><td colspan="7" class="muted">Module konnten nicht geladen werden.</td></tr>';msg(e.message||String(e),'error');});}
function loadAllJobs(){var ps=[];for(var i=0;i<modules.length;i++){ps.push(loadJobList(modules[i]));}return Promise.all(ps);}
function loadJobList(m){var abbr=key(m);if(!abbr){return Promise.resolve();}return fetchJson(API_BASE+'/job/list/'+encodeURIComponent(abbr)).then(function(data){if(Array.isArray(data)){jobsByComponent[abbr]=data;}else{jobsByComponent[abbr]=[];jobErrorsByComponent[abbr]='Unerwartete Antwort von /job/list/'+abbr;}}).catch(function(e){jobsByComponent[abbr]=[];jobErrorsByComponent[abbr]=e.message||String(e);});}
function loadModules(options){
var keepTable=!!(options&&options.keepTable),preserveRunPosition=!!(options&&options.preserveRunPosition),runAnchor=preserveRunPosition?runViewportAnchor():null,generation=++moduleLoadGeneration;
hideMsg();
if(!keepTable){el.moduleTbody.innerHTML='<tr><td colspan="7" class="muted">Module und Jobinformationen werden geladen...</td></tr>';el.moduleCount.textContent='';}
var installables=[];
return fetchJson(API_BASE+'/installables/list').catch(function(e){appendLog('Hinweis: /installables/list konnte nicht geladen werden: '+(e.message||String(e)));return [];}).then(function(data){
installables=Array.isArray(data)?data:[];
return fetchJson(API_BASE+'/list');
}).then(function(data){
if(generation!==moduleLoadGeneration){return;}
if(!Array.isArray(data)){throw new Error('Unerwartete Antwort von /etl/list');}
rememberSpecialConnectors(data);
modules=mergeModuleLists(data,installables);
jobsByComponent={};jobErrorsByComponent={};
renderVersionDiagnostics();
return loadAllJobs().then(function(){
if(generation!==moduleLoadGeneration){return;}
renderModules();restoreRunViewport(runAnchor);renderVersionDiagnostics();msg('Module und Jobinformationen wurden geladen.','ok');
});
}).catch(function(e){
if(generation!==moduleLoadGeneration){return;}
modules=[];jobsByComponent={};jobErrorsByComponent={};
el.moduleTbody.innerHTML='<tr><td colspan="7" class="muted">Module konnten nicht geladen werden.</td></tr>';
msg(e.message||String(e),'error');
});
}
function loadAllJobs(){
var nextIndex=0,workers=[],workerCount=Math.min(4,modules.length);
function worker(){
if(nextIndex>=modules.length){return Promise.resolve();}
var m=modules[nextIndex++];
return loadJobList(m).then(worker);
}
for(var i=0;i<workerCount;i++){workers.push(worker());}
return Promise.all(workers);
}
function loadJobList(m){
var abbr=key(m);if(!abbr){return Promise.resolve();}
var url=API_BASE+'/job/list/'+encodeURIComponent(abbr);
function request(attempt){
return fetchJson(url).then(function(data){
if(Array.isArray(data)){jobsByComponent[abbr]=data;delete jobErrorsByComponent[abbr];return;}
throw new Error('Unerwartete Antwort von /job/list/'+abbr);
}).catch(function(e){
if(attempt<1){return new Promise(function(resolve){setTimeout(resolve,180);}).then(function(){return request(attempt+1);});}
jobsByComponent[abbr]=[];jobErrorsByComponent[abbr]=e.message||String(e);
});
}
return request(0);
}
function versionDiagValue(v,emptyText){return isDeliveredVersion(v)?esc(v):'<span class="muted">'+esc(emptyText||'nicht geliefert')+'</span>';}
function renderVersionDiagnostics(){if(!el.diagnosticsVersions){return;}if(!modules||modules.length===0){el.diagnosticsVersions.innerHTML='<div class="muted">Module sind noch nicht geladen.</div>';return;}var h='<table class="table is-fullwidth is-striped diagnostics-table"><thead><tr><th>Modul</th><th>Installierte Version</th><th>Verfügbare Version</th></tr></thead><tbody>';for(var i=0;i<modules.length;i++){var m=modules[i],abbr=key(m),vi=versionInfo(m);h+='<tr><td><strong>'+esc(m.name||abbr||'-')+'</strong><div class="small muted">Kürzel: '+esc(abbr||'-')+' / Systeminfo-ID: '+esc(m.systeminfoId||'-')+'</div></td>';h+='<td><div>REST / JSON: '+versionDiagValue(vi.jsonInstalled,'nicht geliefert')+'</div><div>db_version: '+versionDiagValue(vi.dbVersion,'kein Eintrag gefunden')+'</div><div><strong>verwendet:</strong> '+versionDiagValue(vi.installed,'nicht geliefert')+'</div></td>';h+='<td><div>REST / JSON: '+versionDiagValue(vi.jsonTarget,'nicht geliefert')+'</div><div>Modul-XML: '+versionDiagValue(vi.xmlVersion,'nicht gefunden')+'</div><div class="small muted">XML-Pfad: '+(vi.xmlPath?code(vi.xmlPath):'<span class="muted">nicht ermittelbar</span>')+'</div><div>VERSION-Datei: '+versionDiagValue(vi.versionFileVersion,'nicht gefunden')+'</div><div class="small muted">VERSION-Pfad: '+(vi.versionFilePath?code(vi.versionFilePath):'<span class="muted">nicht ermittelbar</span>')+'</div><div><strong>verwendet:</strong> '+versionDiagValue(vi.target,'nicht geliefert')+'</div></td></tr>';}
@ -799,9 +1092,37 @@ function restHint(label,errorText){var t=String(errorText||'');if(t.indexOf('HTT @@ -799,9 +1092,37 @@ function restHint(label,errorText){var t=String(errorText||'');if(t.indexOf('HTT
function diagRow(label,state,detail,hint){var type=state==='ok'?'ok':(state==='error'?'error':(state==='skip'?'info':'warn'));return '<div class="diag-status-row"><div>'+code(label)+'</div><div>'+badge(state==='ok'?'ok':(state==='skip'?'übersprungen':(state==='error'?'Fehler':'Hinweis')),type)+'</div><div>'+esc(detail||'')+(hint?'<div class="diag-hint">'+esc(hint)+'</div>':'')+'</div></div>';}
function runDiagnostics(){renderVersionDiagnostics();if(!el.diagnosticsRest){return;}el.diagnosticsRest.innerHTML='<div class="muted">REST-Schnittstellen werden geprüft...</div>';var checks=[];checks.push({label:'/etl/list',url:API_BASE+'/list'});checks.push({label:'/etl/installables/list',url:API_BASE+'/installables/list'});checks.push({label:'/running',url:API_BASE+'/running'});for(var i=0;i<modules.length;i++){var abbr=key(modules[i]);if(abbr){checks.push({label:'/job/list/'+abbr,url:API_BASE+'/job/list/'+encodeURIComponent(abbr)});}}if(currentExecutionId){checks.push({label:'/jobExecutionStatus/'+currentExecutionId,url:API_BASE+'/jobExecutionStatus/'+encodeURIComponent(currentExecutionId)});checks.push({label:'/jobLog/'+currentExecutionId,url:API_BASE+'/jobLog/'+encodeURIComponent(currentExecutionId)});}else{checks.push({label:'/jobExecutionStatus/{id}',skip:true,detail:'Keine aktuelle JobExecutionId bekannt.'});checks.push({label:'/jobLog/{id}',skip:true,detail:'Keine aktuelle JobExecutionId bekannt.'});}var results=[];function next(idx){if(idx>=checks.length){el.diagnosticsRest.innerHTML=results.join('');return Promise.resolve();}var c=checks[idx];if(c.skip){results.push(diagRow(c.label,'skip',c.detail,''));el.diagnosticsRest.innerHTML=results.join('');return next(idx+1);}return fetchText(c.url).then(function(t){var detail='erreichbar';if(t&&t.length){detail+='; Antwortlänge '+t.length+' Zeichen';}results.push(diagRow(c.label,'ok',detail,''));el.diagnosticsRest.innerHTML=results.join('');}).catch(function(e){var text=e.message||String(e);results.push(diagRow(c.label,'error',text,restHint(c.label,text)));el.diagnosticsRest.innerHTML=results.join('');}).then(function(){return next(idx+1);});}return next(0);}
function copyFromElement(id){var node=document.getElementById(id);if(!node){return;}var text=node.value!==undefined?node.value:node.textContent;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(text).then(function(){msg('Text wurde in die Zwischenablage kopiert.','ok');}).catch(function(){node.focus();if(node.select){node.select();}msg('Text markieren und manuell kopieren.','warn');});}else{node.focus();if(node.select){node.select();}msg('Text markieren und manuell kopieren.','warn');}}
function startUnassignedSpecialConnector(connectorId){
var wanted=String(connectorId||''),all=unassignedSpecialConnectors(),c=null;
for(var i=0;i<all.length;i++){if(String(all[i].id||'')===wanted){c=all[i];break;}}
if(!c){msg('Die ausgewählte Sonderladeroutine wurde nicht mehr gefunden. Bitte die Komponentenliste aktualisieren.','error');return;}
var id=String(c.id||''),owner=inferSpecialOwnerKey(c,c._ownerKey||''),db=unassignedExecutionDb(c),label=c.name||id;
var extra=confirmRow('Sonderladeroutine',label)+confirmRow('Zuordnung',owner||'kein Modul aus /etl/list','code-value')+confirmRow('Datenbank',db,'code-value')+confirmRow('Job-ID',id,'code-value');
if(c.description){extra+=confirmRow('Beschreibung',c.description);}
var body=actionConfirmBody('Sonderladeroutine',owner?('Nicht zugeordnet ('+owner+')'):'Nicht zugeordnet',owner,'Die Sonderladeroutine ist in der ETL-Jobdatenbank vorhanden, konnte aber keinem aktuell gelieferten Modul zugeordnet werden. Sie wird direkt über ihre Job-ID gestartet. Bitte nur starten, wenn aktuell kein anderer ETL-Lauf aktiv ist.',extra);
askConfirm('Sonderladeroutine starten?',body,'Sonderladeroutine starten').then(function(ok){if(!ok){return;}startJob(API_BASE+'/job/execute/'+encodeURIComponent(db)+'/'+encodeURIComponent(id),'Sonderladeroutine '+label+' ('+id+')');});
}
function startSpecialConnector(m,connectorId){if(!m){return;}var c=findSpecialConnector(m,connectorId);if(!c){msg('Die ausgewählte Sonderladeroutine wurde nicht mehr gefunden. Bitte die Komponentenliste aktualisieren.','error');return;}var id=String(c.id||''),db=specialSourceDb(m,c),moduleName=m.name||key(m),label=c.name||id,endpoint='/job/execute/'+db+'/'+id;var extra=confirmRow('Sonderladeroutine',label)+confirmRow('Datenquelle',db,'code-value')+confirmRow('Job-ID',id,'code-value');if(c.description){extra+=confirmRow('Beschreibung',c.description);}var body=actionConfirmBody('Sonderladeroutine',moduleName,key(m),'Die Sonderladeroutine wird unabhängig von der normalen Modul-Laderoutine gestartet. Bitte nur starten, wenn aktuell kein anderer ETL-Lauf aktiv ist.',extra);askConfirm('Sonderladeroutine starten?',body,'Sonderladeroutine starten').then(function(ok){if(!ok){return;}startJob(API_BASE+endpoint,'Sonderladeroutine '+label+' ('+id+')');});}
function reloadJobsToDb(){
var body='<div class="content">'+confirmRow('Aktion','Module neu einlesen','action')+'<div class="notification is-warning is-light confirm-hint">Die Modul- und ETL-Jobdefinitionen werden aus dem Dateisystem neu in die Datenbank eingelesen. Anschließend wird die Übersicht automatisch aktualisiert.</div></div>';
askConfirm('Module neu einlesen?',body,'Neu einlesen').then(function(ok){
if(!ok){return;}
el.reloadModulesBtn.disabled=true;
hideMsg();
appendLog('Module neu einlesen: '+API_BASE+'/job/writeJobsToDb');
fetchJson(API_BASE+'/job/writeJobsToDb',{method:'POST'}).then(function(result){
appendLog('Antwort Module neu einlesen: '+stringify(result));
if(result===false||String(result).toLowerCase()==='false'){throw new Error('Der Server hat das Neueinlesen mit false beantwortet.');}
expandedSpecials={};
clearSpecialConnectorCache();
window.location.reload();
return null;
}).catch(function(e){msg('Module konnten nicht neu eingelesen werden: '+(e.message||String(e)),'error');appendLog('Fehler beim Neueinlesen: '+(e.message||String(e)));el.reloadModulesBtn.disabled=false;});
});
}
function startPlan(m,planFn,label){if(!m){return;}var p=planFn(m);if(!p.available){msg(label+' ist nicht verfügbar: '+p.reason,'error');return;}var jobId=(p.job&&p.job.id)?p.job.id:'-';var moduleName=m.name||key(m);var body=actionConfirmBody(label,moduleName,key(m),'Der Vorgang kann länger dauern. Bitte nur starten, wenn aktuell kein anderer ETL-Lauf aktiv ist.');askConfirm(label+' starten?',body,label+' starten').then(function(ok){if(!ok){return;}startJob(API_BASE+p.endpoint,label+' '+moduleName+' ('+jobId+')');});}
function startUpgrade(m){if(!m){return;}var p=upgradePlan(m);var isInstall=m.installed!==true;var label=isInstall?'Installation':'Upgrade';if(!p.available){msg(label+' ist nicht verfügbar: '+p.reason,'error');return;}var abbr=key(m);var moduleName=m.name||abbr;var extra=confirmRow('Installierte Version',p.versionInfo.installed||'nicht geliefert','code-value')+confirmRow('Verfügbare Version',p.versionInfo.target||'nicht geliefert','code-value');var body=actionConfirmBody(label,moduleName,abbr,'Der Vorgang kann länger dauern. Bitte nur starten, wenn aktuell kein anderer ETL-/Installationslauf aktiv ist.',extra);askConfirm(isInstall?'Modul installieren?':'Upgrade starten?',body,label+' starten').then(function(ok){if(!ok){return;}startJob(API_BASE+p.endpoint,(isInstall?'Installieren ':'Upgrade ')+moduleName);});}
function startJob(url,label){hideMsg();setLog('', 'Neuer Lauf wird gestartet.');appendLog('Starte: '+label);appendLog(url);fetchJson(url,{method:'POST'}).then(function(result){var id=parseId(result);if(id===-1){msg('Der Server hat -1 zurückgeliefert. Es wurde kein neuer Job gestartet. Mögliche Ursachen: Ein anderer Job läuft bereits oder der Server konnte für diesen Endpunkt keinen passenden Startjob ermitteln.','error');appendLog('Start nicht angenommen: -1');return checkRunning(true);}if(id===null){msg('Startantwort konnte nicht als JobExecutionId interpretiert werden.','error');appendLog('Startantwort: '+stringify(result));return;}currentExecutionId=id;currentLogId=null;lastStatusPayload=null;currentJobLabel=label;logFailureByExecutionId[id]=false;setLog('Job gestartet. Log wird nach Abschluss automatisch geladen oder kann über „Log laden“ abgefragt werden.','JobExecutionId '+id);updateRun('gestartet');el.loadLogBtn.disabled=false;el.stopRunBtn.disabled=false;msg('Job gestartet. JobExecutionId: '+id,'ok');return syncRunningAfterStart(id,label).then(function(){startPolling();});}).catch(function(e){msg(e.message||String(e),'error');appendLog('Fehler beim Start: '+(e.message||String(e)));});}
function startJob(url,label){hideMsg();setLog('', 'Neuer Lauf wird gestartet.');appendLog('Starte: '+label);appendLog(url);fetchJson(url,{method:'POST'}).then(function(result){var id=parseId(result);if(id===-1){msg('Der Server hat -1 zurückgeliefert. Es wurde kein neuer Job gestartet. Mögliche Ursachen: Ein anderer Job läuft bereits oder der Server konnte für diesen Endpunkt keinen passenden Startjob ermitteln.','error');appendLog('Start nicht angenommen: -1');return checkRunning(true);}if(id===null){msg('Startantwort konnte nicht als JobExecutionId interpretiert werden.','error');appendLog('Startantwort: '+stringify(result));return;}currentExecutionId=id;currentLogId=null;lastStatusPayload=null;currentJobLabel=label;logFailureByExecutionId[id]=false;setLog('Job gestartet. Log wird nach Abschluss automatisch geladen oder kann über „Log laden“ abgefragt werden.','JobExecutionId '+id);updateRun('gestartet');el.loadLogBtn.disabled=false;el.stopRunBtn.disabled=false;msg('Job gestartet. JobExecutionId: '+id,'ok');scrollToRunArea();return syncRunningAfterStart(id,label).then(function(){startPolling();});}).catch(function(e){msg(e.message||String(e),'error');appendLog('Fehler beim Start: '+(e.message||String(e)));});}
function syncRunningAfterStart(startId,label){return new Promise(function(resolve){setTimeout(resolve,700);}).then(function(){return fetchJson(API_BASE+'/running');}).then(function(data){var runningId=parseId(data);if(runningId&&runningId>0&&runningId!==startId){appendLog('Hinweis: /running meldet laufende JobExecutionId '+runningId+' statt Startantwort '+startId+'. Verwende '+runningId+' für Status und Log.');currentExecutionId=runningId;}if(data&&typeof data==='object'){updateKnownIds(data);if(data.jobName){currentJobLabel=data.jobName;}else if(data.name){currentJobLabel=data.name;}}updateRun('gestartet');return Promise.resolve();}).catch(function(e){appendLog('Hinweis: /running konnte nach dem Start nicht zur ID-Prüfung gelesen werden: '+(e.message||String(e)));return Promise.resolve();});}
function parseId(r){if(typeof r==='number'){return r;}if(typeof r==='string'){var s=r.trim();if(/^-?\d+$/.test(s)){return parseInt(s,10);}try{return parseId(JSON.parse(s));}catch(e){return null;}}if(r&&typeof r==='object'){var keys=['jobExecutionId','id','executionId'];for(var i=0;i<keys.length;i++){if(r[keys[i]]!==undefined&&r[keys[i]]!==null){return parseId(r[keys[i]]);}}}return null;}
function stringify(v){if(typeof v==='string'){return v;}try{return JSON.stringify(v,null,2);}catch(e){return String(v);}}
@ -823,7 +1144,7 @@ function logIdCandidates(){var ids=[];if(lastStatusPayload&&typeof lastStatusPay @@ -823,7 +1144,7 @@ function logIdCandidates(){var ids=[];if(lastStatusPayload&&typeof lastStatusPay
function updateRun(s){el.runId.textContent=currentExecutionId||'-';el.runJob.textContent=currentJobLabel||'-';el.runStatus.textContent=s||'-';}
function startPolling(){if(pollTimer){clearInterval(pollTimer);}pollStatus();pollTimer=setInterval(pollStatus,4000);}
function stopPolling(){if(pollTimer){clearInterval(pollTimer);pollTimer=null;}}
function pollStatus(){if(!currentExecutionId){return Promise.resolve();}return fetchJson(API_BASE+'/jobExecutionStatus/'+encodeURIComponent(currentExecutionId)).then(function(data){updateKnownIds(data);renderStatus(data);var s=statusOf(data);if(doneStatus(s)){stopPolling();loadLog(false);loadModules();}}).catch(function(err){return fetchJson(API_BASE+'/jobExcecutionStatus/'+encodeURIComponent(currentExecutionId)).then(function(data){updateKnownIds(data);renderStatus(data);var s=statusOf(data);if(doneStatus(s)){stopPolling();loadLog(false);loadModules();}}).catch(function(){el.runStatus.textContent='Status konnte nicht gelesen werden: '+(err.message||String(err));});});}
function pollStatus(){if(!currentExecutionId){return Promise.resolve();}return fetchJson(API_BASE+'/jobExecutionStatus/'+encodeURIComponent(currentExecutionId)).then(function(data){updateKnownIds(data);renderStatus(data);var s=statusOf(data);if(doneStatus(s)){stopPolling();loadLog(false);loadModules({keepTable:true,preserveRunPosition:true});}}).catch(function(err){return fetchJson(API_BASE+'/jobExcecutionStatus/'+encodeURIComponent(currentExecutionId)).then(function(data){updateKnownIds(data);renderStatus(data);var s=statusOf(data);if(doneStatus(s)){stopPolling();loadLog(false);loadModules({keepTable:true,preserveRunPosition:true});}}).catch(function(){el.runStatus.textContent='Status konnte nicht gelesen werden: '+(err.message||String(err));});});}
function isUnknownStatus(s){var u=String(s||'').trim().toUpperCase();return u==='UNKNOWN'||u==='UNBEKANNT';}
function statusOf(d){
if(!d){return '';}
@ -840,7 +1161,7 @@ function renderStatus(data){var s=statusOf(data);el.runStatus.textContent=displa @@ -840,7 +1161,7 @@ function renderStatus(data){var s=statusOf(data);el.runStatus.textContent=displa
function loadLog(userRequested){if(!currentExecutionId&&!currentLogId){return Promise.resolve();}var candidates=logIdCandidates();if(candidates.length===0){return Promise.resolve();}var failureKey=candidates.join(',');if(logFailureByExecutionId[failureKey]&&userRequested!==true){return Promise.resolve();}el.logHint.textContent='Log wird geladen...';function tryCandidate(pos,errors){if(pos>=candidates.length){var text=errors.join('\n');logFailureByExecutionId[failureKey]=true;var hint='Log konnte für die bekannten IDs '+candidates.join(', ')+' nicht geladen werden.';el.logHint.textContent='Log nicht verfügbar.';if(userRequested===true){appendLog(hint);appendLog(text);appendLog('Hinweis: Der Log-Endpunkt verwendet je nach Backendstand eventuell eine andere ID als /jobExecutionStatus. Die Diagnose kann die aktuell bekannten REST-Antworten sichtbar machen.');}else{appendLog(hint+' Der Laufstatus wurde trotzdem abgefragt.');}return Promise.resolve();}var id=candidates[pos];return fetchJson(API_BASE+'/jobLog/'+encodeURIComponent(id)).then(function(data){currentLogId=id;logFailureByExecutionId[failureKey]=false;var t=extractLogText(data);if(t&&t.trim()){setLog(t, extractLogLabel(data,id));}else{setLog('(Der Log-Endpunkt hat keinen Logtext geliefert.)', extractLogLabel(data,id));}}).catch(function(e){errors.push('/jobLog/'+id+': '+(e.message||String(e)));return tryCandidate(pos+1,errors);});}
return tryCandidate(0,[]);}
function checkRunning(verbose){return fetchJson(API_BASE+'/running').then(function(data){var id=parseId(data);if(id&&id>0){if(verbose){appendLog('Antwort /running: '+stringify(data));}currentExecutionId=id;updateKnownIds(data);currentJobLabel=(data&&data.jobName)?data.jobName:((data&&data.name)?data.name:'laufender Job');logFailureByExecutionId[id]=false;el.loadLogBtn.disabled=false;el.stopRunBtn.disabled=false;updateRun('laufender Job gefunden');startPolling();}else{if(verbose){appendLog('Kein laufender Job laut /running.');}if(!currentExecutionId){currentLogId=null;lastStatusPayload=null;el.runStatus.textContent='Kein aktiver Lauf bekannt.';el.runId.textContent='-';el.runJob.textContent='-';if(el.progressBar.tagName&&el.progressBar.tagName.toLowerCase()==='progress'){el.progressBar.value=0;}else{el.progressBar.style.width='0%';}el.progressText.textContent='-';}}}).catch(function(e){appendLog('Running-Status konnte nicht gelesen werden: '+(e.message||String(e)));});}function stopRun(){if(!currentExecutionId){return;}var body='<div class="content">'+confirmRow('Aktion','Lauf stoppen','action')+confirmRow('JobExecutionId',currentExecutionId,'code-value')+'<div class="notification is-danger is-light confirm-hint">Der laufende Job wird zum Stoppen angefordert.</div></div>';askConfirm('Aktuellen Job stoppen?',body,'Job stoppen').then(function(ok){if(!ok){return;}fetchJson(API_BASE+'/stop/'+encodeURIComponent(currentExecutionId)).then(function(data){appendLog('Stop-Antwort: '+stringify(data));pollStatus();}).catch(function(e){msg('Job konnte nicht gestoppt werden: '+(e.message||String(e)),'error');});});}
el.reloadBtn.onclick=loadModules;el.searchInput.oninput=renderModules;el.filterSelect.onchange=renderModules;el.diagnosticsBtn.onclick=function(){el.diagnosticsPanel.classList.remove('hidden');runDiagnostics();el.diagnosticsPanel.scrollIntoView({behavior:'smooth',block:'start'});};el.refreshDiagnosticsBtn.onclick=runDiagnostics;el.closeDiagnosticsBtn.onclick=function(){el.diagnosticsPanel.classList.add('hidden');};document.addEventListener('click',function(ev){var t=ev.target;if(t&&t.classList&&t.classList.contains('js-copy-text')){copyFromElement(t.getAttribute('data-copy-target'));}});el.closeDetailsBtn.onclick=function(){el.detailsPanel.classList.add('hidden');};el.refreshRunBtn.onclick=function(){if(currentExecutionId){pollStatus();}else{checkRunning(false);}};el.stopRunBtn.onclick=stopRun;el.loadLogBtn.onclick=function(){loadLog(true);};el.clearLogBtn.onclick=function(){setLog('','Log geleert.');};
el.reloadModulesBtn.onclick=reloadJobsToDb;el.searchInput.oninput=renderModules;el.filterSelect.onchange=renderModules;el.diagnosticsBtn.onclick=function(){el.diagnosticsPanel.classList.remove('hidden');runDiagnostics();el.diagnosticsPanel.scrollIntoView({behavior:'smooth',block:'start'});};el.refreshDiagnosticsBtn.onclick=runDiagnostics;el.closeDiagnosticsBtn.onclick=function(){el.diagnosticsPanel.classList.add('hidden');};document.addEventListener('click',function(ev){var t=ev.target;if(t&&t.classList&&t.classList.contains('js-copy-text')){copyFromElement(t.getAttribute('data-copy-target'));}});el.closeDetailsBtn.onclick=function(){el.detailsPanel.classList.add('hidden');};el.refreshRunBtn.onclick=function(){if(currentExecutionId){pollStatus();}else{checkRunning(false);}};el.stopRunBtn.onclick=stopRun;el.loadLogBtn.onclick=function(){loadLog(true);};el.clearLogBtn.onclick=function(){setLog('','Log geleert.');};
loadModules().then(function(){checkRunning(false);});
})();
</script>

32
superx/edit/kern/sx_repository_list.jsp

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<%@ taglib uri="/WEB-INF/dbforms.tld" prefix="db" %>
<%@page pageEncoding="utf-8" contentType="text/html; charset=UTF-8" %>
<html>
<!--Automatically generated by module_scripts_create.x (SuperX)-->
<!--Automatically generated by module_scripts_create.x (SuperX) for Module kern-->
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
@ -49,12 +49,10 @@ if(mandantobject != null) @@ -49,12 +49,10 @@ if(mandantobject != null)
//der superx-Default-Mandant ist bei dbforms ein Leerstring
if(mandantenid.equals("default"))
mandantenid="";
String db_form_name="sx_repository";
String db_form_name="sx_repository_list";
String erlaubt="0";
%>
<%@ include file="/edit/check_authentication.inc" %>
<%
if(erlaubt.equals("0"))
{
@ -74,7 +72,6 @@ keyfieldsused=kf.toString(); @@ -74,7 +72,6 @@ keyfieldsused=kf.toString();
<p align="right">
<a target="_parent" href="http://www.superx-projekt.de"><img border="0" alt="Logo" src="../../images/logo.gif"></a>
<br><% if(!mandantenid.equals("default") && !mandantenid.equals(""))
out.println("<strong>Mandant: " + mandantenid +"</strong>");
%></p>
@ -90,6 +87,16 @@ if(sachgebiete_id != null && !sachgebiete_id.equals("null")){ @@ -90,6 +87,16 @@ if(sachgebiete_id != null && !sachgebiete_id.equals("null")){
filter +="sachgebiete_id="+sachgebiete_id;
}
%>
<%
String art = request.getParameter("art");
if(art != null && !art.equals("null")){
if (!filter.isEmpty()) {
filter += ",";
}
filter +=",art="+art;
}
%>
<db:dbform tableName="sx_repository" maxRows="*" autoUpdate="false" captionResource="true" multipart="false" followUp="/edit/kern/sx_repository_list.jsp" filter="<%= filter %>" orderBy="id"
dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/><db:header>
<table class="dbform_tab" border="1" >
@ -99,7 +106,11 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame @@ -99,7 +106,11 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame
<db:message key="Tid"/></th><th align="left">
<db:message key="Variablenname"/></th><th align="left">
<db:message key="Beschriftung (kurz)"/></th><th align="left">
<db:message key="Art der Variable"/></th><th>Details</th>
<db:message key="Art der Variable"/></th><th align="left">
<db:message key="Inhalt der Variable"/></th><th align="left">
<db:message key="Sachgebiet"/></th><th align="left">
<db:message key="Sortierschl&uuml;ssel 1"/></th><th align="left">
<db:message key="Aktiv (1,0)"/></th><th>Details</th>
</tr>
</db:header>
<db:errors/>
@ -107,11 +118,16 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame @@ -107,11 +118,16 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame
<tr>
<td>
<input type="hidden" name="sachgebiete_id" value="<%= sachgebiete_id %>" />
<input type="hidden" name="art" value="<%= art %>" />
</td><td>
<db:label nullFieldValue="" styleClass="clsInputStyle" fieldName="tid"/></td><td align="left">
<db:textField nullFieldValue="" styleClass="clsInputStyle" maxlength="200" size="30" fieldName="id"/></td><td align="left">
<db:textField nullFieldValue="" styleClass="clsInputStyle" maxlength="200" size="30" fieldName="caption"/></td><td align="left">
<db:textField nullFieldValue="" styleClass="clsInputStyle" maxlength="200" size="30" fieldName="art"/></td><td><%
<db:textField nullFieldValue="" styleClass="clsInputStyle" maxlength="200" size="30" fieldName="art"/></td><td style="max-width: 300px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; line-height: 1.2em; max-height: 3.6em;"><db:label nullFieldValue="" fieldName="content" />
</td><td align="left"> <db:select fieldName="sachgebiete_id" customEntry=",,true"><% sql = "select tid, name from sachgebiete order by name;"; %><db:queryData name="sx_repository_sachgebiete60" query="<%= sql %>" dbConnectionName="<%= mandantenid %>" /></db:select></td><td align="left">
<db:textField nullFieldValue="" styleClass="clsInputStyle" size="6" fieldName="sort1"/></td><td align="left">
<db:textField nullFieldValue="" styleClass="clsInputStyle" size="6" fieldName="aktiv"/></td><td><%
if(currentRow_sx_repository != null)
{
%><a target="details" title="Details" href="<%= response.encodeURL("/superx/edit/kern/sx_repository_edit.jsp") %>?sachgebiete_id=<%= sachgebiete_id %>&tid=<db:label nullFieldValue="" pattern="######" fieldName="tid"/>" onClick="javascript:neu=window.open('<%= response.encodeURL("/superx/edit/kern/sx_repository_edit.jsp") %>?sachgebiete_id=<%= sachgebiete_id %>&tid=<db:label nullFieldValue="" pattern="######" fieldName="tid"/>','_blank','directories=no,location=no,menubar=no,toolbar=no,scrollbars=yes,resizable=yes,width=800,height=600'); return false;"><img border="0" alt="Details" src="../../images/pencil.svg"></a><%
@ -124,4 +140,4 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame @@ -124,4 +140,4 @@ dbConnectionName="<%= mandantenid %>" ><input type="hidden" name="${_csrf.parame
<db:footer><tr></tr>
</table>
</db:footer></db:dbform></body>
</html>
</html>

Loading…
Cancel
Save