Browse Source

ETL-Manager zu Komponentenverwaltung und einige Anpassungen

master
Andre Knieschewski 9 hours ago
parent
commit
3a4f3b5511
  1. 139
      superx/edit/kern/superx_komponentenverwaltung.jsp
  2. 2
      superx/xml/nd_templates.xsl

139
superx/edit/kern/etl_manager.jsp → superx/edit/kern/superx_komponentenverwaltung.jsp

@ -549,7 +549,7 @@ try { @@ -549,7 +549,7 @@ try {
<html lang="de">
<head>
<meta charset="UTF-8">
<title>SuperX ETL Manager</title>
<title>SuperX Komponentenverwaltung</title>
<link rel="stylesheet" href="/superx/style/bulma.css">
<style>
body{background:#f5f7fa}
@ -601,12 +601,35 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p @@ -601,12 +601,35 @@ pre.json{white-space:pre-wrap;word-break:break-word;background:#f2f5f8;border:1p
.diag-textarea{width:100%;min-height:120px;font-family:Consolas,Monaco,"Courier New",monospace;font-size:.8rem;background:#111827;color:#e5e7eb;border:0;border-radius:8px;padding:.75rem;white-space:pre}
.diag-actions{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin:.5rem 0}
.confirm-body p{margin-bottom:.65rem}
.confirm-highlight{font-size:1.05rem}
.confirm-row{margin-bottom:.75rem}
.confirm-label{display:block;font-size:.78rem;line-height:1.2;color:#697386;margin-bottom:.15rem;text-transform:none}
.confirm-value{display:block;font-size:1.08rem;line-height:1.25;font-weight:700;color:#273647}
.confirm-value.action{font-size:1.25rem;color:#164866}
.confirm-value.code-value{font-family:Consolas,Monaco,"Courier New",monospace;font-size:1rem}
.confirm-hint{margin-top:.75rem}
</style>
</head>
<body>
<div class="page">
<div class="header"><h1>SuperX ETL Manager</h1><p>Aufgeräumte Modulübersicht für den täglichen Betrieb. Technische Details, Jobauswahl und JSON-Rohdaten sind über den Detailbereich erreichbar.</p></div>
<div class="header"><h1>SuperX Komponentenverwaltung</h1><p>Aufgeräumte Modulübersicht für den täglichen Betrieb. Technische Details, Jobauswahl und JSON-Rohdaten sind über den Detailbereich erreichbar.</p></div>
<div id="message" class="message hidden"></div>
<div id="confirmModal" class="modal" aria-hidden="true">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p id="confirmTitle" class="modal-card-title">Aktion bestätigen</p>
<button id="confirmCloseBtn" class="delete" aria-label="Schließen" type="button"></button>
</header>
<section id="confirmBody" class="modal-card-body confirm-body"></section>
<footer class="modal-card-foot" style="justify-content:flex-end;">
<button id="confirmCancelBtn" class="button is-light" type="button">Abbrechen</button>
<button id="confirmOkBtn" class="button is-primary" type="button">Starten</button>
</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 id="diagnosticsPanel" class="box diagnostics-panel hidden">
@ -673,10 +696,12 @@ var modules=[]; @@ -673,10 +696,12 @@ var modules=[];
var jobsByComponent={};
var jobErrorsByComponent={};
var currentExecutionId=null;
var currentLogId=null;
var lastStatusPayload=null;
var currentJobLabel=null;
var pollTimer=null;
var logFailureByExecutionId={};
var el={message:document.getElementById('message'),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'),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')};
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>';}
@ -684,11 +709,61 @@ function badge(t,type){var cls='is-light';if(type==='ok'){cls='is-success is-lig @@ -684,11 +709,61 @@ function badge(t,type){var cls='is-light';if(type==='ok'){cls='is-success is-lig
function versionValue(v){if(v===null||v===undefined||v===''){return '<span class="muted">nicht geliefert</span>';}return code(v);}
function msg(t,type){el.message.textContent=t;el.message.className='message'+(type?' '+type:'');el.message.classList.remove('hidden');}
function hideMsg(){el.message.classList.add('hidden');}
function askConfirm(title,bodyHtml,okLabel){
if(!el.confirmModal){return Promise.resolve(confirm(title));}
return new Promise(function(resolve){
var done=false;
function close(result){
if(done){return;}
done=true;
el.confirmModal.classList.remove('is-active');
el.confirmModal.setAttribute('aria-hidden','true');
el.confirmOkBtn.onclick=null;
el.confirmCancelBtn.onclick=null;
el.confirmCloseBtn.onclick=null;
document.removeEventListener('keydown',onKey);
resolve(result===true);
}
function onKey(ev){if(ev.key==='Escape'){close(false);}}
el.confirmTitle.textContent=title||'Aktion bestätigen';
el.confirmBody.innerHTML=bodyHtml||'';
el.confirmOkBtn.textContent=okLabel||'Starten';
el.confirmModal.classList.add('is-active');
el.confirmModal.setAttribute('aria-hidden','false');
el.confirmOkBtn.onclick=function(){close(true);};
el.confirmCancelBtn.onclick=function(){close(false);};
el.confirmCloseBtn.onclick=function(){close(false);};
document.addEventListener('keydown',onKey);
setTimeout(function(){el.confirmOkBtn.focus();},0);
});
}
function confirmRow(label,value,extraClass){
return '<div class="confirm-row"><span class="confirm-label">'+esc(label)+'</span><span class="confirm-value '+(extraClass||'')+'">'+esc(value||'-')+'</span></div>';
}
function actionConfirmBody(actionLabel,moduleName,abbr,hint,extraHtml){
return '<div class="content">'
+confirmRow('Aktion',actionLabel,'action')
+confirmRow('Modul',moduleName||abbr||'','')
+confirmRow('Kürzel',abbr||'','code-value')
+(extraHtml||'')
+'<div class="notification is-warning is-light confirm-hint">'+esc(hint||'Der Vorgang kann länger dauern. Bitte nur starten, wenn aktuell kein anderer ETL-/Installationslauf aktiv ist.')+'</div>'
+'</div>';
}
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 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||'');}
function firstValue(o,keys){if(!o){return null;}for(var i=0;i<keys.length;i++){var v=o[keys[i]];if(v!==undefined&&v!==null&&String(v).trim()!==''){return v;}}return null;}
function cleanModuleKey(v){if(v===undefined||v===null){return '';}var s=String(v).trim();if(!s){return '';}s=s.replace(/\\/g,'/');var parts=s.split('/').filter(function(p){return p&&p.length;});if(parts.length&&parts[parts.length-1].toLowerCase()==='version'&&parts.length>1){s=parts[parts.length-2];}else if(parts.length){s=parts[parts.length-1];}s=s.replace(/\.(xml|conf|properties|txt)$/i,'');s=s.toLowerCase();s=s.replace(/[^a-z0-9_]/g,'');return s;}
function installableKey(m){if(m===undefined||m===null){return '';}if(typeof m!=='object'){return cleanModuleKey(m);}var directKeys=['abbreviation','moduleAbbreviation','module_abbreviation','module','dbModule','dbmodule','nameIntern','name_intern','moduleId','module_id','componentId','component_id','component','key','id'];for(var i=0;i<directKeys.length;i++){var cleaned=cleanModuleKey(m[directKeys[i]]);if(cleaned&&/[a-z]/.test(cleaned)){return cleaned;}}var pathKeys=['xmlPath','moduleXmlPath','module_xml_path','versionFilePath','version_file_path','confPath','confFile','path','file','directory','folder'];for(var j=0;j<pathKeys.length;j++){var p=cleanModuleKey(m[pathKeys[j]]);if(p&&/[a-z]/.test(p)){return p;}}var nameKeys=['name','title','caption'];for(var k=0;k<nameKeys.length;k++){var n=cleanModuleKey(m[nameKeys[k]]);if(n&&/[a-z]/.test(n)){return n;}}return '';}
function installableName(m,abbr){if(m===undefined||m===null){return abbr||'';}if(typeof m!=='object'){return String(m||abbr||'').trim();}return String(firstValue(m,['displayName','componentName','component_name','caption','title','label','name','description'])||abbr||'').trim();}
function installableTargetVersion(m){if(!m||typeof m!=='object'){return null;}return firstValue(m,['targetVersion','availableVersion','latestVersion','packageVersion','fileVersion','newVersion','upgradeVersion','version','target_version','available_version','latest_version','package_version','file_version','new_version','upgrade_version']);}
function mergeModuleLists(installedList,installableList){var byKey={},order=[];for(var i=0;i<installedList.length;i++){var im=installedList[i];var ik=cleanModuleKey(key(im));if(!ik){continue;}byKey[ik]=im;order.push(ik);}for(var j=0;j<installableList.length;j++){var fm=installableList[j];var fk=installableKey(fm);if(!fk||byKey[fk]){continue;}var item={abbreviation:fk,name:installableName(fm,fk),installed:false,databaseConnected:false,lastUpdate:null};var tv=installableTargetVersion(fm);if(tv!==undefined&&tv!==null&&String(tv).trim()!==''){item.targetVersion=String(tv).trim();}byKey[fk]=item;order.push(fk);}var result=[];for(var k=0;k<order.length;k++){if(byKey[order[k]]){result.push(byKey[order[k]]);}}return result;}
function notInstalledPlan(m,searched){return {available:false,job:null,sourceDatabase:null,endpoint:null,reason:'Das Modul ist noch nicht installiert. Zuerst die Modulinstallation ausführen.',searched:searched};}
function flagTrue(v){return v===true||v===1||String(v||'').toLowerCase()==='true'||String(v||'')==='1';}
function inferredJob(id,name){return {type:'INFERRED',name:name||id,id:id,inferred:true};}
function inferredPlan(m,id,reason,searched){var j=inferredJob(id,id);var db=sourceDb(m,j);return {available:true,job:j,sourceDatabase:db,endpoint:'/job/execute/'+db+'/'+id,reason:reason,searched:searched||id};}
function jobs(m){return jobsByComponent[key(m)]||[];}
function jobErr(m){return jobErrorsByComponent[key(m)]||null;}
function findJobById(arr,id){for(var i=0;i<arr.length;i++){if(String(arr[i].id||'')===id){return arr[i];}}return null;}
@ -703,17 +778,17 @@ var jsonInstalled=first(installedKeys);var dbVersion=dbFallback();var installed= @@ -703,17 +778,17 @@ var jsonInstalled=first(installedKeys);var dbVersion=dbFallback();var installed=
var jsonTarget=first(targetKeys);var ai=availableInfo();var xmlVersion=isDeliveredVersion(ai.xmlVersion)?String(ai.xmlVersion).trim():null;var versionFileVersion=isDeliveredVersion(ai.versionFileVersion)?String(ai.versionFileVersion).trim():null;var target=jsonTarget;var targetSource=jsonTarget?'json':'';if(!target&&xmlVersion){target=xmlVersion;targetSource='module_xml';}if(!target&&versionFileVersion){target=versionFileVersion;targetSource='version_file';}
return {installed:installed,target:target,raw:raw,installedSource:installedSource,targetSource:targetSource,jsonInstalled:jsonInstalled,dbVersion:dbVersion,jsonTarget:jsonTarget,xmlVersion:xmlVersion,xmlPath:ai.xmlPath||'',versionFileVersion:versionFileVersion,versionFilePath:ai.versionFilePath||''};}
function reasonForMissing(m,err,arr,text){if(err){return 'Die technische Jobliste konnte nicht geladen werden: '+err;}if(m.installed===false){return 'Das Modul ist laut Komponentenliste nicht installiert.';}if(arr.length===0){return 'Für dieses Modul wurden über /job/list/'+key(m)+' keine technischen Jobs zurückgeliefert.';}return 'In der technischen Jobliste wurde '+text;}
function completePlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unload_load_transform';var j=findJobById(arr,id)||findJobBySuffix(arr,'_unload_load_transform')||findJobContains(arr,'unload_load_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 in der Regel Unload und Update hintereinander aus.',searched:id};}return {available:false,job:null,sourceDatabase:null,endpoint:null,reason:reasonForMissing(m,err,arr,'kein kombinierter Unload-&-Update-Job gefunden. Gesucht wurde '+id+' beziehungsweise *_unload_load_transform.'),searched:id};}
function unloadPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unload';var j=findJobById(arr,id)||findJobBySuffix(arr,'_unload');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 nur den Unload aus.',searched:id};}return {available:false,job:null,sourceDatabase:null,endpoint:null,reason:reasonForMissing(m,err,arr,'kein einzelner Unload-Job gefunden. Gesucht wurde '+id+' beziehungsweise *_unload.'),searched:id};}
function updateOnlyPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_load_and_transform';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};}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);var r=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(m.installed===false){r='Das Modul ist laut Komponentenliste nicht installiert. Der Upgrade-Endpunkt wird trotzdem angeboten, falls die serverseitige Logik daraus eine Installation oder Reparatur ableiten kann.';}if(err){r+=' Hinweis: Die technische Jobliste konnte nicht geladen werden: '+err;}else if(installUpgrade&&installUpgrade.id){r+=' In der technischen Jobliste wurde zusätzlich '+installUpgrade.id+' gefunden.';}else{r+=' In der technischen Jobliste wurde kein *_install_upgrade-Job gefunden; gestartet wird dennoch der Komponenten-Endpunkt.';}return {available:!!abbr,endpoint:'/job/upgrade/'+abbr,job:installUpgrade,reason:r,versionInfo:vi};}
function completePlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unload_load_transform';if(m.installed!==true){return notInstalledPlan(m,id);}var j=findJobById(arr,id)||findJobBySuffix(arr,'_unload_load_transform')||findJobContains(arr,'unload_load_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 in der Regel Unload und Update hintereinander aus.',searched:id};}if(flagTrue(m.unload)&&flagTrue(m.load)){return inferredPlan(m,id,'Über /job/list/'+abbr+' wurde kein passender Kombi-Job gefunden. /etl/list meldet jedoch unload=true und 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 kombinierter Unload-&-Update-Job gefunden. Gesucht wurde '+id+' beziehungsweise *_unload_load_transform.'),searched:id};}
function unloadPlan(m){var abbr=key(m),arr=jobs(m),err=jobErr(m),id=abbr+'_unload';if(m.installed!==true){return notInstalledPlan(m,id);}var j=findJobById(arr,id)||findJobBySuffix(arr,'_unload');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 nur den Unload aus.',searched:id};}if(flagTrue(m.unload)){return inferredPlan(m,id,'Über /job/list/'+abbr+' wurde kein passender Unload-Job gefunden. /etl/list meldet jedoch unload=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 Unload-Job gefunden. Gesucht wurde '+id+' beziehungsweise *_unload.'),searched:id};}
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>';actions+=ug.available?'<button class="button is-warning is-light is-small js-upgrade" type="button" title="'+attr(TOOLTIP_UPGRADE)+'" aria-label="Upgrade: '+attr(TOOLTIP_UPGRADE)+'" data-component="'+attr(abbr)+'">Upgrade</button>':'<button class="button is-light is-small" type="button" title="'+attr(TOOLTIP_UPGRADE)+'" aria-label="Upgrade: '+attr(TOOLTIP_UPGRADE)+'" disabled>Upgrade</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 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 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);h+='<div class="box"><h3>Upgrade</h3><div class="plan-grid"><div class="muted">Bezeichnung</div><div>Modulaktualisierung</div><div class="muted">verfügbar</div><div>'+(ug.available?badge('ja','ok'):badge('nein','warn'))+'</div><div class="muted">Upgradebedarf</div><div>'+(m.needsUpgrade===true?badge('laut /etl/list ja','warn'):badge('laut /etl/list nein','neutral'))+'</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='';return fetchJson(API_BASE+'/list').then(function(data){if(!Array.isArray(data)){throw new Error('Unerwartete Antwort von /etl/list');}modules=data;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 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);});}
@ -722,19 +797,49 @@ function renderVersionDiagnostics(){if(!el.diagnosticsVersions){return;}if(!modu @@ -722,19 +797,49 @@ function renderVersionDiagnostics(){if(!el.diagnosticsVersions){return;}if(!modu
h+='</tbody></table>';el.diagnosticsVersions.innerHTML=h;}
function restHint(label,errorText){var t=String(errorText||'');if(t.indexOf('HTTP 500')>=0&&label.indexOf('/jobLog/')>=0){return 'Hinweis: Bei /jobLog/{id} war eine fehlende oder unvollständige Tabelle job_log eine bekannte Ursache. Prüfen Sie den Bereich „Datenbank / job_log“.';}if(t.indexOf('HTTP 500')>=0&&label.indexOf('/job/list/')>=0){return 'Hinweis: Bei /job/list/{modul} können fehlende Moduldateien, ein falscher aktiver Webapp-Kontext oder fehlende JVM-Optionen mögliche Ursachen sein.';}if(t.indexOf('HTTP 404')>=0){return 'Hinweis: 404 deutet häufig auf einen falschen Context-Pfad, falschen Port/Tunnel oder eine andere aktive Webapp hin.';}if(t.indexOf('Failed to fetch')>=0||t.indexOf('NetworkError')>=0){return 'Hinweis: Browser konnte den Endpunkt nicht erreichen. Tunnel, Port, Context-Pfad oder Anmeldung prüfen.';}return 'Hinweis: Fehlerdetails im Tomcat-Log prüfen. Beispiel-Kommandos stehen unten im Diagnosebereich.';}
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:'/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 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 startPlan(m,planFn,label){if(!m){return;}var p=planFn(m);if(!p.available){msg(label+' ist nicht verfügbar: '+p.reason,'error');return;}startJob(API_BASE+p.endpoint,label+' '+(m.name||key(m))+' ('+p.job.id+')');}
function startUpgrade(m){if(!m){return;}var p=upgradePlan(m);if(!p.available){msg('Upgrade ist nicht verfügbar: '+p.reason,'error');return;}var abbr=key(m);var text='Upgrade starten?\n\n'+(m.name||abbr)+'\n\nHinweis: '+(m.needsUpgrade===true?'Upgradebedarf erkannt.':'Aktuell wird kein Upgradebedarf gemeldet. Das Upgrade wird bewusst erneut ausgeführt.')+'\n\nInstallierte Version: '+(p.versionInfo.installed||'nicht geliefert')+'\nVerfügbare Version: '+(p.versionInfo.target||'nicht geliefert');if(!confirm(text)){return;}startJob(API_BASE+'/job/upgrade/'+encodeURIComponent(abbr),'Upgrade '+(m.name||abbr));}
function startJob(url,label){hideMsg();setLog('', 'Neuer Lauf wird gestartet.');appendLog('Starte: '+label);appendLog(url);fetchJson(url).then(function(result){var id=parseId(result);if(id===-1){msg('Der Server hat -1 zurückgeliefert. Es wurde kein neuer Job gestartet. Vermutlich läuft bereits ein anderer Job.','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;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');startPolling();}).catch(function(e){msg(e.message||String(e),'error');appendLog('Fehler beim Start: '+(e.message||String(e)));});}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 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).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 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);}}
function idValue(v){var id=parseId(v);return (id&&id>0)?id:null;}
function fieldId(o,keys){if(!o||typeof o!=='object'){return null;}for(var i=0;i<keys.length;i++){var v=o[keys[i]];if(v&&typeof v==='object'){if(v.id!==undefined&&v.id!==null){var nested=idValue(v.id);if(nested){return nested;}}if(v.jobExecutionId!==undefined&&v.jobExecutionId!==null){var ne=idValue(v.jobExecutionId);if(ne){return ne;}}}else{var id=idValue(v);if(id){return id;}}}return null;}
function addUniqueId(list,v){var id=idValue(v);if(id&&list.indexOf(id)<0){list.push(id);}return list;}
function updateKnownIds(data){
lastStatusPayload=data||lastStatusPayload;
var exec=fieldId(data,['jobExecutionId','executionId','job_execution_id']);
if(exec&&exec!==currentExecutionId){currentExecutionId=exec;}
var logId=fieldId(data,['jobInstanceId','job_instance_id','jobInstanceID','instanceId','job_instance','jobInstance','jobLogId','job_log_id']);
if(logId){currentLogId=logId;}
if(data&&typeof data==='object'){
if(data.jobExecution){var nestedExec=fieldId(data.jobExecution,['jobExecutionId','executionId','id']);if(nestedExec&&nestedExec!==currentExecutionId){currentExecutionId=nestedExec;}}
if(data.jobInstance){var nestedLog=fieldId(data.jobInstance,['jobInstanceId','id']);if(nestedLog){currentLogId=nestedLog;}}
}
}
function logIdCandidates(){var ids=[];if(lastStatusPayload&&typeof lastStatusPayload==='object'){addUniqueId(ids,fieldId(lastStatusPayload,['jobInstanceId','job_instance_id','jobInstanceID','instanceId','jobLogId','job_log_id']));if(lastStatusPayload.jobInstance){addUniqueId(ids,fieldId(lastStatusPayload.jobInstance,['jobInstanceId','id']));}}addUniqueId(ids,currentLogId);addUniqueId(ids,currentExecutionId);return ids;}
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){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){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 statusOf(d){if(!d){return '';}if(typeof d==='string'){return d;}if(d.status){return String(d.status);}if(d.exitStatus&&d.exitStatus.exitCode){return String(d.exitStatus.exitCode);}if(d.batchStatus){return String(d.batchStatus);}return '';}
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 isUnknownStatus(s){var u=String(s||'').trim().toUpperCase();return u==='UNKNOWN'||u==='UNBEKANNT';}
function statusOf(d){
if(!d){return '';}
if(typeof d==='string'){return d;}
var primary=d.status!==undefined&&d.status!==null?String(d.status):'';
if(primary&&!isUnknownStatus(primary)){return primary;}
if(d.exitStatus&&d.exitStatus.exitCode){return String(d.exitStatus.exitCode);}
if(d.batchStatus&&!isUnknownStatus(d.batchStatus)){return String(d.batchStatus);}
return primary||'';
}
function displayRunStatus(s){var raw=String(s||'').trim();if(!raw){return '';}var u=raw.toUpperCase();if(u.indexOf('COMPLETED')>=0){return 'Abgeschlossen';}if(u.indexOf('FAILED')>=0){return 'Fehlgeschlagen';}if(u.indexOf('STOPPED')>=0){return 'Gestoppt';}if(u.indexOf('ABANDONED')>=0){return 'Abgebrochen';}if(isUnknownStatus(raw)){return 'Läuft';}if(u.indexOf('RUNNING')>=0||u.indexOf('STARTED')>=0||u.indexOf('STARTING')>=0||u.indexOf('ANLAUF')>=0||u.indexOf('ANLAUEN')>=0){return 'Läuft';}return raw;}
function doneStatus(s){s=String(s||'').toUpperCase();return s.indexOf('COMPLETED')>=0||s.indexOf('FAILED')>=0||s.indexOf('STOPPED')>=0||s.indexOf('ABANDONED')>=0;}
function renderStatus(data){var s=statusOf(data);el.runStatus.textContent=s||stringify(data).substring(0,300);el.runId.textContent=currentExecutionId||'-';el.runJob.textContent=currentJobLabel||'-';var p=progress(data);if(el.progressBar.tagName&&el.progressBar.tagName.toLowerCase()==='progress'){el.progressBar.value=p.percent;el.progressBar.textContent=p.percent+'%';}else{el.progressBar.style.width=p.percent+'%';}el.progressText.textContent=p.text;if(doneStatus(s)){el.stopRunBtn.disabled=true;}}function progress(d){var done=null,total=null;if(d&&typeof d==='object'){var dk=['executedStepsCount','completedStepsCount','executedSteps','stepsExecuted','writeCount'],tk=['totalStepsCount','countTotalSteps','totalSteps','stepCount'];for(var i=0;i<dk.length;i++){if(d[dk[i]]!==undefined&&d[dk[i]]!==null&&!isNaN(Number(d[dk[i]]))){done=Number(d[dk[i]]);break;}}for(var j=0;j<tk.length;j++){if(d[tk[j]]!==undefined&&d[tk[j]]!==null&&!isNaN(Number(d[tk[j]]))){total=Number(d[tk[j]]);break;}}if((done===null||total===null)&&Array.isArray(d.stepExecutions)){total=d.stepExecutions.length;done=0;for(var k=0;k<d.stepExecutions.length;k++){if(doneStatus(statusOf(d.stepExecutions[k]))){done++;}}}}if(total!==null&&total>0&&done!==null){var pc=Math.max(0,Math.min(100,Math.round(done*100/total)));return {percent:pc,text:done+' von '+total+' Schritten ('+pc+'%)'};}var s=statusOf(d).toUpperCase();if(s.indexOf('COMPLETED')>=0){return {percent:100,text:'abgeschlossen'};}if(s.indexOf('START')>=0||s.indexOf('RUNNING')>=0){return {percent:35,text:'läuft, Detailfortschritt nicht verfügbar'};}return {percent:0,text:'kein Fortschritt verfügbar'};}
function loadLog(userRequested){if(!currentExecutionId){return Promise.resolve();}var id=currentExecutionId;if(logFailureByExecutionId[id]&&userRequested!==true){return Promise.resolve();}el.logHint.textContent='Log wird geladen...';return fetchJson(API_BASE+'/jobLog/'+encodeURIComponent(id)).then(function(data){logFailureByExecutionId[id]=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){var text=e.message||String(e);if(userRequested!==true&&!logFailureByExecutionId[id]){logFailureByExecutionId[id]=true;el.logHint.textContent='Log noch nicht verfügbar. Automatischer zweiter Versuch folgt.';return new Promise(function(resolve){setTimeout(resolve,3000);}).then(function(){logFailureByExecutionId[id]=false;return loadLog(true);});}logFailureByExecutionId[id]=true;var hint='Log für JobExecutionId '+id+' ist über /jobLog/'+id+' nicht verfügbar.';el.logHint.textContent='Log nicht verfügbar.';if(userRequested===true){appendLog(hint);appendLog(text);appendLog('Hinweis: Prüfen Sie über Diagnose die REST-Schnittstelle /jobLog/{id} und die Tabelle job_log.');}else{appendLog(hint+' Der Laufstatus wurde trotzdem abgefragt; vermutlich liefert der Server für diesen Log-Endpunkt HTTP 500.');}return Promise.resolve();});}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;currentJobLabel=(data&&data.jobName)?data.jobName:'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){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;}if(!confirm('Aktuellen Job stoppen?\n\nJobExecutionId: '+currentExecutionId)){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');});}
function renderStatus(data){var s=statusOf(data);el.runStatus.textContent=displayRunStatus(s)||stringify(data).substring(0,300);el.runId.textContent=currentExecutionId||'-';el.runJob.textContent=currentJobLabel||'-';var p=progress(data);if(el.progressBar.tagName&&el.progressBar.tagName.toLowerCase()==='progress'){el.progressBar.value=p.percent;el.progressBar.textContent=p.percent+'%';}else{el.progressBar.style.width=p.percent+'%';}el.progressText.textContent=p.text;if(doneStatus(s)){el.stopRunBtn.disabled=true;}}function progress(d){var done=null,total=null;if(d&&typeof d==='object'){var dk=['executedStepsCount','completedStepsCount','executedSteps','stepsExecuted','writeCount'],tk=['totalStepsCount','countTotalSteps','totalSteps','stepCount'];for(var i=0;i<dk.length;i++){if(d[dk[i]]!==undefined&&d[dk[i]]!==null&&!isNaN(Number(d[dk[i]]))){done=Number(d[dk[i]]);break;}}for(var j=0;j<tk.length;j++){if(d[tk[j]]!==undefined&&d[tk[j]]!==null&&!isNaN(Number(d[tk[j]]))){total=Number(d[tk[j]]);break;}}if((done===null||total===null)&&Array.isArray(d.stepExecutions)){total=d.stepExecutions.length;done=0;for(var k=0;k<d.stepExecutions.length;k++){if(doneStatus(statusOf(d.stepExecutions[k]))){done++;}}}}if(total!==null&&total>0&&done!==null){var pc=Math.max(0,Math.min(100,Math.round(done*100/total)));return {percent:pc,text:done+' von '+total+' Schritten ('+pc+'%)'};}var s=statusOf(d).toUpperCase();if(s.indexOf('COMPLETED')>=0){return {percent:100,text:'abgeschlossen'};}if(isUnknownStatus(s)||s.indexOf('START')>=0||s.indexOf('RUNNING')>=0){return {percent:35,text:'läuft, Detailfortschritt noch nicht verfügbar'};}return {percent:0,text:'kein Fortschritt verfügbar'};}
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.');};
loadModules().then(function(){checkRunning(false);});
})();

2
superx/xml/nd_templates.xsl

@ -473,7 +473,7 @@ $( "#content" ).load('/superx/xml/welcome_wiki.jsp',ajaxErrorHandler); @@ -473,7 +473,7 @@ $( "#content" ).load('/superx/xml/welcome_wiki.jsp',ajaxErrorHandler);
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<a href="../edit/kern/etl_manager.jsp" class="dropdown-item" target="_blank">ETL Manager</a>
<a href="../edit/kern/superx_komponentenverwaltung.jsp" class="dropdown-item" target="_blank">SuperX Komponentenverwaltung</a>
<a href="../edit/kern/webapp_manager.jsp" class="dropdown-item" target="_blank">Webanwendung Manager</a>
<a href="../servlet/SuperXManager?xupdater=true" class="dropdown-item" target="_blank">Maskensicherung</a>
<hr class="dropdown-divider" />

Loading…
Cancel
Save