homelab: Plattenschwellen 80/90 % statt 85/95 % (ext4 haelt 5 % fuer root zurueck; AdGuard war bei 94 % innen voll)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c08a144006
commit
6a5134ef79
@@ -558,8 +558,8 @@ def pruefe_gaeste() -> list[Befund]:
|
||||
for e in inventar.erreichbarkeit() if not e["ok"]]
|
||||
|
||||
|
||||
GAST_PLATTE_GELB = 0.85
|
||||
GAST_PLATTE_ROT = 0.95
|
||||
GAST_PLATTE_GELB = 0.80
|
||||
GAST_PLATTE_ROT = 0.90 # ext4 hält 5 % für root zurück: bei 94 % war AdGuard innen schon voll
|
||||
|
||||
|
||||
def _gb_komma(n: float) -> str:
|
||||
|
||||
@@ -371,7 +371,7 @@ def test_waechter_und_uebersicht_sehen_volle_gastplatten(daten):
|
||||
if g["vmid"] == 100:
|
||||
g["platte"] = {"belegt": 2_000_000_000, "gesamt": 2_040_000_000}
|
||||
if g["vmid"] == 104:
|
||||
g["platte"] = {"belegt": 1_800_000_000, "gesamt": 2_000_000_000}
|
||||
g["platte"] = {"belegt": 1_700_000_000, "gesamt": 2_000_000_000}
|
||||
if g["vmid"] == 101:
|
||||
g["platte"] = {"belegt": 500_000_000, "gesamt": 2_000_000_000}
|
||||
kanal.bericht_speichern(bericht)
|
||||
|
||||
+1
-1
@@ -284,7 +284,7 @@ flowchart LR
|
||||
mit `MC_ARCANE_ECHT=1`. Die Arcane-VM braucht dafür kein Etikett, weil der Ausführer nicht beteiligt ist.
|
||||
- **Wächter** in der Rolle `homelab`: Platte, Partner (die KI-Box), Ausführer (kein Bericht seit 30 min = rot),
|
||||
jede Weboberfläche der freigegebenen Gäste (außer mitten in ihrem Update-Lauf; das Ergebnis meldet der Lauf), die
|
||||
Platten der laufenden Container (ab 85 % gelb, ab 95 % rot; der Ausführer schickt `platte` mit) und das
|
||||
Platten der laufenden Container (ab 80 % gelb, ab 90 % rot — ext4 hält 5 % für root zurück; der Ausführer schickt `platte` mit) und das
|
||||
wöchentliche Suchen (gelb, wenn es wiederholt scheitert).
|
||||
- **Oberfläche** (Bereich „Homelab“, nur Daten aus `/api/homelab/…`):
|
||||
- **Cockpit** (`/homelab`): oben die Lage wie das Warnpanel der KI-Box — die große Leuchte (Störung vor Hinweis vor
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ Wichtige Knöpfe fragen vorher nach. Was du auslöst, bestätigt eine kurze Meld
|
||||
- **Eine Lampe je Gerät** (Proxmox-Host, die Container, Arcane): grün mit der laufenden Version, cyan mit der neuen
|
||||
Version oder der Zahl der Pakete, rot bei „antwortet nicht“.
|
||||
- **Hinweise** des Homelab-Wächters, falls es welche gibt.
|
||||
- **Geräte:** je Gerät App-Version, Stand der Pakete, wie voll die Platte ist (ab 85 % bernstein, ab 95 % rot) und
|
||||
- **Geräte:** je Gerät App-Version, Stand der Pakete, wie voll die Platte ist (ab 80 % bernstein, ab 90 % rot) und
|
||||
ob es im Netz antwortet. „unklar“ heißt: Die Paketlisten im
|
||||
Gast sind alt. Der Homelab-Teil erneuert sie jede Nacht selbst, sobald sie älter als eine Woche sind; mit dem
|
||||
kleinen Pfeil daneben geht es sofort.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{c as e,i as t,p as n,s as r,t as i,u as a}from"./button-BheHBEml.js";import{B as o,Ct as s,T as c,ht as l,lt as u,ot as d}from"./index-BAH01Yfc.js";import{a as f,i as p,n as m,r as h,t as g}from"./sheet-CKp82pOA.js";var _=r(),v=n(a(),1),y=e();function b(e){let t=(0,_.c)(4),{unit:n}=e,r=o(n),i;if(r.isPending)i=`Wird gelesen …`;else if(r.isError)i=`Das Protokoll lässt sich gerade nicht lesen: ${r.error.message}`;else if(r.data.ok===!1)i=r.data.err||r.data.text||`Das Protokoll lässt sich nicht lesen.`;else{let e;t[0]===r.data.text?e=t[1]:(e=r.data.text?.trim()||`Noch keine Einträge.`,t[0]=r.data.text,t[1]=e),i=e}let a;return t[2]===i?a=t[3]:(a=(0,y.jsx)(`pre`,{className:`ziffern m-0 max-h-[50vh] w-full basis-full overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap break-all text-text-2`,children:i}),t[2]=i,t[3]=a),a}function x({offen:e,onSchliessen:n}){let r=s(),a=c(),[o,_]=(0,v.useState)(null),[x,S]=(0,v.useState)(null),[C,w]=(0,v.useState)(null);async function T(e){if(!C){S(null),w(e.unit);try{let t=await l(`/api/maintenance/restart`,{service:e.unit});t.ok?d(`erfolg`,`${e.name} ${e.schlaeft?`wird geweckt`:`startet neu`}.`):d(`fehler`,`${e.name} ließ sich nicht starten: ${u(t)}`),r.invalidateQueries({queryKey:[`dienste`]})}catch(e){d(`fehler`,e.message)}finally{w(null)}}}let E;return E=a.isPending?(0,y.jsx)(`p`,{role:`status`,className:`px-4 text-[15px] text-text-2`,children:`Dienste werden gelesen …`}):a.isError&&!a.data?(0,y.jsxs)(`p`,{role:`alert`,className:`px-4 text-[15px] text-rot-text`,children:[`Die Dienste lassen sich gerade nicht lesen: `,a.error.message]}):a.data?.services.length?(0,y.jsx)(`ul`,{className:`m-0 flex list-none flex-col px-4 pb-4`,children:a.data.services.map(e=>(0,y.jsxs)(`li`,{className:`flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5`,children:[(0,y.jsxs)(`span`,{className:`flex min-w-0 items-center gap-2.5`,children:[(0,y.jsx)(`span`,{className:t(`size-2.5 shrink-0 rounded-full`,e.ok?`bg-gruen`:e.schlaeft?`bg-text-3`:`bg-rot`),"aria-hidden":!0}),(0,y.jsxs)(`span`,{className:`min-w-0`,children:[(0,y.jsx)(`span`,{className:`block text-[15px]`,children:e.name}),(0,y.jsxs)(`span`,{className:`ziffern text-xs text-text-3`,children:[e.unit,` · `,e.ok?`läuft`:e.schlaeft?`schläft (abgeschaltet)`:`antwortet nicht`]})]})]}),(0,y.jsxs)(`span`,{className:`flex flex-wrap gap-2`,children:[(0,y.jsx)(i,{variant:o===e.unit?`info`:`ghost`,size:`sm`,"aria-expanded":o===e.unit,onClick:()=>_(o===e.unit?null:e.unit),children:`Protokoll`}),x===e.unit?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(i,{variant:`gefahr`,size:`sm`,onClick:()=>T(e),children:e.schlaeft?`Wirklich wecken?`:`Wirklich neu starten?`}),(0,y.jsx)(i,{variant:`ghost`,size:`sm`,onClick:()=>S(null),children:`Nein`})]}):(0,y.jsx)(i,{variant:`outline`,size:`sm`,disabled:C!==null,onClick:()=>S(e.unit),children:C===e.unit?`Startet …`:e.schlaeft?`Wecken`:`Neu starten`})]}),o===e.unit&&(0,y.jsx)(b,{unit:e.unit})]},e.unit))}):(0,y.jsx)(`p`,{className:`px-4 text-[15px] text-text-2`,children:`Keine Dienste gefunden.`}),(0,y.jsx)(g,{open:e,onOpenChange:e=>!e&&n(),children:(0,y.jsxs)(m,{side:`right`,className:`gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl`,children:[(0,y.jsxs)(p,{children:[(0,y.jsx)(f,{className:`schild text-lg`,children:`Dienste und Protokolle`}),(0,y.jsx)(h,{className:`text-text-2`,children:`Welcher Dienst läuft, was er zuletzt geschrieben hat.`})]}),E]})})}export{x as Dienste};
|
||||
import{c as e,i as t,p as n,s as r,t as i,u as a}from"./button-BheHBEml.js";import{B as o,Ct as s,T as c,ht as l,lt as u,ot as d}from"./index-C6xT7cKA.js";import{a as f,i as p,n as m,r as h,t as g}from"./sheet-CKp82pOA.js";var _=r(),v=n(a(),1),y=e();function b(e){let t=(0,_.c)(4),{unit:n}=e,r=o(n),i;if(r.isPending)i=`Wird gelesen …`;else if(r.isError)i=`Das Protokoll lässt sich gerade nicht lesen: ${r.error.message}`;else if(r.data.ok===!1)i=r.data.err||r.data.text||`Das Protokoll lässt sich nicht lesen.`;else{let e;t[0]===r.data.text?e=t[1]:(e=r.data.text?.trim()||`Noch keine Einträge.`,t[0]=r.data.text,t[1]=e),i=e}let a;return t[2]===i?a=t[3]:(a=(0,y.jsx)(`pre`,{className:`ziffern m-0 max-h-[50vh] w-full basis-full overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap break-all text-text-2`,children:i}),t[2]=i,t[3]=a),a}function x({offen:e,onSchliessen:n}){let r=s(),a=c(),[o,_]=(0,v.useState)(null),[x,S]=(0,v.useState)(null),[C,w]=(0,v.useState)(null);async function T(e){if(!C){S(null),w(e.unit);try{let t=await l(`/api/maintenance/restart`,{service:e.unit});t.ok?d(`erfolg`,`${e.name} ${e.schlaeft?`wird geweckt`:`startet neu`}.`):d(`fehler`,`${e.name} ließ sich nicht starten: ${u(t)}`),r.invalidateQueries({queryKey:[`dienste`]})}catch(e){d(`fehler`,e.message)}finally{w(null)}}}let E;return E=a.isPending?(0,y.jsx)(`p`,{role:`status`,className:`px-4 text-[15px] text-text-2`,children:`Dienste werden gelesen …`}):a.isError&&!a.data?(0,y.jsxs)(`p`,{role:`alert`,className:`px-4 text-[15px] text-rot-text`,children:[`Die Dienste lassen sich gerade nicht lesen: `,a.error.message]}):a.data?.services.length?(0,y.jsx)(`ul`,{className:`m-0 flex list-none flex-col px-4 pb-4`,children:a.data.services.map(e=>(0,y.jsxs)(`li`,{className:`flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5`,children:[(0,y.jsxs)(`span`,{className:`flex min-w-0 items-center gap-2.5`,children:[(0,y.jsx)(`span`,{className:t(`size-2.5 shrink-0 rounded-full`,e.ok?`bg-gruen`:e.schlaeft?`bg-text-3`:`bg-rot`),"aria-hidden":!0}),(0,y.jsxs)(`span`,{className:`min-w-0`,children:[(0,y.jsx)(`span`,{className:`block text-[15px]`,children:e.name}),(0,y.jsxs)(`span`,{className:`ziffern text-xs text-text-3`,children:[e.unit,` · `,e.ok?`läuft`:e.schlaeft?`schläft (abgeschaltet)`:`antwortet nicht`]})]})]}),(0,y.jsxs)(`span`,{className:`flex flex-wrap gap-2`,children:[(0,y.jsx)(i,{variant:o===e.unit?`info`:`ghost`,size:`sm`,"aria-expanded":o===e.unit,onClick:()=>_(o===e.unit?null:e.unit),children:`Protokoll`}),x===e.unit?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(i,{variant:`gefahr`,size:`sm`,onClick:()=>T(e),children:e.schlaeft?`Wirklich wecken?`:`Wirklich neu starten?`}),(0,y.jsx)(i,{variant:`ghost`,size:`sm`,onClick:()=>S(null),children:`Nein`})]}):(0,y.jsx)(i,{variant:`outline`,size:`sm`,disabled:C!==null,onClick:()=>S(e.unit),children:C===e.unit?`Startet …`:e.schlaeft?`Wecken`:`Neu starten`})]}),o===e.unit&&(0,y.jsx)(b,{unit:e.unit})]},e.unit))}):(0,y.jsx)(`p`,{className:`px-4 text-[15px] text-text-2`,children:`Keine Dienste gefunden.`}),(0,y.jsx)(g,{open:e,onOpenChange:e=>!e&&n(),children:(0,y.jsxs)(m,{side:`right`,className:`gap-3 overflow-y-auto border-linie bg-panel data-[side=right]:w-full data-[side=right]:sm:max-w-2xl`,children:[(0,y.jsxs)(p,{children:[(0,y.jsx)(f,{className:`schild text-lg`,children:`Dienste und Protokolle`}),(0,y.jsx)(h,{className:`text-text-2`,children:`Welcher Dienst läuft, was er zuletzt geschrieben hat.`})]}),E]})})}export{x as Dienste};
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{c as e,s as t}from"./button-BheHBEml.js";import{a as n,o as r}from"./index-BAH01Yfc.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-CKp82pOA.js";var l=t(),u=e();function d(e){let t=(0,l.c)(10),{onDienste:d,onEinstellungen:f,onSchliessen:p}=e,m;t[0]===p?m=t[1]:(m=e=>!e&&p(),t[0]=p,t[1]=m);let h;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(h=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=h):h=t[2];let g;t[3]!==d||t[4]!==f||t[5]!==p?(g=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-6 border-linie bg-[#0c0f12] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[h,(0,u.jsx)(r,{onDienste:d,onEinstellungen:f,onGewaehlt:p})]}),t[3]=d,t[4]=f,t[5]=p,t[6]=g):g=t[6];let _;return t[7]!==m||t[8]!==g?(_=(0,u.jsx)(c,{open:!0,onOpenChange:m,children:g}),t[7]=m,t[8]=g,t[9]=_):_=t[9],_}export{d as MenueSchublade};
|
||||
import{c as e,s as t}from"./button-BheHBEml.js";import{a as n,o as r}from"./index-C6xT7cKA.js";import{a as i,i as a,n as o,r as s,t as c}from"./sheet-CKp82pOA.js";var l=t(),u=e();function d(e){let t=(0,l.c)(10),{onDienste:d,onEinstellungen:f,onSchliessen:p}=e,m;t[0]===p?m=t[1]:(m=e=>!e&&p(),t[0]=p,t[1]=m);let h;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(h=(0,u.jsxs)(a,{className:`p-0`,children:[(0,u.jsx)(i,{asChild:!0,children:(0,u.jsx)(`div`,{children:(0,u.jsx)(n,{})})}),(0,u.jsx)(s,{className:`sr-only`,children:`Bereiche und Seiten`})]}),t[2]=h):h=t[2];let g;t[3]!==d||t[4]!==f||t[5]!==p?(g=(0,u.jsxs)(o,{side:`left`,className:`flex flex-col gap-6 border-linie bg-[#0c0f12] px-4 pt-5 pb-[calc(1.25rem+env(safe-area-inset-bottom))] data-[side=left]:w-[300px]`,children:[h,(0,u.jsx)(r,{onDienste:d,onEinstellungen:f,onGewaehlt:p})]}),t[3]=d,t[4]=f,t[5]=p,t[6]=g):g=t[6];let _;return t[7]!==m||t[8]!==g?(_=(0,u.jsx)(c,{open:!0,onOpenChange:m,children:g}),t[7]=m,t[8]=g,t[9]=_):_=t[9],_}export{d as MenueSchublade};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,9 +7,9 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Homelab Orchestrator</title>
|
||||
<script type="module" crossorigin src="/assets/index-BAH01Yfc.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-C6xT7cKA.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/button-BheHBEml.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cb8UJMqV.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BGR43ZZ7.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -42,7 +42,7 @@ function SystemText({ b }: { b: BausteinStand }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Belegung der Platte: ruhig bis 85 %, dann bernstein, ab 95 % rot. */
|
||||
/** Belegung der Platte: ruhig bis 80 %, dann bernstein, ab 90 % rot. */
|
||||
function PlatteZelle({ ziel }: { ziel: ZielStand }) {
|
||||
const anteil = plattenAnteil(ziel)
|
||||
if (anteil === null) return <span className="text-text-3">–</span>
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("GeraeteListe", () => {
|
||||
expect(start).toHaveBeenCalledWith(suchen)
|
||||
})
|
||||
|
||||
it("zeigt die Belegung der Platte und sagt „voll“ ab 95 %", () => {
|
||||
it("zeigt die Belegung der Platte und sagt „voll“ ab 90 %", () => {
|
||||
render(<GeraeteListe ziele={[{ ...netbird, platte: { belegt: 1_960_000_000, gesamt: 2_000_000_000 } }]}
|
||||
laufend={new Map()} gesperrt={false} onAktion={() => {}} />)
|
||||
expect(screen.getByText("98 % voll")).toHaveAttribute("title", "2,0 von 2,0 GB belegt")
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("lampeFuer", () => {
|
||||
|
||||
it("warnt bei voller Platte vor allem anderen außer Stille", () => {
|
||||
expect(lampeFuer({ ...netbird, platte: { belegt: 98, gesamt: 100 } }, true)).toMatchObject({ zustand: "fehler", wert: "Platte voll" })
|
||||
expect(lampeFuer({ ...npm, platte: { belegt: 90, gesamt: 100 } }, false)).toMatchObject({ zustand: "warn", wert: "Platte 90 %" })
|
||||
expect(lampeFuer({ ...npm, platte: { belegt: 85, gesamt: 100 } }, false)).toMatchObject({ zustand: "warn", wert: "Platte 85 %" })
|
||||
expect(lampeFuer({ ...npm, platte: { belegt: 50, gesamt: 100 } }, false).zustand).toBe("ok")
|
||||
})
|
||||
|
||||
|
||||
@@ -81,14 +81,14 @@ export function neuText(b: BausteinStand): string {
|
||||
return b.kurz || b.verfuegbar || "neu"
|
||||
}
|
||||
|
||||
/** Anteil der belegten Platte (0…1) oder null, wo unbekannt. Ab 85 % gelb, ab 95 % rot — wie der Wächter. */
|
||||
/** Anteil der belegten Platte (0…1) oder null, wo unbekannt. Ab 80 % gelb, ab 90 % rot — wie der Wächter (ext4 hält 5 % zurück). */
|
||||
export function plattenAnteil(ziel: ZielStand): number | null {
|
||||
const p = ziel.platte
|
||||
return p && p.gesamt > 0 ? p.belegt / p.gesamt : null
|
||||
}
|
||||
|
||||
export const PLATTE_GELB = 0.85
|
||||
export const PLATTE_ROT = 0.95
|
||||
export const PLATTE_GELB = 0.8
|
||||
export const PLATTE_ROT = 0.9
|
||||
|
||||
export function lampeFuer(ziel: ZielStand, laeuft: boolean): Lampe {
|
||||
const label = kurzname(ziel)
|
||||
|
||||
Reference in New Issue
Block a user