Wer einen Server aus der Ping-Ueberwachung nehmen wollte, musste sich bisher auf den Rechner mit check_fds.sh einloggen und die hosts.conf editieren - und die Kachel blieb danach trotzdem im System-Monitor stehen, weil ein einmal gemeldetes System nirgends wieder geloescht werden konnte. Die Liste steht jetzt in der Datenbank und ist ueber ein Zahnrad neben der Ueberschrift "Systeme" pflegbar, analog zum Zahnrad am PO-Platz (Issue #27). Jeder Host gehoert dem OPs-Agenten (Issue #66), der ihn anpingt: API und Ping-Check laufen nicht zwangslaeufig auf demselben Rechner, die API kann die hosts.conf also nicht selbst schreiben. check_fds.sh holt sie stattdessen bei jedem Lauf mit seinem Agenten-Key ab und schreibt daraus die lokale Datei; ist die API nicht erreichbar, pingt es unveraendert nach der zuletzt geschriebenen Liste weiter. Beim ersten Lauf laedt der Check seine bestehende hosts.conf hoch, damit die bereits ueberwachten Server nicht von Hand nachgetragen werden muessen. Dass die API das nur einmal annimmt (Marker in host_config_imports) und "noch nie konfiguriert" per 204 von "im Frontend geleert" per 200 unterscheidet, ist noetig, damit ein Ping-Rechner mit alter lokaler Datei die dort geloeschten Hosts nicht wieder anlegt. Loeschen raeumt mit auf: Ein entfernter Host nimmt seine Kachel mit - beide Ids, denn der status.txt-Import benennt ihn nach dem Hostnamen, die REST-Meldung nach dessen Slug. Kacheln ohne zugehoerigen Host (Selbstmelder oder Reste laengst entfernter Hosts) stehen im selben Dialog unter "Weitere Kacheln im System-Monitor" und sind dort einzeln loeschbar - dafuer gibt es neu DELETE /api/v1/systems/{id}. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vNjbfr8M3HSfWMee92GTE
915 lines
18 KiB
CSS
915 lines
18 KiB
CSS
/* Buero-Visualisierung (Phase 2, statische Szene). Bewusst ohne UI-Framework, siehe
|
|
Visualization.md Abschnitt 5. Farbschema hell/freundlich (F9: "Zeichentrick"-Anmutung). */
|
|
|
|
:root {
|
|
--color-floor: #eef2f7;
|
|
--color-wall: #cfe0f0;
|
|
--color-wall-side: #bcd3ea;
|
|
--color-door: #8fb8dd;
|
|
--color-board-frame: #94622f;
|
|
--color-note: #fde68a;
|
|
--color-note-border: #d9b34f;
|
|
--color-note-stack-badge: #d97706;
|
|
--color-monitor-frame: #2b2f38;
|
|
--color-monitor-screen: #10131a;
|
|
--color-monitor-placeholder: #1a2436;
|
|
--color-desk-table: #b98a54;
|
|
--color-desk-chair: #6b7280;
|
|
--color-desk-screen-idle: #3a3f4b;
|
|
--color-text: #1f2937;
|
|
--color-text-inverse: #f8fafc;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Die Szene soll immer vollstaendig sichtbar sein - auch im normalen Fenstermodus,
|
|
ohne Scrollen. Dazu spannt der body eine Flex-Spalte ueber die Viewport-Hoehe auf:
|
|
Header nimmt seine natuerliche Hoehe, #office-scene bekommt den Rest, und das SVG
|
|
skaliert sich per aspect-ratio in diesen Rest hinein (siehe unten). */
|
|
html {
|
|
height: 100%;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
font-family: "Segoe UI", Verdana, Arial, sans-serif;
|
|
background: #dbe7f3;
|
|
color: var(--color-text);
|
|
/* height (nicht min-height): Nur eine definite Hoehe laesst die prozentualen
|
|
max-height-Angaben weiter unten aufloesen. dvh rechnet mobile Browserleisten
|
|
mit, vh ist der Fallback fuer aeltere Engines. */
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
header.office-header {
|
|
flex: 0 0 auto;
|
|
padding: 12px 24px;
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
}
|
|
|
|
header.office-header h1 {
|
|
margin: 0;
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
/* Rein kosmetischer Countdown bis zum naechsten Codeberg-Sync (Issue #12), rechtsbuendig
|
|
anstelle des frueheren erklaerenden Fliesstexts. Tabular-nums verhindert Wackeln der
|
|
Breite, wenn sich die Ziffernzahl aendert (z.B. 9s -> 10s). */
|
|
.reload-countdown {
|
|
color: #475569;
|
|
font-size: 0.9rem;
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Warnbanner, wenn die API nicht erreichbar ist und die Szene auf Demo-Daten zurueckfaellt
|
|
(siehe setApiBanner() in js/office-render.js). */
|
|
.api-banner {
|
|
flex: 0 0 auto;
|
|
padding: 10px 24px;
|
|
background: #dc2626;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
text-align: center;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
/* Muss nach der Regel oben stehen: ohne diese Zeile wuerde das hidden-Attribut vom
|
|
flex-Wert ueberstimmt und der Banner waere dauerhaft sichtbar. */
|
|
.api-banner[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
/* Reine Vorwarnung (Anmeldung laeuft demnaechst ab, der Agent arbeitet noch): gelb statt rot,
|
|
damit das Rot dem tatsaechlichen Stillstand vorbehalten bleibt. Dunkle Schrift, weil Weiss
|
|
auf Gelb kaum zu lesen ist. */
|
|
.api-banner.api-banner-warning {
|
|
background: #facc15;
|
|
color: #422006;
|
|
}
|
|
|
|
#office-scene {
|
|
flex: 1 1 auto;
|
|
/* min-height: 0 hebt die Flex-Default-Mindesthoehe (min-content) auf - sonst kann
|
|
das Kind den Container nicht unterschreiten und die Szene laeuft aus dem Bild. */
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 16px 16px;
|
|
}
|
|
|
|
/* Das SVG soll sich an der jeweils knapperen Achse ausrichten. Welche das ist, haengt
|
|
vom Seitenverhaeltnis des Fensters ab - daher die beiden Media-Queries unten. Die
|
|
max-*-Grenzen sind das Sicherheitsnetz fuer den Grenzbereich (die Header-Hoehe geht
|
|
in das Verhaeltnis der Szenenflaeche ein, in die Media-Query aber nicht): Dort wird
|
|
die Elementbox notfalls leicht gestaucht - der Szeneninhalt bleibt dank
|
|
preserveAspectRatio unverzerrt und vollstaendig sichtbar. */
|
|
.office-svg {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
aspect-ratio: 1920 / 1080;
|
|
display: block;
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.25);
|
|
}
|
|
|
|
/* Fenster breiter als 16:9 -> die Hoehe ist der Engpass. */
|
|
@media (min-aspect-ratio: 16 / 9) {
|
|
.office-svg {
|
|
height: 100%;
|
|
width: auto;
|
|
}
|
|
}
|
|
|
|
/* Fenster schmaler als 16:9 -> die Breite ist der Engpass. */
|
|
@media (max-aspect-ratio: 16 / 9) {
|
|
.office-svg {
|
|
width: 100%;
|
|
height: auto;
|
|
}
|
|
}
|
|
|
|
@media (max-height: 640px) {
|
|
header.office-header {
|
|
padding: 8px 16px;
|
|
}
|
|
|
|
header.office-header h1 {
|
|
font-size: 1.1rem;
|
|
}
|
|
}
|
|
|
|
.floor {
|
|
fill: var(--color-floor);
|
|
}
|
|
|
|
.wall {
|
|
fill: var(--color-wall);
|
|
}
|
|
|
|
.wall-side {
|
|
fill: var(--color-wall-side);
|
|
}
|
|
|
|
.door {
|
|
fill: var(--color-door);
|
|
stroke: #5f86ab;
|
|
stroke-width: 3;
|
|
transition: fill 0.4s ease-in-out;
|
|
}
|
|
|
|
/* Phase 6 (F2): kurzes Aufleuchten, waehrend ein "user"-Tuerbesuch die Tuer passiert. */
|
|
.door-active {
|
|
fill: #d7ecff;
|
|
}
|
|
|
|
.wall-label {
|
|
fill: #f8fafc;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Kanban-Board */
|
|
|
|
.board-frame {
|
|
fill: #7a5230;
|
|
stroke: var(--color-board-frame);
|
|
stroke-width: 6;
|
|
}
|
|
|
|
.board-column-label {
|
|
fill: var(--color-text-inverse);
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.board-column-divider {
|
|
stroke: rgba(255, 255, 255, 0.25);
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.note {
|
|
fill: var(--color-note);
|
|
stroke: var(--color-note-border);
|
|
stroke-width: 2;
|
|
}
|
|
|
|
/* Zettel in der Farbe seines Projekts (Issue #27): die Fuellung kommt als Inline-Style aus
|
|
der Projektkonfiguration, die Kontur muss dann neutral sein - die gelbe Standardkontur
|
|
passt nur zum gelben Standardzettel. */
|
|
.note-colored {
|
|
stroke: rgba(15, 23, 42, 0.35);
|
|
}
|
|
|
|
.note-stack-badge {
|
|
fill: var(--color-note-stack-badge);
|
|
stroke: #92400e;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.note-stack-count {
|
|
fill: #ffffff;
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Monitor-Wand */
|
|
|
|
.monitor-frame {
|
|
fill: var(--color-monitor-frame);
|
|
}
|
|
|
|
.monitor-screen {
|
|
fill: var(--color-monitor-screen);
|
|
}
|
|
|
|
.monitor-screen-placeholder {
|
|
fill: var(--color-monitor-placeholder);
|
|
}
|
|
|
|
.monitor-event-line {
|
|
font-family: "Consolas", "Courier New", monospace;
|
|
font-size: 13px;
|
|
/* Grundfarbe fuer Schweregrade ohne eigene Farbe (z.B. info) - ohne sie stuende der Text
|
|
schwarz auf dem dunklen Monitor. */
|
|
fill: #cbd5e1;
|
|
}
|
|
|
|
/* Kopfzeile eines Events (Zeit + System): tritt gegenueber der Meldung zurueck. */
|
|
.monitor-event-meta {
|
|
opacity: 0.75;
|
|
}
|
|
|
|
.severity-warning,
|
|
.severity-warning .monitor-event-line {
|
|
fill: #facc15;
|
|
}
|
|
|
|
.severity-error,
|
|
.severity-error .monitor-event-line {
|
|
fill: #f87171;
|
|
}
|
|
|
|
.system-tile-bg {
|
|
fill: #1e293b;
|
|
}
|
|
|
|
.system-tile-bar {
|
|
fill: #334155;
|
|
}
|
|
|
|
.system-tile-led {
|
|
fill: #64748b;
|
|
}
|
|
|
|
.status-ok .system-tile-led {
|
|
fill: #4ade80;
|
|
}
|
|
|
|
.status-error .system-tile-led {
|
|
fill: #f87171;
|
|
}
|
|
|
|
.system-tile-label {
|
|
fill: var(--color-text-inverse);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.monitor-placeholder-text {
|
|
fill: #64748b;
|
|
font-size: 15px;
|
|
}
|
|
|
|
/* Schreibtische */
|
|
|
|
.desk-table {
|
|
fill: var(--color-desk-table);
|
|
stroke: #8a6238;
|
|
stroke-width: 3;
|
|
}
|
|
|
|
.desk-screen {
|
|
fill: var(--color-desk-screen-idle);
|
|
stroke: #1f2430;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.desk-screen.pose-working {
|
|
fill: #7dd3fc;
|
|
}
|
|
|
|
/* Blockierter Platz (Claude-Anmeldung abgelaufen): der Bildschirm traegt den Zustand mit,
|
|
damit er auch dann am Platz sichtbar bleibt, wenn der Avatar gerade nicht dort steht. */
|
|
.desk-screen.pose-auth_expired {
|
|
fill: #7f1d1d;
|
|
stroke: #f87171;
|
|
}
|
|
|
|
.desk-screen.pose-auth_expiring {
|
|
fill: #713f12;
|
|
stroke: #facc15;
|
|
}
|
|
|
|
.desk-chair {
|
|
fill: var(--color-desk-chair);
|
|
}
|
|
|
|
.person-head {
|
|
fill: #f3d9b1;
|
|
stroke: #d8b98a;
|
|
stroke-width: 1.5;
|
|
}
|
|
|
|
.person-body {
|
|
fill: var(--role-color, #94a3b8);
|
|
}
|
|
|
|
.desk-name {
|
|
fill: #334155;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.desk-name-empty {
|
|
fill: #94a3b8;
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
}
|
|
|
|
/* Der Name eines Agenten am Schreibtisch steht am Tisch, nicht am Avatar (updateAgentsLayer()
|
|
blendet .avatar-name am Platz aus) - der Problemzustand faerbt deshalb hier. */
|
|
.desk-name.desk-name-problem-error {
|
|
fill: #b91c1c;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.desk-name.desk-name-problem-warning {
|
|
fill: #a16207;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.desk-deco-star {
|
|
fill: #fbbf24;
|
|
}
|
|
|
|
.desk-deco-flower circle {
|
|
fill: #f9a8d4;
|
|
}
|
|
|
|
.desk-deco-flower-center {
|
|
fill: #fde047 !important;
|
|
}
|
|
|
|
.desk-deco-check {
|
|
fill: none;
|
|
stroke: #22c55e;
|
|
stroke-width: 4;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.desk-deco-gear circle,
|
|
.desk-deco-gear rect {
|
|
fill: #a78bfa;
|
|
}
|
|
|
|
/* Heller Kern, damit der gefuellte Kreis als Zahnrad-Ring lesbar wird (wie beim Bluetenkern). */
|
|
.desk-deco-gear-center {
|
|
fill: #ede9fe !important;
|
|
}
|
|
|
|
.desk-empty .desk-chair {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* Klickbare Bereiche (Phase 3/4: Board-Spalten + Monitore oeffnen ein Modal) */
|
|
|
|
.clickable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.board-column-hit {
|
|
fill: transparent;
|
|
pointer-events: all;
|
|
}
|
|
|
|
/* Trefferflaeche des PO-Platzes (Issue #27): macht den ganzen Platz klickbar, nicht nur die
|
|
gefuellten Moebelstuecke - ein Klick daneben landete sonst auf dem Boden. */
|
|
.po-desk-hit {
|
|
fill: transparent;
|
|
pointer-events: all;
|
|
}
|
|
|
|
/* Konfig-Zahnrad (Issue #27 am PO-Platz, Issue #70 am Systeme-Monitor): zeigt ueberhaupt
|
|
erst, dass sich hier etwas einstellen laesst. Aussehen ist ueberall gleich, nur der
|
|
Ausloeser des Hovers unterscheidet sich - beim PO der ganze Schreibtisch, beim Monitor
|
|
das Symbol selbst (der Monitor traegt schon eine eigene Klick-Aktion). */
|
|
.config-hint-bg {
|
|
fill: #ffffff;
|
|
stroke: #94a3b8;
|
|
stroke-width: 1.5;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.config-hint-tooth,
|
|
.config-hint-hub {
|
|
stroke: #475569;
|
|
stroke-width: 2;
|
|
stroke-linecap: round;
|
|
fill: none;
|
|
}
|
|
|
|
.po-desk:hover .config-hint-bg,
|
|
.system-config-hint:hover .config-hint-bg {
|
|
fill: #e0f2fe;
|
|
stroke: #0284c7;
|
|
opacity: 1;
|
|
}
|
|
|
|
.po-desk:hover .config-hint-tooth,
|
|
.po-desk:hover .config-hint-hub,
|
|
.system-config-hint:hover .config-hint-tooth,
|
|
.system-config-hint:hover .config-hint-hub {
|
|
stroke: #0284c7;
|
|
}
|
|
|
|
.system-config-hint-hit {
|
|
fill: transparent;
|
|
pointer-events: all;
|
|
}
|
|
|
|
/* Reload-Symbol neben der Board-Ueberschrift (Issue #51): stoesst ein komplettes
|
|
Neueinlesen des Kanban-Boards an, analog zum Zahnrad am PO-Platz. */
|
|
.board-reload-hint-bg {
|
|
fill: #ffffff;
|
|
stroke: #94a3b8;
|
|
stroke-width: 1.5;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.board-reload-hint-arrow {
|
|
fill: none;
|
|
stroke: #475569;
|
|
stroke-width: 2;
|
|
stroke-linecap: round;
|
|
}
|
|
|
|
.board-reload-hint-arrowhead {
|
|
fill: #475569;
|
|
}
|
|
|
|
.board-reload-hint-hit {
|
|
fill: transparent;
|
|
pointer-events: all;
|
|
}
|
|
|
|
.board-reload-hint:hover .board-reload-hint-bg {
|
|
fill: #e0f2fe;
|
|
stroke: #0284c7;
|
|
opacity: 1;
|
|
}
|
|
|
|
.board-reload-hint:hover .board-reload-hint-arrow {
|
|
stroke: #0284c7;
|
|
}
|
|
|
|
.board-reload-hint:hover .board-reload-hint-arrowhead {
|
|
fill: #0284c7;
|
|
}
|
|
|
|
.just-updated .board-frame,
|
|
.just-updated .monitor-frame {
|
|
animation: office-flash 0.9s ease-out;
|
|
}
|
|
|
|
@keyframes office-flash {
|
|
0% {
|
|
filter: drop-shadow(0 0 0 rgba(250, 204, 21, 0));
|
|
}
|
|
25% {
|
|
filter: drop-shadow(0 0 12px rgba(250, 204, 21, 0.9));
|
|
}
|
|
100% {
|
|
filter: drop-shadow(0 0 0 rgba(250, 204, 21, 0));
|
|
}
|
|
}
|
|
|
|
/* Kaffee-Ecke (Phase 5, Raum-Prop zwischen den Schreibtischreihen) */
|
|
|
|
.coffee-table {
|
|
fill: #8a6238;
|
|
stroke: #6b4a28;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.coffee-cup {
|
|
fill: #f8fafc;
|
|
stroke: #94a3b8;
|
|
stroke-width: 1.5;
|
|
}
|
|
|
|
.coffee-icon {
|
|
font-size: 20px;
|
|
}
|
|
|
|
/* Agenten-Avatare (Phase 5: bewegliche Schicht, siehe office-render.js) */
|
|
|
|
.agent-avatar {
|
|
transition: transform 1.1s ease-in-out;
|
|
}
|
|
|
|
.agent-avatar .person-head {
|
|
fill: #f3d9b1;
|
|
stroke: #d8b98a;
|
|
stroke-width: 1.5;
|
|
}
|
|
|
|
.agent-avatar .person-body {
|
|
fill: var(--role-color, #94a3b8);
|
|
}
|
|
|
|
.agent-avatar.pose-walking .person-leg {
|
|
fill: #475569;
|
|
transform-box: fill-box;
|
|
transform-origin: top center;
|
|
animation: office-leg-swing 0.6s ease-in-out infinite alternate;
|
|
}
|
|
|
|
.agent-avatar.pose-walking .person-leg-right {
|
|
animation-delay: 0.3s;
|
|
}
|
|
|
|
@keyframes office-leg-swing {
|
|
from {
|
|
transform: rotate(-18deg);
|
|
}
|
|
to {
|
|
transform: rotate(18deg);
|
|
}
|
|
}
|
|
|
|
/* Blockierter Agent (AGENT_PROBLEM_STATES): der Koerper verliert seine Rollenfarbe, damit die
|
|
Figur als "steht still" lesbar wird, ohne aus der Szene zu verschwinden. */
|
|
.agent-avatar.pose-auth_expired .person-body,
|
|
.agent-avatar.pose-auth_expiring .person-body {
|
|
fill: #94a3b8;
|
|
}
|
|
|
|
.person-prop-lock-body {
|
|
fill: #f87171;
|
|
stroke: #7f1d1d;
|
|
stroke-width: 1.5;
|
|
}
|
|
|
|
.person-prop-lock-shackle {
|
|
fill: none;
|
|
stroke: #f87171;
|
|
stroke-width: 3;
|
|
stroke-linecap: round;
|
|
}
|
|
|
|
.pose-auth_expiring .person-prop-lock-body {
|
|
fill: #facc15;
|
|
stroke: #713f12;
|
|
}
|
|
|
|
.pose-auth_expiring .person-prop-lock-shackle {
|
|
stroke: #facc15;
|
|
}
|
|
|
|
/* Nur der harte Ausfall pulsiert - eine pulsierende Vorwarnung waere auf einem Dauermonitor
|
|
Rauschen, das man nach einer Stunde nicht mehr sieht. */
|
|
.agent-avatar.pose-auth_expired .person-prop-lock {
|
|
animation: office-lock-pulse 1.4s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes office-lock-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.35;
|
|
}
|
|
}
|
|
|
|
.person-prop-cup {
|
|
fill: #f8fafc;
|
|
stroke: #94a3b8;
|
|
}
|
|
|
|
.person-prop-newspaper {
|
|
fill: #f8fafc;
|
|
stroke: #94a3b8;
|
|
stroke-width: 1;
|
|
}
|
|
|
|
.person-prop-speech {
|
|
fill: #f8fafc;
|
|
stroke: #94a3b8;
|
|
stroke-width: 1;
|
|
}
|
|
|
|
.avatar-name {
|
|
fill: #334155;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
text-anchor: middle;
|
|
}
|
|
|
|
/* KPI-Sparklines (Phase 4, F10: kein Chart-Lib, eigenes SVG-Zeichnen) */
|
|
|
|
.kpi-tile-label {
|
|
fill: var(--color-text-inverse);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Kombiniert notiert, weil .kpi-tile-label weiter unten steht als .severity-* und die Farbe
|
|
sonst bei gleicher Spezifitaet ueber die Reihenfolge zurueckgewinnt. */
|
|
.kpi-tile-label.severity-error {
|
|
fill: #f87171;
|
|
}
|
|
|
|
.kpi-tile-label.severity-warning {
|
|
fill: #facc15;
|
|
}
|
|
|
|
.kpi-sparkline-bg {
|
|
fill: rgba(255, 255, 255, 0.04);
|
|
}
|
|
|
|
.kpi-sparkline-line {
|
|
fill: none;
|
|
stroke: #7dd3fc;
|
|
stroke-width: 2;
|
|
stroke-linejoin: round;
|
|
stroke-linecap: round;
|
|
}
|
|
|
|
/* Modal (Phase 3/4: Board-/Monitor-Detailansicht, Visualization.md 4.5) */
|
|
|
|
.office-modal {
|
|
border: none;
|
|
border-radius: 12px;
|
|
padding: 0;
|
|
width: min(640px, 92vw);
|
|
max-height: 80vh;
|
|
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.4);
|
|
}
|
|
|
|
.office-modal::backdrop {
|
|
background: rgba(15, 23, 42, 0.55);
|
|
}
|
|
|
|
.office-modal-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
max-height: 80vh;
|
|
}
|
|
|
|
.office-modal-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.office-modal-title {
|
|
margin: 0;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.office-modal-close {
|
|
border: none;
|
|
background: transparent;
|
|
font-size: 1.4rem;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
color: #64748b;
|
|
}
|
|
|
|
.office-modal-body {
|
|
padding: 16px 20px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.office-modal-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.office-modal-item {
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.office-modal-item p {
|
|
margin: 6px 0 0;
|
|
color: #475569;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.office-modal-item-heading {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.office-modal-badge {
|
|
font-size: 0.75rem;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
background: #e2e8f0;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.office-modal-item.severity-error {
|
|
border-color: #fca5a5;
|
|
background: #fef2f2;
|
|
}
|
|
|
|
.office-modal-item.severity-warning {
|
|
border-color: #fde68a;
|
|
background: #fffbeb;
|
|
}
|
|
|
|
.office-modal-item.status-error {
|
|
border-color: #fca5a5;
|
|
background: #fef2f2;
|
|
}
|
|
|
|
.office-modal-empty {
|
|
color: #64748b;
|
|
font-style: italic;
|
|
}
|
|
|
|
.kpi-modal-sparkline {
|
|
width: 100%;
|
|
height: 80px;
|
|
margin-top: 8px;
|
|
background: #10131a;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* Projekte-Modal (Issue #27: Klick auf den PO -> Projekte des Codeberg-Sync verwalten) */
|
|
|
|
.office-modal-error {
|
|
margin: 0 0 10px;
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
background: #fef2f2;
|
|
border: 1px solid #fca5a5;
|
|
color: #991b1b;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.repo-config-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.repo-config-swatch {
|
|
flex: none;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 4px;
|
|
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
}
|
|
|
|
.repo-config-slug {
|
|
flex: 1;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.repo-config-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex: none;
|
|
}
|
|
|
|
.repo-config-form {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.repo-config-form-fields {
|
|
display: flex;
|
|
flex: 1;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.repo-config-form-fields input[type="text"] {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 6px 8px;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 6px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.repo-config-form-fields input[type="color"] {
|
|
flex: none;
|
|
width: 36px;
|
|
height: 32px;
|
|
padding: 2px;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.icon-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
border: 1px solid transparent;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
color: #475569;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.icon-button:hover {
|
|
background: #f1f5f9;
|
|
border-color: #e2e8f0;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.icon-button-svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.repo-config-add-button {
|
|
margin-top: 8px;
|
|
color: #16a34a;
|
|
}
|
|
|
|
.repo-config-add-button:hover {
|
|
background: #f0fdf4;
|
|
border-color: #bbf7d0;
|
|
color: #15803d;
|
|
}
|
|
|
|
/* Issue #53: Ampel-Symbole (Schreibrechte/Webhook) rechts neben den Bearbeiten-/
|
|
Loeschen-Symbolen einer Projektzeile - Tooltip (title) traegt den Pruefgrund. */
|
|
.repo-config-status {
|
|
display: flex;
|
|
gap: 2px;
|
|
flex: none;
|
|
}
|
|
|
|
.repo-config-status-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
cursor: help;
|
|
}
|
|
|
|
.repo-config-status-icon.is-ok {
|
|
color: #16a34a;
|
|
}
|
|
|
|
.repo-config-status-icon.is-fail {
|
|
color: #dc2626;
|
|
}
|
|
|
|
/* Hosts-Modal (Issue #70) - teilt sich Zeilen-, Formular- und Symbolstile mit dem
|
|
Projekte-Modal; eigene Regeln braucht nur, was es dort nicht gibt. */
|
|
|
|
.host-config-subheading {
|
|
margin: 18px 0 4px;
|
|
font-size: 0.95rem;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.host-config-agent {
|
|
flex: none;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
background: #f1f5f9;
|
|
color: #475569;
|
|
font-size: 0.8rem;
|
|
}
|