Update
This commit is contained in:
1 parent
2c3379026b
commit
98a6df5081
7 files changed
+250
-111
No files matched your search
@@ -783,4 +783,4 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
|
|||||||
Zielversion: ${Je}.
|
Zielversion: ${Je}.
|
||||||
Während des Updates befindet sich StackPulse im Wartungsmodus.
|
Während des Updates befindet sich StackPulse im Wartungsmodus.
|
||||||
Fortfahren?`)))try{X(""),_(!0),await C(),e({variant:"info",title:"Update gestartet",description:"Das Portainer-Update wurde gestartet."})}catch(Nt){const zt=((gt=(mt=Nt.response)==null?void 0:mt.data)==null?void 0:gt.error)||Nt.message||"Portainer-Update konnte nicht gestartet werden";X(zt),e({variant:"error",title:"Update fehlgeschlagen",description:zt})}finally{_(!1)}},[j,P,ft,C,e]);Z.useCallback(async Je=>{var zt,hr,Tr,rn;if(!Je)return;if(j||P){e({variant:"warning",title:"Aktion nicht möglich",description:"Während des Wartungsmodus sind Bereinigungen deaktiviert."});return}const mt=(zt=Je.canonical)==null?void 0:zt.Id;if(!mt)return;const gt=(Je.duplicates||[]).map(yn=>yn.Id).filter(Boolean);if(!gt.length)return;const Nt=((hr=Je.canonical)==null?void 0:hr.Name)||Je.name||`Stack ${mt}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${Nt}" starten?
|
Fortfahren?`)))try{X(""),_(!0),await C(),e({variant:"info",title:"Update gestartet",description:"Das Portainer-Update wurde gestartet."})}catch(Nt){const zt=((gt=(mt=Nt.response)==null?void 0:mt.data)==null?void 0:gt.error)||Nt.message||"Portainer-Update konnte nicht gestartet werden";X(zt),e({variant:"error",title:"Update fehlgeschlagen",description:zt})}finally{_(!1)}},[j,P,ft,C,e]);Z.useCallback(async Je=>{var zt,hr,Tr,rn;if(!Je)return;if(j||P){e({variant:"warning",title:"Aktion nicht möglich",description:"Während des Wartungsmodus sind Bereinigungen deaktiviert."});return}const mt=(zt=Je.canonical)==null?void 0:zt.Id;if(!mt)return;const gt=(Je.duplicates||[]).map(yn=>yn.Id).filter(Boolean);if(!gt.length)return;const Nt=((hr=Je.canonical)==null?void 0:hr.Name)||Je.name||`Stack ${mt}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${Nt}" starten?
|
||||||
Es werden ${gt.length} Duplikate entfernt: ${gt.join(", ")}`))){xt(String(mt));try{const ee=(await Nr.post("/api/maintenance/duplicates/cleanup",{canonicalId:mt,duplicateIds:gt})).data??{};if(ee.success===!1)throw new Error(ee.error||"Bereinigung fehlgeschlagen");const n=Array.isArray(ee.results)?ee.results.filter(o=>o.status==="deleted").map(o=>o.id):gt;e({variant:"success",title:"Bereinigung abgeschlossen",description:`${Nt} – entfernte IDs: ${n.join(", ")}`}),await Ct({silent:!0})}catch(yn){const ee=((rn=(Tr=yn.response)==null?void 0:Tr.data)==null?void 0:rn.error)||yn.message||"Bereinigung fehlgeschlagen";e({variant:"error",title:"Bereinigung fehlgeschlagen",description:ee})}finally{xt(null)}}},[Ct,j,e,P]);const Gn=t!=null&&t.activatedAt?Ws(t.activatedAt):null,yr=t!=null&&t.updatedAt?Ws(t.updatedAt):null,gn=(r==null?void 0:r.status)??(P?"running":"idle"),_o=EV[gn]??gn,Uo=Bs[gn]??Bs.idle,bn=r!=null&&r.stage?MV[r.stage]??r.stage:"–",po=(r==null?void 0:r.logs)??[],Gr=(r==null?void 0:r.targetVersion)??"-",ho=(r==null?void 0:r.resultVersion)??"-",Rn=r!=null&&r.startedAt?Ws(r.startedAt):"-",Oo=r!=null&&r.finishedAt?Ws(r.finishedAt):"-",vn=(r==null?void 0:r.message)??(P?"Update läuft…":""),zr=P||v||l;return te.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(j||P)&&te.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:te.jsxs("div",{className:"flex flex-col gap-1",children:[te.jsxs("span",{children:["Wartungsmodus aktiv",R?` – ${R}`:P?" – Portainer-Update läuft":"","."]}),P&&te.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",bn]})]})}),te.jsxs(De.Card,{children:[te.jsx(De.CardHeader,{variant:"gradient",color:"gray",className:"mb-8 p-6",children:te.jsx(De.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:te.jsx("span",{children:"Portainer"})})}),te.jsxs(De.CardBody,{className:"flex flex-col gap-4 p-4 pt-0",children:[te.jsxs("div",{className:"flex flex-col gap-4 md:flex-row md:items-center md:justify-between",children:[te.jsxs("div",{children:[te.jsx("h2",{className:"block antialiased font-sans text-lg font-semibold",children:"Portainer Status"}),te.jsx("p",{className:"block antialiased font-sans text-sm font-light leading-normal text-inherit",children:Bt?"Status wird geprüft…":Tt||(P?"Portainer wird aktuell aktualisiert.":(ft==null?void 0:ft.updateAvailable)===!0?"Es ist ein Update für Portainer verfügbar.":"Portainer ist auf dem aktuellen Stand.")}),Ur&&!Bt&&te.jsxs("p",{className:"mt-1 text-xs text-gray-500 block antialiased font-sans",children:["Stand: ",Ur.toLocaleString("de-DE",{hour:"2-digit",minute:"2-digit",second:"2-digit"})]})]}),te.jsx("div",{className:"flex items-center gap-3",children:te.jsx(De.Button,{onClick:()=>ct({silent:!1}),disabled:Bt||br,className:"w-full",children:"Status aktualisieren"})})]}),te.jsx("div",{className:"flex flex-col gap-4 mt-5 space-y-4 block antialiased font-sans text-sm font-light leading-normal text-inherit",children:!Bt&&!Tt&&ft&&te.jsxs("div",{className:"",children:[te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Installierte Version"}),te.jsx("span",{className:"font-medium",children:ft.currentVersion??"Unbekannt"})]}),te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Neueste Version"}),te.jsx("span",{className:"font-medium",children:ft.latestVersion??"Unbekannt"})]}),te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Status"}),te.jsx("span",{className:`rounded-full px-3 py-0.5 text-xs font-semibold ${ft.updateAvailable===!0?Bs.error:ft.updateAvailable===!1?Bs.success:Bs.idle}`,children:ft.updateAvailable===!0?"Update verfügbar":ft.updateAvailable===!1?"Aktuell":"Unbekannt"})]}),ft.edition&&te.jsxs("div",{className:"flex items-center justify-between",children:[te.jsx("span",{className:"text-gray-400",children:"Edition"}),te.jsx("span",{className:"font-medium",children:ft.edition})]}),ft.build&&te.jsxs("div",{className:"flex items-center justify-between",children:[te.jsx("span",{className:"text-gray-400",children:"Build"}),te.jsx("span",{className:"font-medium",children:ft.build})]}),((tn=ft.errors)==null?void 0:tn.latestVersion)&&te.jsxs("div",{className:"rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs",children:["Neueste Version konnte nicht ermittelt werden: ",ft.errors.latestVersion]}),((mn=ft.errors)==null?void 0:mn.container)&&te.jsxs("div",{className:"rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs",children:["Container-Details konnten nicht ermittelt werden: ",ft.errors.container]})]})}),te.jsxs("div",{className:"mt-6 grid gap-6 lg:grid-cols-2 xl:grid-cols-3",children:[te.jsxs("div",{className:"rounded-md border border-gray-700/20 p-4",children:[te.jsx("div",{className:"flex item
Line truncated
|
Es werden ${gt.length} Duplikate entfernt: ${gt.join(", ")}`))){xt(String(mt));try{const ee=(await Nr.post("/api/maintenance/duplicates/cleanup",{canonicalId:mt,duplicateIds:gt})).data??{};if(ee.success===!1)throw new Error(ee.error||"Bereinigung fehlgeschlagen");const n=Array.isArray(ee.results)?ee.results.filter(o=>o.status==="deleted").map(o=>o.id):gt;e({variant:"success",title:"Bereinigung abgeschlossen",description:`${Nt} – entfernte IDs: ${n.join(", ")}`}),await Ct({silent:!0})}catch(yn){const ee=((rn=(Tr=yn.response)==null?void 0:Tr.data)==null?void 0:rn.error)||yn.message||"Bereinigung fehlgeschlagen";e({variant:"error",title:"Bereinigung fehlgeschlagen",description:ee})}finally{xt(null)}}},[Ct,j,e,P]);const Gn=t!=null&&t.activatedAt?Ws(t.activatedAt):null,yr=t!=null&&t.updatedAt?Ws(t.updatedAt):null,gn=(r==null?void 0:r.status)??(P?"running":"idle"),_o=EV[gn]??gn,Uo=Bs[gn]??Bs.idle,bn=r!=null&&r.stage?MV[r.stage]??r.stage:"–",po=(r==null?void 0:r.logs)??[],Gr=(r==null?void 0:r.targetVersion)??"-",ho=(r==null?void 0:r.resultVersion)??"-",Rn=r!=null&&r.startedAt?Ws(r.startedAt):"-",Oo=r!=null&&r.finishedAt?Ws(r.finishedAt):"-",vn=(r==null?void 0:r.message)??(P?"Update läuft…":""),zr=P||v||l;return te.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(j||P)&&te.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:te.jsxs("div",{className:"flex flex-col gap-1",children:[te.jsxs("span",{children:["Wartungsmodus aktiv",R?` – ${R}`:P?" – Portainer-Update läuft":"","."]}),P&&te.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",bn]})]})}),te.jsxs(De.Card,{children:[te.jsx(De.CardHeader,{variant:"gradient",color:"gray",className:"mb-8 p-6",children:te.jsx(De.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:te.jsx("span",{children:"Portainer"})})}),te.jsxs(De.CardBody,{className:"flex flex-col gap-4 p-4 pt-0",children:[te.jsxs("div",{className:"flex flex-col gap-4 md:flex-row md:items-center md:justify-between",children:[te.jsxs("div",{children:[te.jsx("h2",{className:"block antialiased font-sans text-lg font-semibold",children:"Portainer Status"}),te.jsx("p",{className:"block antialiased font-sans text-sm font-light leading-normal text-inherit",children:Bt?"Status wird geprüft…":Tt||(P?"Portainer wird aktuell aktualisiert.":(ft==null?void 0:ft.updateAvailable)===!0?"Es ist ein Update für Portainer verfügbar.":"Portainer ist auf dem aktuellen Stand.")}),Ur&&!Bt&&te.jsxs("p",{className:"mt-1 text-xs text-gray-500 block antialiased font-sans",children:["Stand: ",Ur.toLocaleString("de-DE",{hour:"2-digit",minute:"2-digit",second:"2-digit"})]})]}),te.jsx("div",{className:"flex items-center gap-3",children:te.jsx(De.Button,{onClick:()=>ct({silent:!1}),disabled:Bt||br,className:"w-full",children:"Status aktualisieren"})})]}),te.jsx("div",{className:"flex flex-col gap-4 mt-5 space-y-4 block antialiased font-sans text-sm font-light leading-normal text-inherit",children:!Bt&&!Tt&&ft&&te.jsxs("div",{className:"",children:[te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Installierte Version"}),te.jsx("span",{className:"font-medium",children:ft.currentVersion??"Unbekannt"})]}),te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Neueste Version"}),te.jsx("span",{className:"font-medium",children:ft.latestVersion??"Unbekannt"})]}),te.jsxs("div",{className:"flex items-center justify-between space-y-2",children:[te.jsx("span",{children:"Status"}),te.jsx("span",{className:`rounded-full px-3 py-0.5 text-xs font-semibold ${ft.updateAvailable===!0?Bs.error:ft.updateAvailable===!1?Bs.success:Bs.idle}`,children:ft.updateAvailable===!0?"Update verfügbar":ft.updateAvailable===!1?"Aktuell":"Unbekannt"})]}),ft.edition&&te.jsxs("div",{className:"flex items-center justify-between",children:[te.jsx("span",{className:"text-gray-400",children:"Edition"}),te.jsx("span",{className:"font-medium",children:ft.edition})]}),ft.build&&te.jsxs("div",{className:"flex items-center justify-between",children:[te.jsx("span",{className:"text-gray-400",children:"Build"}),te.jsx("span",{className:"font-medium",children:ft.build})]}),((tn=ft.errors)==null?void 0:tn.latestVersion)&&te.jsxs("div",{className:"rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs",children:["Neueste Version konnte nicht ermittelt werden: ",ft.errors.latestVersion]}),((mn=ft.errors)==null?void 0:mn.container)&&te.jsxs("div",{className:"rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs",children:["Container-Details konnten nicht ermittelt werden: ",ft.errors.container]})]})}),te.jsxs("div",{className:"mt-6 grid gap-6 lg:grid-cols-2 xl:grid-cols-3",children:[te.jsxs("div",{className:"rounded-md border border-gray-700/20 p-4",children:[te.jsx("div",{className:"flex item
Line truncated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-left-1{leftLine truncated
|
||||||
@@ -1 +0,0 @@
|
|||||||
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-left-1{leftLine truncated
|
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
<!-- Nepcha Analytics (nepcha.com) -->
|
<!-- Nepcha Analytics (nepcha.com) -->
|
||||||
<!-- Nepcha is a easy-to-use web analytics. No cookies and fully compliant with GDPR, CCPA and PECR. -->
|
<!-- Nepcha is a easy-to-use web analytics. No cookies and fully compliant with GDPR, CCPA and PECR. -->
|
||||||
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
||||||
<script type="module" crossorigin src="/assets/index-55cb988b.js"></script>
|
<script type="module" crossorigin src="/assets/index-3cdb6846.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-c9347383.css">
|
<link rel="stylesheet" href="/assets/index-422138e0.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ const ToastContext = createContext(null);
|
|||||||
|
|
||||||
const VARIANT_STYLES = {
|
const VARIANT_STYLES = {
|
||||||
success: {
|
success: {
|
||||||
container: "border-green-500/80 bg-green-950/90 text-green-100",
|
container: "border-mossGreen-500/80 bg-mossGreen-900/90 text-white",
|
||||||
accent: "bg-green-400"
|
accent: "bg-mossGreen-400"
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
container: "border-yellow-500/80 bg-yellow-950/90 text-yellow-100",
|
container: "border-warmAmberGlow-500/80 bg-warmAmberGlow-900/90 text-white",
|
||||||
accent: "bg-yellow-400"
|
accent: "bg-warmAmberGlow-400"
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
container: "border-red-500/80 bg-red-950/90 text-red-100",
|
container: "border-sunsetCoral-500/80 bg-sunsetCoral-900/90 text-white",
|
||||||
accent: "bg-red-400"
|
accent: "bg-sunsetCoral-400"
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
container: "border-sky-500/60 bg-sky-950/90 text-sky-100",
|
container: "border-arcticBlue-500 bg-arcticBlue-900/90 text-white",
|
||||||
accent: "bg-sky-400"
|
accent: "bg-arcticBlue-400"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
useSelect
|
useSelect
|
||||||
} from "@material-tailwind/react";
|
} from "@material-tailwind/react";
|
||||||
|
|
||||||
const StickyOption = React.forwardRef(({ value, onValueSelect, onClick, onKeyDown, ...props }, ref) => {
|
const StickyOption = React.forwardRef(({ value, onClick, onKeyDown, ...props }, ref) => {
|
||||||
const { setOpen } = useSelect();
|
const { setOpen } = useSelect();
|
||||||
|
|
||||||
const reopen = useCallback(() => {
|
const reopen = useCallback(() => {
|
||||||
@@ -31,20 +31,13 @@ const StickyOption = React.forwardRef(({ value, onValueSelect, onClick, onKeyDow
|
|||||||
});
|
});
|
||||||
}, [setOpen]);
|
}, [setOpen]);
|
||||||
|
|
||||||
const commitSelection = useCallback(() => {
|
|
||||||
if (typeof onValueSelect === "function") {
|
|
||||||
onValueSelect(value);
|
|
||||||
}
|
|
||||||
}, [onValueSelect, value]);
|
|
||||||
|
|
||||||
const handleClick = useCallback((event) => {
|
const handleClick = useCallback((event) => {
|
||||||
if (typeof onClick === "function") {
|
if (typeof onClick === "function") {
|
||||||
onClick(event);
|
onClick(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
commitSelection();
|
|
||||||
reopen();
|
reopen();
|
||||||
}, [onClick, commitSelection, reopen]);
|
}, [onClick, reopen]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback((event) => {
|
const handleKeyDown = useCallback((event) => {
|
||||||
if (typeof onKeyDown === "function") {
|
if (typeof onKeyDown === "function") {
|
||||||
@@ -52,10 +45,9 @@ const StickyOption = React.forwardRef(({ value, onValueSelect, onClick, onKeyDow
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Enter" || event.key === " ") {
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
commitSelection();
|
|
||||||
reopen();
|
reopen();
|
||||||
}
|
}
|
||||||
}, [onKeyDown, commitSelection, reopen]);
|
}, [onKeyDown, reopen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Option
|
<Option
|
||||||
@@ -71,10 +63,10 @@ const StickyOption = React.forwardRef(({ value, onValueSelect, onClick, onKeyDow
|
|||||||
StickyOption.displayName = "StickyOption";
|
StickyOption.displayName = "StickyOption";
|
||||||
|
|
||||||
const STATUS_COLORS = {
|
const STATUS_COLORS = {
|
||||||
success: "text-green-400",
|
success: "text-mossGreen-500",
|
||||||
warning: "text-yellow-400",
|
warning: "text-warmAmberGlow-500",
|
||||||
error: "text-red-400",
|
error: "text-sunsetCoral-500",
|
||||||
started: "text-blue-300"
|
started: "text-arcticBlue-500"
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimestamp = (value) => {
|
const formatTimestamp = (value) => {
|
||||||
@@ -419,7 +411,7 @@ export function Logs() {
|
|||||||
}
|
}
|
||||||
}, [filtersReady, currentFilters, perPage, page]);
|
}, [filtersReady, currentFilters, perPage, page]);
|
||||||
|
|
||||||
const handleMultiSelectChange = (setter) => (valueOrEvent) => {
|
const handleMultiSelectChange = useCallback((setter) => (valueOrEvent) => {
|
||||||
if (typeof valueOrEvent === "string") {
|
if (typeof valueOrEvent === "string") {
|
||||||
const value = valueOrEvent;
|
const value = valueOrEvent;
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
@@ -453,7 +445,12 @@ export function Logs() {
|
|||||||
setter(values);
|
setter(values);
|
||||||
}
|
}
|
||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
}, [setPage]);
|
||||||
|
|
||||||
|
const selectStacks = useMemo(() => handleMultiSelectChange(setSelectedStacks), [handleMultiSelectChange, setSelectedStacks]);
|
||||||
|
const selectStatuses = useMemo(() => handleMultiSelectChange(setSelectedStatuses), [handleMultiSelectChange, setSelectedStatuses]);
|
||||||
|
const selectRedeployTypes = useMemo(() => handleMultiSelectChange(setSelectedRedeployTypes), [handleMultiSelectChange, setSelectedRedeployTypes]);
|
||||||
|
const selectEndpoints = useMemo(() => handleMultiSelectChange(setSelectedEndpoints), [handleMultiSelectChange, setSelectedEndpoints]);
|
||||||
|
|
||||||
const removeFilterValue = (setter, value) => {
|
const removeFilterValue = (setter, value) => {
|
||||||
setter((prev) => prev.filter((entry) => entry !== value));
|
setter((prev) => prev.filter((entry) => entry !== value));
|
||||||
@@ -631,7 +628,7 @@ export function Logs() {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-12 mb-8 flex flex-col gap-12">
|
<div className="mt-12 mb-8 flex flex-col gap-12">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader variant="gradient" color="gray" className="p-6 pt-2 pb-2">
|
<CardHeader variant="gradient" color="gray" className="p-4 pt-2 pb-2">
|
||||||
<Typography variant="h6" color="white">
|
<Typography variant="h6" color="white">
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleFilters}
|
onClick={handleToggleFilters}
|
||||||
@@ -679,7 +676,7 @@ export function Logs() {
|
|||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
className="text-gray-500"
|
className="text-stormGrey-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
dismiss={{ itemPress: false }}
|
dismiss={{ itemPress: false }}
|
||||||
label="Stacks"
|
label="Stacks"
|
||||||
@@ -688,16 +685,22 @@ export function Logs() {
|
|||||||
<StickyOption
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onValueSelect={handleMultiSelectChange(setSelectedStacks)}
|
onClick={() => selectStacks(value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectStacks(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</StickyOption>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-stormGrey-400">
|
||||||
{selectedStacks.length === 0 ? (
|
{selectedStacks.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300 ">
|
<span className="rounded-full bg-stormGrey-700/60 px-2 py-0.5 text-white ">
|
||||||
Alle Stacks
|
Alle Stacks
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -707,7 +710,7 @@ export function Logs() {
|
|||||||
key={stackId}
|
key={stackId}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeFilterValue(setSelectedStacks, stackId)}
|
onClick={() => removeFilterValue(setSelectedStacks, stackId)}
|
||||||
className="rounded-full bg-purple-500/80 px-2 py-0.5 text-white transition hover:bg-purple-500/90 focus:outline-none focus:ring-2 focus:ring-purple-300 cursor-pointer"
|
className="rounded-full bg-lavenderSmoke-600/80 px-2 py-0.5 text-white transition hover:bg-lavenderSmoke-600/90 focus:outline-none focus:ring-2 focus:ring-lavenderSmoke-400 cursor-pointer"
|
||||||
title="Filter entfernen"
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{stackLabelMap.get(stackId) ?? `Stack ${stackId}`}
|
{stackLabelMap.get(stackId) ?? `Stack ${stackId}`}
|
||||||
@@ -721,7 +724,7 @@ export function Logs() {
|
|||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
className="text-gray-500"
|
className="text-stormGrey-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
dismiss={{ itemPress: false }}
|
dismiss={{ itemPress: false }}
|
||||||
label="Status"
|
label="Status"
|
||||||
@@ -730,16 +733,22 @@ export function Logs() {
|
|||||||
<StickyOption
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onValueSelect={handleMultiSelectChange(setSelectedStatuses)}
|
onClick={() => selectStatuses(value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectStatuses(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</StickyOption>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-stormGrey-400">
|
||||||
{selectedStatuses.length === 0 ? (
|
{selectedStatuses.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-stormGrey-700/60 px-2 py-0.5 text-white">
|
||||||
Alle Status
|
Alle Status
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -749,7 +758,7 @@ export function Logs() {
|
|||||||
key={status}
|
key={status}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeFilterValue(setSelectedStatuses, status)}
|
onClick={() => removeFilterValue(setSelectedStatuses, status)}
|
||||||
className="rounded-full bg-brown-500/80 px-2 py-0.5 text-white transition hover:bg-brown-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
className="rounded-full bg-copperRust-600/80 px-2 py-0.5 text-white transition hover:bg-copperRust-600/90 focus:outline-none focus:ring-2 focus:ring-copperRust-400 cursor-pointer"
|
||||||
title="Filter entfernen"
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{status}
|
{status}
|
||||||
@@ -763,7 +772,7 @@ export function Logs() {
|
|||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
className="text-gray-500"
|
className="text-stormGrey-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
dismiss={{ itemPress: false }}
|
dismiss={{ itemPress: false }}
|
||||||
label="Redeploy-Typ"
|
label="Redeploy-Typ"
|
||||||
@@ -772,7 +781,13 @@ export function Logs() {
|
|||||||
<StickyOption
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onValueSelect={handleMultiSelectChange(setSelectedRedeployTypes)}
|
onClick={() => selectRedeployTypes(value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectRedeployTypes(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -780,9 +795,9 @@ export function Logs() {
|
|||||||
|
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-stormGrey-400">
|
||||||
{selectedRedeployTypes.length === 0 ? (
|
{selectedRedeployTypes.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-stormGrey-700/60 px-2 py-0.5 text-white">
|
||||||
Alle Typen
|
Alle Typen
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -792,7 +807,7 @@ export function Logs() {
|
|||||||
key={type}
|
key={type}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeFilterValue(setSelectedRedeployTypes, type)}
|
onClick={() => removeFilterValue(setSelectedRedeployTypes, type)}
|
||||||
className="rounded-full bg-brown-500/80 px-2 py-0.5 text-white transition hover:bg-brown-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
className="rounded-full bg-citrusPunch-600/80 px-2 py-0.5 text-white transition hover:bg-citrusPunch-600/90 focus:outline-none focus:ring-2 focus:ring-citrusPunch-400 cursor-pointer"
|
||||||
title="Filter entfernen"
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{REDEPLOY_TYPE_LABELS[type] ?? type}
|
{REDEPLOY_TYPE_LABELS[type] ?? type}
|
||||||
@@ -806,7 +821,7 @@ export function Logs() {
|
|||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
onChange={noop}
|
onChange={noop}
|
||||||
className="text-gray-500"
|
className="text-stormGrey-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
dismiss={{ itemPress: false }}
|
dismiss={{ itemPress: false }}
|
||||||
label="Endpoints"
|
label="Endpoints"
|
||||||
@@ -815,16 +830,22 @@ export function Logs() {
|
|||||||
<StickyOption
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onValueSelect={handleMultiSelectChange(setSelectedEndpoints)}
|
onClick={() => selectEndpoints(value)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectEndpoints(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</StickyOption>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-stormGrey-400">
|
||||||
{selectedEndpoints.length === 0 ? (
|
{selectedEndpoints.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-stormGrey-700/60 px-2 py-0.5 text-white">
|
||||||
Alle Endpoints
|
Alle Endpoints
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -834,7 +855,7 @@ export function Logs() {
|
|||||||
key={endpoint}
|
key={endpoint}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeFilterValue(setSelectedEndpoints, endpoint)}
|
onClick={() => removeFilterValue(setSelectedEndpoints, endpoint)}
|
||||||
className="rounded-full bg-teal-500/80 px-2 py-0.5 text-white transition hover:bg-teal-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
className="rounded-full bg-emeraldMist-500/80 px-2 py-0.5 text-white transition hover:bg-emeraldMist-500/90 focus:outline-none focus:ring-2 focus:ring-emeraldMist-400 cursor-pointer"
|
||||||
title="Filter entfernen"
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
Endpoint {endpoint}
|
Endpoint {endpoint}
|
||||||
@@ -903,7 +924,7 @@ export function Logs() {
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader variant="gradient" color="gray" className="mb-8 p-6">
|
<CardHeader variant="gradient" color="gray" className="mb-5 p-4">
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
color="white"
|
color="white"
|
||||||
@@ -918,7 +939,7 @@ export function Logs() {
|
|||||||
<select
|
<select
|
||||||
value={perPage}
|
value={perPage}
|
||||||
onChange={handlePerPageChange}
|
onChange={handlePerPageChange}
|
||||||
className="rounded-md border border-gray-700 bg-gray-900 px-2 py-1 text-sm text-gray-100 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500"
|
className="rounded-md border border-gray-700 bg-gray-900 px-2 py-1 text-sm text-gray-100 focus:border-lavenderSmoke-500 focus:outline-none focus:ring-1 focus:ring-lavenderSmoke-500"
|
||||||
>
|
>
|
||||||
{PER_PAGE_OPTIONS.map(({ value, label }) => (
|
{PER_PAGE_OPTIONS.map(({ value, label }) => (
|
||||||
<option key={value} value={value}>
|
<option key={value} value={value}>
|
||||||
@@ -936,11 +957,11 @@ export function Logs() {
|
|||||||
{["Zeitpunkt", "Stack", "Art", "Status", "Nachricht", "Endpoint", "Aktionen"].map((el) => (
|
{["Zeitpunkt", "Stack", "Art", "Status", "Nachricht", "Endpoint", "Aktionen"].map((el) => (
|
||||||
<th
|
<th
|
||||||
key={el}
|
key={el}
|
||||||
className="border-b border-blue-gray-50 py-3 px-5 text-left"
|
className="border-b border-stormGrey-50 py-3 px-5 text-left"
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="small"
|
variant="small"
|
||||||
className="text-[11px] font-bold uppercase text-blue-gray-400"
|
className="text-[11px] font-bold uppercase text-stormGrey-400"
|
||||||
>
|
>
|
||||||
{el}
|
{el}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -962,7 +983,7 @@ export function Logs() {
|
|||||||
<td className={className}>
|
<td className={className}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="small"
|
variant="small"
|
||||||
className="mb-1 block text-xs font-medium text-blue-gray-600">
|
className="mb-1 block text-xs font-medium text-stormGrey-600">
|
||||||
{formatTimestamp(log.timestamp)}
|
{formatTimestamp(log.timestamp)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</td>
|
</td>
|
||||||
@@ -973,7 +994,7 @@ export function Logs() {
|
|||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
{showStackId && (
|
{showStackId && (
|
||||||
<span className="text-xs text-gray-400">ID: {log.stackId}</span>
|
<span className="text-xs text-stormGrey-400">ID: {log.stackId}</span>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ const UPDATE_STATUS_LABELS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UPDATE_STATUS_STYLES = {
|
const UPDATE_STATUS_STYLES = {
|
||||||
idle: "bg-gray-700 text-gray-200",
|
idle: "bg-stormGrey-700 text-gray-200",
|
||||||
running: "bg-sky-600 text-white",
|
running: "bg-arcticBlue-600 text-white",
|
||||||
success: "bg-green-600 text-white",
|
success: "bg-mossGreen-600 text-white",
|
||||||
error: "bg-red-600 text-white"
|
error: "bg-sunsetCoral-600 text-white"
|
||||||
};
|
};
|
||||||
|
|
||||||
const UPDATE_STAGE_LABELS = {
|
const UPDATE_STAGE_LABELS = {
|
||||||
@@ -36,13 +36,13 @@ const UPDATE_STAGE_LABELS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LOG_LEVEL_STYLES = {
|
const LOG_LEVEL_STYLES = {
|
||||||
info: "text-blue-700",
|
info: "text-arcticBlue-700",
|
||||||
success: "text-green-700",
|
success: "text-mossGreen-700",
|
||||||
warning: "text-amber-700",
|
warning: "text-warmAmberGlow-700",
|
||||||
error: "text-red-700",
|
error: "text-sunsetCoral-700",
|
||||||
stdout: "text-gray-700",
|
stdout: "text-stormGrey-700",
|
||||||
stderr: "text-orange-700",
|
stderr: "text-warmAmberGlow-700",
|
||||||
debug: "text-cyan-700"
|
debug: "text-lavenderSmoke-700"
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatLogTimestamp = (value) => {
|
const formatLogTimestamp = (value) => {
|
||||||
@@ -128,6 +128,7 @@ export function Maintenance() {
|
|||||||
|
|
||||||
const maintenanceActive = Boolean(maintenanceMeta?.active);
|
const maintenanceActive = Boolean(maintenanceMeta?.active);
|
||||||
const maintenanceMessage = maintenanceMeta?.message;
|
const maintenanceMessage = maintenanceMeta?.message;
|
||||||
|
const maintenanceExtraType = maintenanceMeta?.extra?.type;
|
||||||
const updateRunning = Boolean(updateState?.running);
|
const updateRunning = Boolean(updateState?.running);
|
||||||
|
|
||||||
const [scriptDraft, setScriptDraft] = useState("");
|
const [scriptDraft, setScriptDraft] = useState("");
|
||||||
@@ -432,10 +433,16 @@ export function Maintenance() {
|
|||||||
|
|
||||||
setMaintenanceToggleLoading(true);
|
setMaintenanceToggleLoading(true);
|
||||||
try {
|
try {
|
||||||
await setMaintenanceMode({
|
const shouldPreserveMessage = nextActive && Boolean(maintenanceExtraType);
|
||||||
active: nextActive,
|
const payload = { active: nextActive };
|
||||||
message: nextActive ? (maintenanceMessage ?? undefined) : null
|
|
||||||
});
|
if (shouldPreserveMessage && maintenanceMessage) {
|
||||||
|
payload.message = maintenanceMessage;
|
||||||
|
} else {
|
||||||
|
payload.message = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await setMaintenanceMode(payload);
|
||||||
showToast({
|
showToast({
|
||||||
variant: nextActive ? "info" : "success",
|
variant: nextActive ? "info" : "success",
|
||||||
title: nextActive ? "Wartungsmodus aktiviert" : "Wartungsmodus deaktiviert",
|
title: nextActive ? "Wartungsmodus aktiviert" : "Wartungsmodus deaktiviert",
|
||||||
@@ -449,7 +456,7 @@ export function Maintenance() {
|
|||||||
} finally {
|
} finally {
|
||||||
setMaintenanceToggleLoading(false);
|
setMaintenanceToggleLoading(false);
|
||||||
}
|
}
|
||||||
}, [maintenanceLoading, maintenanceToggleLoading, maintenanceActive, updateRunning, setMaintenanceMode, maintenanceMessage, showToast]);
|
}, [maintenanceLoading, maintenanceToggleLoading, maintenanceActive, updateRunning, setMaintenanceMode, maintenanceMessage, maintenanceExtraType, showToast]);
|
||||||
|
|
||||||
const handleTriggerUpdate = useCallback(async () => {
|
const handleTriggerUpdate = useCallback(async () => {
|
||||||
if (maintenanceActive || updateRunning) {
|
if (maintenanceActive || updateRunning) {
|
||||||
@@ -589,8 +596,41 @@ export function Maintenance() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader variant="gradient" color="gray" className="mb-8 p-6">
|
<CardHeader variant="gradient" color="gray" className="mb-5 p-4">
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
color="white"
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>Wartungsmodus</span>
|
||||||
|
|
||||||
|
</Typography>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody className="flex flex-col gap-4 p-4">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm antialiased font-sans text-sm font-light leading-normal text-inherit">
|
||||||
|
Schaltet StackPulse für Benutzer in den Wartungsmodus.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={maintenanceActive}
|
||||||
|
disabled={maintenanceToggleLoading || maintenanceLoading || updateRunning}
|
||||||
|
onChange={(event) => handleMaintenanceToggle(event.target.checked)}
|
||||||
|
ripple={false}
|
||||||
|
color="amber"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{maintenanceToggleLoading && (
|
||||||
|
<p className="text-xs text-stormGrey-500">Wartungsmodus wird aktualisiert…</p>
|
||||||
|
)}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader variant="gradient" color="gray" className="mb-5 p-4">
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
color="white"
|
color="white"
|
||||||
@@ -616,7 +656,7 @@ export function Maintenance() {
|
|||||||
: "Portainer ist auf dem aktuellen Stand.")}
|
: "Portainer ist auf dem aktuellen Stand.")}
|
||||||
</p>
|
</p>
|
||||||
{portainerUpdatedAt && !portainerLoading && (
|
{portainerUpdatedAt && !portainerLoading && (
|
||||||
<p className="mt-1 text-xs text-gray-500 block antialiased font-sans">
|
<p className="mt-1 text-xs text-stormGrey-500 block antialiased font-sans">
|
||||||
Stand: {portainerUpdatedAt.toLocaleString("de-DE", {
|
Stand: {portainerUpdatedAt.toLocaleString("de-DE", {
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
@@ -665,23 +705,23 @@ export function Maintenance() {
|
|||||||
</div>
|
</div>
|
||||||
{portainerStatus.edition && (
|
{portainerStatus.edition && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-gray-400">Edition</span>
|
<span className="text-stormGrey-400">Edition</span>
|
||||||
<span className="font-medium">{portainerStatus.edition}</span>
|
<span className="font-medium">{portainerStatus.edition}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{portainerStatus.build && (
|
{portainerStatus.build && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-gray-400">Build</span>
|
<span className="text-stormGrey-400">Build</span>
|
||||||
<span className="font-medium">{portainerStatus.build}</span>
|
<span className="font-medium">{portainerStatus.build}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{portainerStatus.errors?.latestVersion && (
|
{portainerStatus.errors?.latestVersion && (
|
||||||
<div className="rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs">
|
<div className="rounded-md mt-8 border bg-sunsetCoral-600 text-white px-3 py-2 text-xs">
|
||||||
Neueste Version konnte nicht ermittelt werden: {portainerStatus.errors.latestVersion}
|
Neueste Version konnte nicht ermittelt werden: {portainerStatus.errors.latestVersion}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{portainerStatus.errors?.container && (
|
{portainerStatus.errors?.container && (
|
||||||
<div className="rounded-md mt-8 border border-red-500/40 bg-red-900/40 px-3 py-2 text-xs">
|
<div className="rounded-md mt-8 border bg-sunsetCoral-600 text-white px-3 py-2 text-xs">
|
||||||
Container-Details konnten nicht ermittelt werden: {portainerStatus.errors.container}
|
Container-Details konnten nicht ermittelt werden: {portainerStatus.errors.container}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -870,7 +910,7 @@ export function Maintenance() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{sshTestResult && (
|
{sshTestResult && (
|
||||||
<p className={`mt-3 text-xs ${sshTestResult.success ? 'text-green-800' : 'text-red-800'}`}>
|
<p className={`mt-3 text-xs ${sshTestResult.success ? 'text-mossGreen-600' : 'text-sunsetCoral-500'}`}>
|
||||||
{sshTestResult.success ? 'SSH-Verbindung erfolgreich.' : `SSH-Verbindung fehlgeschlagen: ${sshTestResult.error}`}
|
{sshTestResult.success ? 'SSH-Verbindung erfolgreich.' : `SSH-Verbindung fehlgeschlagen: ${sshTestResult.error}`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -905,7 +945,7 @@ export function Maintenance() {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{scriptConfig?.customUpdatedAt && (
|
{scriptConfig?.customUpdatedAt && (
|
||||||
<p className="mt-2 text-xs text-gray-500">
|
<p className="mt-2 text-xs text-stormGrey-500">
|
||||||
Zuletzt geändert: {formatCreatedAt(scriptConfig.customUpdatedAt)}
|
Zuletzt geändert: {formatCreatedAt(scriptConfig.customUpdatedAt)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -940,29 +980,29 @@ export function Maintenance() {
|
|||||||
<span>{updateFinishedAt}</span>
|
<span>{updateFinishedAt}</span>
|
||||||
</div>
|
</div>
|
||||||
{updateStatusMessage && (
|
{updateStatusMessage && (
|
||||||
<div className="rounded-md border border-green-700 bg-green-800 px-3 py-2 text-xs text-white">
|
<div className="rounded-md border border-mossGreen-700 bg-mossGreen-800 px-3 py-2 text-xs text-white">
|
||||||
{updateStatusMessage}
|
{updateStatusMessage}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{updateState?.error && (
|
{updateState?.error && (
|
||||||
<div className="rounded-md border border-red-500/60 bg-red-900/40 px-3 py-2 text-xs text-red-200">
|
<div className="rounded-md border border-sunsetCoral-500/60 bg-sunsetCoral-900/40 px-3 py-2 text-xs text-white">
|
||||||
{updateState.error}
|
{updateState.error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{updateActionError && (
|
{updateActionError && (
|
||||||
<div className="rounded-md border border-red-500/60 bg-red-900/40 px-3 py-2 text-xs text-red-200">
|
<div className="rounded-md border border-sunsetCoral-500/60 bg-sunsetCoral-900/40 px-3 py-2 text-xs text-white">
|
||||||
{updateActionError}
|
{updateActionError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 h-40 overflow-y-auto rounded-md border border-gray-700/20 bg-gray-950/70 p-3 font-mono text-[11px] leading-relaxed">
|
<div className="mt-3 h-40 overflow-y-auto rounded-md border border-gray-700/20 bg-gray-950/70 p-3 font-mono text-[11px] leading-relaxed">
|
||||||
{updateLogs.length === 0 ? (
|
{updateLogs.length === 0 ? (
|
||||||
<p className="text-gray-500">Noch keine Protokolle vorhanden.</p>
|
<p className="text-stormGrey-500">Noch keine Protokolle vorhanden.</p>
|
||||||
) : (
|
) : (
|
||||||
updateLogs.map((entry, index) => (
|
updateLogs.map((entry, index) => (
|
||||||
<div key={`${entry.timestamp}-${index}`} className="mb-2 last:mb-0">
|
<div key={`${entry.timestamp}-${index}`} className="mb-2 last:mb-0">
|
||||||
<span className="text-gray-500">[{formatLogTimestamp(entry.timestamp)}]</span>{" "}
|
<span className="text-gray-500">[{formatLogTimestamp(entry.timestamp)}]</span>{" "}
|
||||||
<span className={(LOG_LEVEL_STYLES[entry.level] ?? "text-gray-300") + " whitespace-normal break-words"} style={{ overflowWrap: "anywhere", wordBreak: "normal" }}>
|
<span className={(LOG_LEVEL_STYLES[entry.level] ?? "text-stormGrey-300") + " whitespace-normal break-words"} style={{ overflowWrap: "anywhere", wordBreak: "normal" }}>
|
||||||
{entry.message}
|
{entry.message}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -984,46 +1024,124 @@ export function Maintenance() {
|
|||||||
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader
|
<CardHeader variant="gradient" color="gray" className="mb-5 p-4">
|
||||||
color="transparent"
|
<Typography
|
||||||
floated={false}
|
variant="h6"
|
||||||
shadow={false}
|
color="white"
|
||||||
className="m-0 p-4 block antialiased font-sans"
|
className="flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<Typography variant="h5" color="blue-gray" className="block antialiased font-sans">
|
<span>Doppelte Stacks</span>
|
||||||
Wartungsmodus
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody className="flex flex-col gap-4 p-4">
|
<CardBody className="flex flex-col gap-4 p-4">
|
||||||
|
<div className="rounded-lg border border-warmAmberGlow-600 bg-warmAmberGlow-700 px-4 py-3 text-sm text-white">
|
||||||
|
Das Entfernen von deppelten Stacks ist noch nicht getestet. Bitte nicht in Produktivumgebungen einsetzen.
|
||||||
|
</div>
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-sm text-blue-gray-200">
|
<p className="text-sm antialiased font-sans text-sm font-light leading-normal text-inherit">
|
||||||
Schaltet StackPulse für Benutzer in den Wartungsmodus.
|
{isDuplicatesDisabled
|
||||||
|
? "Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert."
|
||||||
|
: duplicatesLoading
|
||||||
|
? "Analyse läuft…"
|
||||||
|
: totals.groups === 0
|
||||||
|
? "Keine Duplikate gefunden"
|
||||||
|
: `${totals.groups} Stack-Namen mit insgesamt ${totals.duplicateCount} Duplikaten gefunden`}
|
||||||
</p>
|
</p>
|
||||||
{maintenanceMessage ? (<p className="text-xs text-gray-500">Hinweis: {maintenanceMessage}</p>) : null}
|
{duplicatesUpdatedAt && !duplicatesLoading && !isDuplicatesDisabled && (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="mt-1 text-xs text-gray-500">
|
||||||
{maintenanceActive
|
Stand: {duplicatesUpdatedAt.toLocaleString("de-DE", {
|
||||||
? `Aktiv seit ${maintenanceActivatedAt ?? "-"}.`
|
hour: "2-digit",
|
||||||
: `Zuletzt geändert: ${maintenanceUpdatedAt ?? "-"}.`}
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
{updateRunning && (
|
|
||||||
<p className="text-xs text-amber-600">Portainer-Update läuft – Umschalten vorübergehend gesperrt.</p>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => fetchDuplicates({ silent: false })}
|
||||||
|
disabled={isDuplicatesDisabled || duplicatesLoading || duplicatesRefreshing || activeCleanupId !== null}
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
Aktualisieren
|
||||||
|
</Button>
|
||||||
|
{duplicatesError && !isDuplicatesDisabled && (
|
||||||
|
<div className="rounded-lg border border-sunsetCoral-500/60 bg-sunsetCoral-900/40 px-4 py-3 text-sm text-white">
|
||||||
|
{duplicatesError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{duplicatesLoading ? (
|
||||||
|
<div className="flex flex-col gap-4 p-4">
|
||||||
|
Daten werden geladen…
|
||||||
|
</div>
|
||||||
|
) : totals.groups === 0 || isDuplicatesDisabled ? (
|
||||||
|
<div className="rounded-xl border-mossGreen-500/80 bg-mossGreen-900/90 text-mossGreen-100 p-8 text-center text-sm text-white">
|
||||||
|
{isDuplicatesDisabled
|
||||||
|
? "Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert."
|
||||||
|
: "Es wurden keine doppelten Stacks gefunden."}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-5">
|
||||||
|
{duplicates.map((entry) => {
|
||||||
|
const canonicalId = entry?.canonical?.Id;
|
||||||
|
const duplicatesForEntry = entry?.duplicates || [];
|
||||||
|
const isProcessing = activeCleanupId === String(canonicalId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={canonicalId || entry.name}
|
||||||
|
className="rounded-xl border border-gray-700 bg-gray-800/70 p-6 shadow"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="text-lg font-semibold text-white">{entry.name}</h3>
|
||||||
|
<span className="rounded-full bg-amber-500/20 px-3 py-0.5 text-xs font-medium text-amber-200">
|
||||||
|
{duplicatesForEntry.length} Duplikat{duplicatesForEntry.length === 1 ? "" : "e"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-300">
|
||||||
|
Behaltener Stack: ID {canonicalId} (Endpoint {entry?.canonical?.EndpointId ?? "-"})
|
||||||
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
Status: {maintenanceActive ? "Aktiv" : "Inaktiv"}
|
Typ: {resolveStackType(entry?.canonical?.Type)} • Erstellt: {formatCreatedAt(entry?.canonical?.Created)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
|
||||||
checked={maintenanceActive}
|
<button
|
||||||
disabled={maintenanceToggleLoading || maintenanceLoading || updateRunning}
|
type="button"
|
||||||
onChange={(event) => handleMaintenanceToggle(event.target.checked)}
|
onClick={() => handleCleanup(entry)}
|
||||||
ripple={false}
|
disabled={isDuplicatesDisabled || isProcessing || duplicatesRefreshing || duplicatesLoading}
|
||||||
color="amber"
|
className="self-start rounded-lg bg-sunsetCoral-500 px-4 py-2 text-sm font-semibold text-white transition hover:bg-sunsetCoral-600 disabled:opacity-50"
|
||||||
/>
|
>
|
||||||
|
{isProcessing ? "Bereinigung läuft…" : `Bereinigen (${duplicatesForEntry.length})`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-5 grid gap-3">
|
||||||
|
{duplicatesForEntry.map((duplicate) => (
|
||||||
|
<div
|
||||||
|
key={duplicate.Id}
|
||||||
|
className="rounded-lg border border-sunsetCoral-500/40 bg-sunsetCoral-900/20 p-4 text-sm text-white"
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
<span className="font-semibold text-white">ID: {duplicate.Id}</span>
|
||||||
|
<span>Endpoint: {duplicate.EndpointId ?? "-"}</span>
|
||||||
|
<span>Typ: {resolveStackType(duplicate.Type)}</span>
|
||||||
|
<span>Erstellt: {formatCreatedAt(duplicate.Created)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
{maintenanceToggleLoading && (
|
|
||||||
<p className="text-xs text-gray-500">Wartungsmodus wird aktualisiert…</p>
|
|
||||||
)}
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in new issue
Block a user