boxwart: Start wartet nie auf die Update-Suche im Netz, Verlauf am Handy kompakter
Ampel / ampel (push) Failing after 20s
Ampel / ampel (push) Failing after 20s
Nach einem MC2-Neustart hing /api/start, bis GitHub, apt und Hugging Face geantwortet hatten. Jetzt kommt der letzte Stand sofort, frisch geholt wird im Hintergrund; bis dahin steht "wird geprueft" da. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ed6948429a
commit
4b226d5d22
+25
-10
@@ -36,19 +36,31 @@ router = APIRouter(prefix="/api", tags=["boxwart"])
|
|||||||
LOCAL_TZ = ZoneInfo(os.environ.get("MC_LOCAL_TZ", "Europe/Berlin"))
|
LOCAL_TZ = ZoneInfo(os.environ.get("MC_LOCAL_TZ", "Europe/Berlin"))
|
||||||
|
|
||||||
_updates_lock = threading.Lock()
|
_updates_lock = threading.Lock()
|
||||||
_updates_cache: dict = {"ts": 0.0, "daten": None}
|
_updates_cache: dict = {"ts": 0.0, "daten": None, "laeuft": False}
|
||||||
UPDATES_CACHE_S = 600
|
UPDATES_CACHE_S = 600
|
||||||
|
|
||||||
|
|
||||||
def _updates_gecacht() -> dict:
|
def _updates_holen() -> None:
|
||||||
"""maintenance.updates() fragt GitHub, apt und Hugging Face — das darf den Start nicht bremsen."""
|
try:
|
||||||
|
daten = maintenance.updates()
|
||||||
|
except Exception:
|
||||||
|
daten = None
|
||||||
with _updates_lock:
|
with _updates_lock:
|
||||||
if _updates_cache["daten"] is None or time.time() - _updates_cache["ts"] > UPDATES_CACHE_S:
|
if daten is not None or _updates_cache["daten"] is None:
|
||||||
try:
|
_updates_cache["daten"] = daten or {}
|
||||||
_updates_cache["daten"] = maintenance.updates()
|
_updates_cache["ts"] = time.time()
|
||||||
except Exception:
|
_updates_cache["laeuft"] = False
|
||||||
_updates_cache["daten"] = _updates_cache["daten"] or {}
|
|
||||||
_updates_cache["ts"] = time.time()
|
|
||||||
|
def _updates_gecacht() -> dict:
|
||||||
|
"""maintenance.updates() fragt GitHub, apt und Hugging Face und braucht nach einem Neustart
|
||||||
|
gern zehn Sekunden und mehr. Der Start wartet darauf nie: Er bekommt den letzten Stand (oder
|
||||||
|
zunächst nichts), frisch geholt wird im Hintergrund."""
|
||||||
|
with _updates_lock:
|
||||||
|
veraltet = _updates_cache["daten"] is None or time.time() - _updates_cache["ts"] > UPDATES_CACHE_S
|
||||||
|
if veraltet and not _updates_cache["laeuft"]:
|
||||||
|
_updates_cache["laeuft"] = True
|
||||||
|
threading.Thread(target=_updates_holen, name="updates-holen", daemon=True).start()
|
||||||
return _updates_cache["daten"] or {}
|
return _updates_cache["daten"] or {}
|
||||||
|
|
||||||
|
|
||||||
@@ -129,6 +141,8 @@ def _lampen(stand: dict, u: dict) -> list[dict]:
|
|||||||
fest = len(update_verlauf.festgehalten())
|
fest = len(update_verlauf.festgehalten())
|
||||||
if fest:
|
if fest:
|
||||||
lampen.append(lampe("updates", "Updates", "warn", f"{fest} festgehalten"))
|
lampen.append(lampe("updates", "Updates", "warn", f"{fest} festgehalten"))
|
||||||
|
elif _updates_cache["daten"] is None:
|
||||||
|
lampen.append(lampe("updates", "Updates", "aus", "wird geprüft"))
|
||||||
else:
|
else:
|
||||||
lampen.append(lampe("updates", "Updates", "info" if n else "ok", f"{n} bereit" if n else "aktuell"))
|
lampen.append(lampe("updates", "Updates", "info" if n else "ok", f"{n} bereit" if n else "aktuell"))
|
||||||
return lampen
|
return lampen
|
||||||
@@ -166,7 +180,8 @@ def start() -> dict:
|
|||||||
"lampen": _lampen(stand, u),
|
"lampen": _lampen(stand, u),
|
||||||
"box": _box(),
|
"box": _box(),
|
||||||
"updates": {"bausteine": _bausteine(u), "modelle": u.get("model_list") or [],
|
"updates": {"bausteine": _bausteine(u), "modelle": u.get("model_list") or [],
|
||||||
"festgehalten": update_verlauf.festgehalten()},
|
"festgehalten": update_verlauf.festgehalten(),
|
||||||
|
"gelesen": _updates_cache["daten"] is not None},
|
||||||
"flugplan": zeitplan.flugplan(),
|
"flugplan": zeitplan.flugplan(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
import{c as e,f as t,i as n,t as r,u as i}from"./button-Dd8hfusv.js";import{F as a,O as o,_ as s,k as c,u as l}from"./index-DaRexeYf.js";import{a as u,i as d,n as f,r as p,t as m}from"./sheet-DWG2Q04V.js";var h=t(i(),1),g=e();function _({offen:e,onSchliessen:t}){let i=a(),_=l(),[v,y]=(0,h.useState)(null),[b,x]=(0,h.useState)(null),S=s(v);async function C(e){x(null);try{let t=await o(`/api/maintenance/restart`,{service:e});t.ok?c(`erfolg`,`${e} startet neu.`):c(`fehler`,t.err||`${e} ließ sich nicht neu starten.`),i.invalidateQueries({queryKey:[`dienste`]})}catch(e){c(`fehler`,e.message)}}return(0,g.jsx)(m,{open:e,onOpenChange:e=>!e&&t(),children:(0,g.jsxs)(f,{side:`right`,className:`w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-2xl`,children:[(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{className:`schild text-lg`,children:`Dienste und Protokolle`}),(0,g.jsx)(p,{className:`text-text-2`,children:`Welcher Dienst läuft, was er zuletzt geschrieben hat.`})]}),(0,g.jsx)(`ul`,{className:`m-0 flex list-none flex-col px-4 pb-2`,children:(_.data?.services??[]).map(e=>(0,g.jsxs)(`li`,{className:`flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5`,children:[(0,g.jsxs)(`span`,{className:`flex min-w-0 items-center gap-2.5`,children:[(0,g.jsx)(`span`,{className:n(`size-2.5 shrink-0 rounded-full`,e.ok?`bg-gruen`:`bg-rot`),"aria-hidden":!0}),(0,g.jsxs)(`span`,{className:`min-w-0`,children:[(0,g.jsx)(`span`,{className:`block text-[15px]`,children:e.name}),(0,g.jsxs)(`span`,{className:`ziffern text-xs text-text-3`,children:[e.unit,` · `,e.ok?`läuft`:`antwortet nicht`]})]})]}),(0,g.jsxs)(`span`,{className:`flex gap-2`,children:[(0,g.jsx)(r,{variant:v===e.unit?`info`:`ghost`,size:`sm`,onClick:()=>y(e.unit),children:`Protokoll`}),b===e.unit?(0,g.jsx)(r,{variant:`gefahr`,size:`sm`,onClick:()=>C(e.unit),children:`Wirklich neu starten?`}):(0,g.jsx)(r,{variant:`outline`,size:`sm`,onClick:()=>x(e.unit),children:`Neu starten`})]})]},e.unit))}),v&&(0,g.jsx)(`pre`,{className:`ziffern mx-4 mb-4 max-h-[50vh] overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2`,children:S.isFetching?`Wird gelesen …`:S.data?.text||S.data?.err||`Kein Protokoll.`})]})})}export{_ as Dienste};
|
import{c as e,f as t,i as n,t as r,u as i}from"./button-Dd8hfusv.js";import{F as a,O as o,_ as s,k as c,u as l}from"./index-B6MevgIX.js";import{a as u,i as d,n as f,r as p,t as m}from"./sheet-DWG2Q04V.js";var h=t(i(),1),g=e();function _({offen:e,onSchliessen:t}){let i=a(),_=l(),[v,y]=(0,h.useState)(null),[b,x]=(0,h.useState)(null),S=s(v);async function C(e){x(null);try{let t=await o(`/api/maintenance/restart`,{service:e});t.ok?c(`erfolg`,`${e} startet neu.`):c(`fehler`,t.err||`${e} ließ sich nicht neu starten.`),i.invalidateQueries({queryKey:[`dienste`]})}catch(e){c(`fehler`,e.message)}}return(0,g.jsx)(m,{open:e,onOpenChange:e=>!e&&t(),children:(0,g.jsxs)(f,{side:`right`,className:`w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-2xl`,children:[(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{className:`schild text-lg`,children:`Dienste und Protokolle`}),(0,g.jsx)(p,{className:`text-text-2`,children:`Welcher Dienst läuft, was er zuletzt geschrieben hat.`})]}),(0,g.jsx)(`ul`,{className:`m-0 flex list-none flex-col px-4 pb-2`,children:(_.data?.services??[]).map(e=>(0,g.jsxs)(`li`,{className:`flex flex-wrap items-center justify-between gap-2 border-t border-linie py-2.5`,children:[(0,g.jsxs)(`span`,{className:`flex min-w-0 items-center gap-2.5`,children:[(0,g.jsx)(`span`,{className:n(`size-2.5 shrink-0 rounded-full`,e.ok?`bg-gruen`:`bg-rot`),"aria-hidden":!0}),(0,g.jsxs)(`span`,{className:`min-w-0`,children:[(0,g.jsx)(`span`,{className:`block text-[15px]`,children:e.name}),(0,g.jsxs)(`span`,{className:`ziffern text-xs text-text-3`,children:[e.unit,` · `,e.ok?`läuft`:`antwortet nicht`]})]})]}),(0,g.jsxs)(`span`,{className:`flex gap-2`,children:[(0,g.jsx)(r,{variant:v===e.unit?`info`:`ghost`,size:`sm`,onClick:()=>y(e.unit),children:`Protokoll`}),b===e.unit?(0,g.jsx)(r,{variant:`gefahr`,size:`sm`,onClick:()=>C(e.unit),children:`Wirklich neu starten?`}):(0,g.jsx)(r,{variant:`outline`,size:`sm`,onClick:()=>x(e.unit),children:`Neu starten`})]})]},e.unit))}),v&&(0,g.jsx)(`pre`,{className:`ziffern mx-4 mb-4 max-h-[50vh] overflow-auto rounded-lg border border-linie bg-[#0b0d0f] p-3 text-xs leading-relaxed whitespace-pre-wrap text-text-2`,children:S.isFetching?`Wird gelesen …`:S.data?.text||S.data?.err||`Kein Protokoll.`})]})})}export{_ as Dienste};
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
import{c as e,f as t,t as n,u as r}from"./button-Dd8hfusv.js";import{D as i,F as a,N as o,O as s,P as c,k as l}from"./index-DaRexeYf.js";import{a as u,i as d,n as f,r as p,t as m}from"./sheet-DWG2Q04V.js";var h=t(r(),1),g=e();function _({offen:e,onSchliessen:t}){let r=a(),_=c({queryKey:[`geheimnisse`],queryFn:()=>i(`/api/maintenance/geheimnisse`),enabled:e}),[v,y]=(0,h.useState)(``);async function b(e){try{await s(`/api/maintenance/geheimnisse`,{schluessel:`hf_token`,wert:e}),l(`erfolg`,e?`Hugging-Face-Zugang gespeichert.`:`Hugging-Face-Zugang gelöscht.`),y(``),r.invalidateQueries({queryKey:[`geheimnisse`]})}catch(e){l(`fehler`,e.message)}}let x=_.data;return(0,g.jsx)(m,{open:e,onOpenChange:e=>!e&&t(),children:(0,g.jsxs)(f,{side:`right`,className:`w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-lg`,children:[(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{className:`schild text-lg`,children:`Einstellungen`}),(0,g.jsx)(p,{className:`text-text-2`,children:`Was die Box sich merkt.`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-3 border-t border-linie px-4 pt-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hugging-Face-Zugang`}),(0,g.jsxs)(`p`,{className:`text-sm text-text-2`,children:[x?.hf_token_gesetzt?`Ist hinterlegt.`:`Fehlt.`,` Nötig für gesperrte Modelle und schnellere Downloads.`,x?.hf_token_aus_env?` Er kommt aus der Dienst-Einstellung und lässt sich hier nicht löschen.`:``]}),(0,g.jsxs)(`form`,{className:`flex gap-2`,onSubmit:e=>{e.preventDefault(),v.trim()&&b(v.trim())},children:[(0,g.jsx)(`label`,{htmlFor:`hf-token`,className:`sr-only`,children:`Hugging-Face-Zugang`}),(0,g.jsx)(`input`,{id:`hf-token`,type:`password`,autoComplete:`off`,value:v,onChange:e=>y(e.target.value),placeholder:`hf_…`,className:`h-11 min-w-0 flex-1 rounded-lg border border-linie-stark bg-erhaben px-3 text-[15px] outline-none focus:border-cyan`}),(0,g.jsx)(n,{type:`submit`,disabled:!v.trim()||x?.schreibbar===!1,children:`Speichern`})]}),x?.hf_token_gesetzt&&!x.hf_token_aus_env&&(0,g.jsx)(n,{variant:`ghost`,size:`sm`,className:`self-start`,onClick:()=>b(null),children:`Zugang löschen`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-2 border-t border-linie px-4 pt-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hinweise`}),(0,g.jsx)(`p`,{className:`text-sm text-text-2`,children:`Dringende Hinweise gehen zusätzlich per Telegram raus, alles andere bleibt hier. Einfache Probleme wie einen abgestürzten Dienst behebt der Wächter selbst und schreibt es in den Verlauf.`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-2 border-t border-linie px-4 pt-4 pb-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hermes`}),(0,g.jsxs)(`a`,{href:`/hermes-ui/`,target:`_blank`,rel:`noopener`,className:`flex h-11 items-center gap-2 self-start rounded-lg border border-linie-stark bg-erhaben px-4 text-[15px] hover:border-text-3`,children:[(0,g.jsx)(o,{className:`size-4`,"aria-hidden":!0}),` Hermes-Dashboard öffnen`]}),(0,g.jsx)(`p`,{className:`text-xs text-text-3`,children:`Chat, Sitzungen und Cron-Jobs verwaltet Hermes selbst. Es fragt nach seiner eigenen Anmeldung.`})]})]})})}export{_ as Einstellungen};
|
import{c as e,f as t,t as n,u as r}from"./button-Dd8hfusv.js";import{D as i,F as a,N as o,O as s,P as c,k as l}from"./index-B6MevgIX.js";import{a as u,i as d,n as f,r as p,t as m}from"./sheet-DWG2Q04V.js";var h=t(r(),1),g=e();function _({offen:e,onSchliessen:t}){let r=a(),_=c({queryKey:[`geheimnisse`],queryFn:()=>i(`/api/maintenance/geheimnisse`),enabled:e}),[v,y]=(0,h.useState)(``);async function b(e){try{await s(`/api/maintenance/geheimnisse`,{schluessel:`hf_token`,wert:e}),l(`erfolg`,e?`Hugging-Face-Zugang gespeichert.`:`Hugging-Face-Zugang gelöscht.`),y(``),r.invalidateQueries({queryKey:[`geheimnisse`]})}catch(e){l(`fehler`,e.message)}}let x=_.data;return(0,g.jsx)(m,{open:e,onOpenChange:e=>!e&&t(),children:(0,g.jsxs)(f,{side:`right`,className:`w-full gap-3 overflow-y-auto border-linie bg-panel sm:max-w-lg`,children:[(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{className:`schild text-lg`,children:`Einstellungen`}),(0,g.jsx)(p,{className:`text-text-2`,children:`Was die Box sich merkt.`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-3 border-t border-linie px-4 pt-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hugging-Face-Zugang`}),(0,g.jsxs)(`p`,{className:`text-sm text-text-2`,children:[x?.hf_token_gesetzt?`Ist hinterlegt.`:`Fehlt.`,` Nötig für gesperrte Modelle und schnellere Downloads.`,x?.hf_token_aus_env?` Er kommt aus der Dienst-Einstellung und lässt sich hier nicht löschen.`:``]}),(0,g.jsxs)(`form`,{className:`flex gap-2`,onSubmit:e=>{e.preventDefault(),v.trim()&&b(v.trim())},children:[(0,g.jsx)(`label`,{htmlFor:`hf-token`,className:`sr-only`,children:`Hugging-Face-Zugang`}),(0,g.jsx)(`input`,{id:`hf-token`,type:`password`,autoComplete:`off`,value:v,onChange:e=>y(e.target.value),placeholder:`hf_…`,className:`h-11 min-w-0 flex-1 rounded-lg border border-linie-stark bg-erhaben px-3 text-[15px] outline-none focus:border-cyan`}),(0,g.jsx)(n,{type:`submit`,disabled:!v.trim()||x?.schreibbar===!1,children:`Speichern`})]}),x?.hf_token_gesetzt&&!x.hf_token_aus_env&&(0,g.jsx)(n,{variant:`ghost`,size:`sm`,className:`self-start`,onClick:()=>b(null),children:`Zugang löschen`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-2 border-t border-linie px-4 pt-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hinweise`}),(0,g.jsx)(`p`,{className:`text-sm text-text-2`,children:`Dringende Hinweise gehen zusätzlich per Telegram raus, alles andere bleibt hier. Einfache Probleme wie einen abgestürzten Dienst behebt der Wächter selbst und schreibt es in den Verlauf.`})]}),(0,g.jsxs)(`section`,{className:`flex flex-col gap-2 border-t border-linie px-4 pt-4 pb-4`,children:[(0,g.jsx)(`h3`,{className:`schild text-sm text-text-3`,children:`Hermes`}),(0,g.jsxs)(`a`,{href:`/hermes-ui/`,target:`_blank`,rel:`noopener`,className:`flex h-11 items-center gap-2 self-start rounded-lg border border-linie-stark bg-erhaben px-4 text-[15px] hover:border-text-3`,children:[(0,g.jsx)(o,{className:`size-4`,"aria-hidden":!0}),` Hermes-Dashboard öffnen`]}),(0,g.jsx)(`p`,{className:`text-xs text-text-3`,children:`Chat, Sitzungen und Cron-Jobs verwaltet Hermes selbst. Es fragt nach seiner eigenen Anmeldung.`})]})]})})}export{_ as Einstellungen};
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
import{c as e,s as t}from"./button-Dd8hfusv.js";import{M as n,N as r,j as i}from"./index-DaRexeYf.js";import{a,i as o,n as s,r as c,t as l}from"./sheet-DWG2Q04V.js";var u=t(),d=e();function f(e){let t=(0,u.c)(17),{verbindung:f,onDienste:p,onEinstellungen:m,onSchliessen:h}=e,g;t[0]===h?g=t[1]:(g=e=>!e&&h(),t[0]=h,t[1]=g);let _;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(_=(0,d.jsxs)(o,{children:[(0,d.jsx)(a,{className:`schild text-lg`,children:`Mehr`}),(0,d.jsx)(c,{className:`sr-only`,children:`Weitere Bereiche`})]}),t[2]=_):_=t[2];let v;t[3]===Symbol.for(`react.memo_cache_sentinel`)?(v=(0,d.jsxs)(`a`,{href:`/hermes-ui/`,target:`_blank`,rel:`noopener`,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-base`,children:[(0,d.jsx)(r,{className:`size-5`,"aria-hidden":!0}),` Hermes-Dashboard öffnen`]}),t[3]=v):v=t[3];let y;t[4]===Symbol.for(`react.memo_cache_sentinel`)?(y=(0,d.jsx)(n,{className:`size-5`,"aria-hidden":!0}),t[4]=y):y=t[4];let b;t[5]===p?b=t[6]:(b=(0,d.jsxs)(`button`,{type:`button`,onClick:p,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-left text-base`,children:[y,` Dienste und Protokolle`]}),t[5]=p,t[6]=b);let x;t[7]===Symbol.for(`react.memo_cache_sentinel`)?(x=(0,d.jsx)(i,{className:`size-5`,"aria-hidden":!0}),t[7]=x):x=t[7];let S;t[8]===m?S=t[9]:(S=(0,d.jsxs)(`button`,{type:`button`,onClick:m,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-left text-base`,children:[x,` Einstellungen`]}),t[8]=m,t[9]=S);let C;t[10]!==b||t[11]!==S||t[12]!==f?(C=(0,d.jsxs)(s,{side:`bottom`,className:`border-linie bg-panel pb-[calc(1rem+env(safe-area-inset-bottom))]`,children:[_,(0,d.jsxs)(`div`,{className:`flex flex-col gap-2 px-4`,children:[f,v,b,S]})]}),t[10]=b,t[11]=S,t[12]=f,t[13]=C):C=t[13];let w;return t[14]!==g||t[15]!==C?(w=(0,d.jsx)(l,{open:!0,onOpenChange:g,children:C}),t[14]=g,t[15]=C,t[16]=w):w=t[16],w}export{f as MehrMenue};
|
import{c as e,s as t}from"./button-Dd8hfusv.js";import{M as n,N as r,j as i}from"./index-B6MevgIX.js";import{a,i as o,n as s,r as c,t as l}from"./sheet-DWG2Q04V.js";var u=t(),d=e();function f(e){let t=(0,u.c)(17),{verbindung:f,onDienste:p,onEinstellungen:m,onSchliessen:h}=e,g;t[0]===h?g=t[1]:(g=e=>!e&&h(),t[0]=h,t[1]=g);let _;t[2]===Symbol.for(`react.memo_cache_sentinel`)?(_=(0,d.jsxs)(o,{children:[(0,d.jsx)(a,{className:`schild text-lg`,children:`Mehr`}),(0,d.jsx)(c,{className:`sr-only`,children:`Weitere Bereiche`})]}),t[2]=_):_=t[2];let v;t[3]===Symbol.for(`react.memo_cache_sentinel`)?(v=(0,d.jsxs)(`a`,{href:`/hermes-ui/`,target:`_blank`,rel:`noopener`,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-base`,children:[(0,d.jsx)(r,{className:`size-5`,"aria-hidden":!0}),` Hermes-Dashboard öffnen`]}),t[3]=v):v=t[3];let y;t[4]===Symbol.for(`react.memo_cache_sentinel`)?(y=(0,d.jsx)(n,{className:`size-5`,"aria-hidden":!0}),t[4]=y):y=t[4];let b;t[5]===p?b=t[6]:(b=(0,d.jsxs)(`button`,{type:`button`,onClick:p,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-left text-base`,children:[y,` Dienste und Protokolle`]}),t[5]=p,t[6]=b);let x;t[7]===Symbol.for(`react.memo_cache_sentinel`)?(x=(0,d.jsx)(i,{className:`size-5`,"aria-hidden":!0}),t[7]=x):x=t[7];let S;t[8]===m?S=t[9]:(S=(0,d.jsxs)(`button`,{type:`button`,onClick:m,className:`flex h-12 items-center gap-3 rounded-lg border border-linie px-4 text-left text-base`,children:[x,` Einstellungen`]}),t[8]=m,t[9]=S);let C;t[10]!==b||t[11]!==S||t[12]!==f?(C=(0,d.jsxs)(s,{side:`bottom`,className:`border-linie bg-panel pb-[calc(1rem+env(safe-area-inset-bottom))]`,children:[_,(0,d.jsxs)(`div`,{className:`flex flex-col gap-2 px-4`,children:[f,v,b,S]})]}),t[10]=b,t[11]=S,t[12]=f,t[13]=C):C=t[13];let w;return t[14]!==g||t[15]!==C?(w=(0,d.jsx)(l,{open:!0,onOpenChange:g,children:C}),t[14]=g,t[15]=C,t[16]=w):w=t[16],w}export{f as MehrMenue};
|
||||||
+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
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
-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="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>MC2 · Box-Wart</title>
|
<title>MC2 · Box-Wart</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DaRexeYf.js"></script>
|
<script type="module" crossorigin src="/assets/index-B6MevgIX.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/button-Dd8hfusv.js">
|
<link rel="modulepreload" crossorigin href="/assets/button-Dd8hfusv.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-kGmxGfY5.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D4C10235.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ import type {
|
|||||||
const takt = (ms: number) => () => (stromVerbunden() ? ms * 5 : ms)
|
const takt = (ms: number) => () => (stromVerbunden() ? ms * 5 : ms)
|
||||||
|
|
||||||
export const useStart = () =>
|
export const useStart = () =>
|
||||||
useQuery({ queryKey: ["start"], queryFn: () => api<Start>("/api/start"), refetchInterval: takt(20_000) })
|
useQuery({
|
||||||
|
queryKey: ["start"],
|
||||||
|
queryFn: () => api<Start>("/api/start"),
|
||||||
|
// Sucht MC2 gerade noch nach Updates (nach einem Neustart), bald wieder nachsehen.
|
||||||
|
refetchInterval: (q) => (q.state.data && !q.state.data.updates.gelesen ? 4000 : takt(20_000)()),
|
||||||
|
})
|
||||||
|
|
||||||
export const useModelle = () =>
|
export const useModelle = () =>
|
||||||
useQuery({
|
useQuery({
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export interface Start {
|
|||||||
bausteine: { id: string; name: string; neu: string }[]
|
bausteine: { id: string; name: string; neu: string }[]
|
||||||
modelle: { role: string; title: string; repo: string }[]
|
modelle: { role: string; title: string; repo: string }[]
|
||||||
festgehalten: Festgehalten[]
|
festgehalten: Festgehalten[]
|
||||||
|
/** false, solange MC2 nach einem Neustart noch im Netz nach Updates sucht. */
|
||||||
|
gelesen: boolean
|
||||||
}
|
}
|
||||||
flugplan: { gelaufen: FlugplanEintrag[]; geplant: FlugplanEintrag[] }
|
flugplan: { gelaufen: FlugplanEintrag[]; geplant: FlugplanEintrag[] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ const BAUSTEINE = [
|
|||||||
{ id: "hermes", name: "Hermes" },
|
{ id: "hermes", name: "Hermes" },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
function BausteinZeile({ id, name, hatUpdate, fest, onStart, onFreigeben, laeuftGerade }: {
|
function BausteinZeile({ id, name, gelesen, hatUpdate, fest, onStart, onFreigeben, laeuftGerade }: {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
gelesen: boolean
|
||||||
hatUpdate: boolean
|
hatUpdate: boolean
|
||||||
fest: Festgehalten | undefined
|
fest: Festgehalten | undefined
|
||||||
onStart: () => void
|
onStart: () => void
|
||||||
@@ -31,7 +32,9 @@ function BausteinZeile({ id, name, hatUpdate, fest, onStart, onFreigeben, laeuft
|
|||||||
const { data, isPending } = useUpdateDetails(id)
|
const { data, isPending } = useUpdateDetails(id)
|
||||||
const v = versionen(id, data)
|
const v = versionen(id, data)
|
||||||
const neu = hatUpdate ? v.neu ?? "neu" : null
|
const neu = hatUpdate ? v.neu ?? "neu" : null
|
||||||
const was = !hatUpdate
|
const was = !gelesen
|
||||||
|
? "Wird gerade geprüft …"
|
||||||
|
: !hatUpdate
|
||||||
? "Aktuell."
|
? "Aktuell."
|
||||||
: data?.summary?.trim() || (id === "os" && data?.count ? "Paketliste unten in den Details." : isPending ? "Wird gelesen …" : "")
|
: data?.summary?.trim() || (id === "os" && data?.count ? "Paketliste unten in den Details." : isPending ? "Wird gelesen …" : "")
|
||||||
const neuText = neu ? <span className="text-cyan">{neu}</span> : <span className="text-text-3">–</span>
|
const neuText = neu ? <span className="text-cyan">{neu}</span> : <span className="text-text-3">–</span>
|
||||||
@@ -86,13 +89,13 @@ const PUNKT: Record<UpdateLauf["stufe"], string> = {
|
|||||||
function LaufEintrag({ lauf }: { lauf: UpdateLauf }) {
|
function LaufEintrag({ lauf }: { lauf: UpdateLauf }) {
|
||||||
return (
|
return (
|
||||||
<li className="flex flex-col gap-2 border-t border-linie py-3">
|
<li className="flex flex-col gap-2 border-t border-linie py-3">
|
||||||
<div className="grid grid-cols-[112px_minmax(0,1fr)_auto] items-center gap-3">
|
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 md:grid-cols-[112px_minmax(0,1fr)_auto]">
|
||||||
<span className="ziffern text-sm text-text-3">{flugplanZeit(lauf.start)}</span>
|
<span className="ziffern text-sm text-text-3">{flugplanZeit(lauf.start)}</span>
|
||||||
<span className="min-w-0 text-[15px]">{lauf.anlass}</span>
|
<span className="min-w-0 text-[15px]">{lauf.anlass}</span>
|
||||||
<Chip art={LAUF_ART[lauf.stufe]}>{lauf.titel}</Chip>
|
<Chip art={LAUF_ART[lauf.stufe]}>{lauf.titel}</Chip>
|
||||||
</div>
|
</div>
|
||||||
{lauf.stufe !== "grau" && lauf.zeilen.length > 0 && (
|
{lauf.stufe !== "grau" && lauf.zeilen.length > 0 && (
|
||||||
<ul className="m-0 flex list-none flex-col gap-1 p-0 sm:pl-[124px]">
|
<ul className="m-0 flex list-none flex-col gap-1 p-0 md:pl-[124px]">
|
||||||
{lauf.zeilen.map((z) => (
|
{lauf.zeilen.map((z) => (
|
||||||
<li key={`${z.baustein}-${z.text}`} className="flex items-baseline gap-2 text-sm leading-snug text-text-2">
|
<li key={`${z.baustein}-${z.text}`} className="flex items-baseline gap-2 text-sm leading-snug text-text-2">
|
||||||
<span aria-hidden className={cn("size-1.5 shrink-0 -translate-y-px rounded-full", PUNKT[z.stufe])} />
|
<span aria-hidden className={cn("size-1.5 shrink-0 -translate-y-px rounded-full", PUNKT[z.stufe])} />
|
||||||
@@ -182,6 +185,7 @@ export function UpdatesSeite() {
|
|||||||
key={b.id}
|
key={b.id}
|
||||||
id={b.id}
|
id={b.id}
|
||||||
name={b.name}
|
name={b.name}
|
||||||
|
gelesen={start.data?.updates.gelesen ?? false}
|
||||||
hatUpdate={mitUpdate.has(b.id)}
|
hatUpdate={mitUpdate.has(b.id)}
|
||||||
fest={festgehalten.get(b.id)}
|
fest={festgehalten.get(b.id)}
|
||||||
laeuftGerade={laeuft}
|
laeuftGerade={laeuft}
|
||||||
|
|||||||
Reference in New Issue
Block a user