Design: Pflicht-Fixes gegen helle Schrift und dunkle Flaechen, Diagrammfarben, Chat-Variablen
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
47a26fb9b2
commit
f911dbcb03
17 files changed
+143
-103
No files matched your search
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { fetchAnalyse, setAnalyse } from '../lib/analyseApi'
|
||||
import { analyseStatus, textformLabel, canSubmitTextform, channelLabel, legalLinks, chatTemplate, privacyHint } from '../lib/analyseView'
|
||||
|
||||
const COLORS = { green: '#10b981', red: '#ef4444', grey: '#a0aec0' }
|
||||
const COLORS = { green: 'var(--green-fg)', red: 'var(--red-fg)', grey: 'var(--text-2)' }
|
||||
|
||||
// Kompakte Zeile "Website-Analyse" je Projekt (lädt beim Einbinden; mit key=Projekt-ID verwenden)
|
||||
export default function AnalyseRow({ projectId }) {
|
||||
|
||||
@@ -10,21 +10,21 @@ import { needsPreviewButton, assignSummary } from '../lib/projectAssign'
|
||||
import { updateWebsiteProject, backfillProjectPushes } from '../lib/projectAdminApi'
|
||||
|
||||
const STATUS_COLORS = {
|
||||
ready: '#10b981',
|
||||
live: '#10b981',
|
||||
online: '#10b981',
|
||||
provisioning: '#f59e0b',
|
||||
pending: '#f59e0b',
|
||||
building: '#3b82f6',
|
||||
error: '#ef4444',
|
||||
failed: '#ef4444',
|
||||
ready: 'var(--green-fg)',
|
||||
live: 'var(--green-fg)',
|
||||
online: 'var(--green-fg)',
|
||||
provisioning: 'var(--orange-fg)',
|
||||
pending: 'var(--orange-fg)',
|
||||
building: 'var(--blue-fg)',
|
||||
error: 'var(--red-fg)',
|
||||
failed: 'var(--red-fg)',
|
||||
}
|
||||
|
||||
function StatusBadge({ label, value }) {
|
||||
if (!value) return null
|
||||
const color = STATUS_COLORS[String(value).toLowerCase()] || '#a0aec0'
|
||||
const color = STATUS_COLORS[String(value).toLowerCase()] || 'var(--text-2)'
|
||||
return (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: '#cbd5e0', marginRight: 12 }}>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-2)', marginRight: 12 }}>
|
||||
{label}:
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, color }}>
|
||||
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block' }} />
|
||||
@@ -79,7 +79,7 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: 'rgba(30,41,59,0.5)', border: '1px solid rgba(59,130,246,0.25)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
|
||||
<div style={{ background: 'var(--bg-subtle)', border: '1px solid var(--line)', borderRadius: 10, padding: '12px 14px', marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<strong>{project.projectName}</strong>
|
||||
@@ -99,7 +99,7 @@ export default function AssignedProjectCard({ project, onUnassign, showActions =
|
||||
title="Vom Ticket lösen"
|
||||
aria-label="Vom Ticket lösen"
|
||||
onClick={() => onUnassign(project.$id)}
|
||||
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 6, background: 'rgba(239, 68, 68, 0.12)', border: '1px solid rgba(239, 68, 68, 0.35)', color: '#f87171', cursor: 'pointer' }}
|
||||
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 6, background: 'var(--red-bg)', border: '1px solid var(--line)', color: 'var(--red-fg)', cursor: 'pointer' }}
|
||||
>
|
||||
<FaLinkSlash size={16} />
|
||||
</button>
|
||||
|
||||
@@ -191,7 +191,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
))}
|
||||
</select>
|
||||
{isAcquisition && (
|
||||
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
|
||||
"Zugesagt" macht den Lead automatisch zum festen Kunden.
|
||||
</small>
|
||||
)}
|
||||
@@ -259,7 +259,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
onChange={(value) => handleChange('totalTime', value)}
|
||||
disabled={formData.isComment}
|
||||
/>
|
||||
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
|
||||
{autoCalculate && formData.startTime && formData.endTime
|
||||
? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten'
|
||||
: autoCalculate ? 'Beginn und Ende eintragen oder Zeit wählen' : 'Von Hand gewählt'}
|
||||
|
||||
@@ -17,9 +17,9 @@ export default function GiteaLinkButton({ href, title = 'In Gitea oeffnen', size
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
background: 'rgba(99, 102, 241, 0.15)',
|
||||
border: '1px solid rgba(99, 102, 241, 0.35)',
|
||||
color: '#818cf8',
|
||||
background: 'var(--violet-bg)',
|
||||
border: '1px solid var(--line)',
|
||||
color: 'var(--violet-fg)',
|
||||
textDecoration: 'none',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
|
||||
@@ -208,13 +208,13 @@ export default function PreviewBlock({ projectId }) {
|
||||
<span style={{ overflowWrap: 'anywhere' }}>{status.text}</span>
|
||||
</div>
|
||||
{data.state === 'failed' && data.error && !isDevUnavailableError(data.error) && (
|
||||
<div style={{ color: '#f87171', ...small, marginTop: 4, overflowWrap: 'anywhere' }}>{data.error}</div>
|
||||
<div style={{ color: 'var(--red-fg)', ...small, marginTop: 4, overflowWrap: 'anywhere' }}>{data.error}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{inactive && <div className="text-grey" style={{ ...small, marginTop: 8 }}>{NOT_ACTIVE}.</div>}
|
||||
{error && <div style={{ color: '#f87171', ...small, marginTop: 8, overflowWrap: 'anywhere' }}>{error}</div>}
|
||||
{error && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 8, overflowWrap: 'anywhere' }}>{error}</div>}
|
||||
|
||||
{data && !inactive && (
|
||||
<>
|
||||
@@ -251,7 +251,7 @@ export default function PreviewBlock({ projectId }) {
|
||||
{logs.map((l) => <option key={l.runFile} value={l.runFile}>{logDate(l.at)}</option>)}
|
||||
</select>
|
||||
{logFile && (
|
||||
<pre style={{ marginTop: 6, maxHeight: 260, overflow: 'auto', background: 'rgba(0,0,0,0.35)', padding: 8, borderRadius: 6, fontFamily: 'monospace', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
||||
<pre style={{ marginTop: 6, maxHeight: 260, overflow: 'auto', background: 'var(--bg-subtle)', padding: 8, borderRadius: 6, fontFamily: 'monospace', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
||||
{logText || '…'}
|
||||
</pre>
|
||||
)}
|
||||
@@ -263,7 +263,7 @@ export default function PreviewBlock({ projectId }) {
|
||||
<div style={{ marginTop: 6 }}>
|
||||
{names.length === 0 && <div className="text-grey" style={small}>Keine Variablen.</div>}
|
||||
{names.map((n) => (
|
||||
<div key={n} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '4px 0', borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<div key={n} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '4px 0', borderBottom: '1px solid var(--line)' }}>
|
||||
<code style={{ flex: '1 1 140px', overflowWrap: 'anywhere' }}>{n}</code>
|
||||
<span className="text-grey">•••</span>
|
||||
{editName === n ? (
|
||||
@@ -282,7 +282,7 @@ export default function PreviewBlock({ projectId }) {
|
||||
<input type="password" autoComplete="off" className="form-control" style={{ flex: '1 1 140px', minWidth: 0 }} placeholder="Wert" onKeyDown={enter(handleAdd)} value={newValue} onChange={(e) => setNewValue(e.target.value)} />
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleAdd}>Hinzufügen</button>
|
||||
</div>
|
||||
{envError && <div style={{ color: '#f87171', ...small, marginTop: 4 }}>{envError}</div>}
|
||||
{envError && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 4 }}>{envError}</div>}
|
||||
{dirty && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleSaveEnv}>Variablen speichern</button>
|
||||
|
||||
@@ -30,9 +30,9 @@ export default function PreviewLinkButton({ href, subdomain, title = 'Preview oe
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
background: 'rgba(16, 185, 129, 0.15)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.35)',
|
||||
color: '#34d399',
|
||||
background: 'var(--green-bg)',
|
||||
border: '1px solid var(--line)',
|
||||
color: 'var(--green-fg)',
|
||||
textDecoration: 'none',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
|
||||
@@ -4,21 +4,22 @@ import { serviceTypeLabel, needsTimeEntry, worksheetTone } from '../lib/ticketFo
|
||||
import { statusLabel } from '../lib/ticketStatus'
|
||||
import { parseSupporters, supportMinutes, firstName } from '../lib/support'
|
||||
|
||||
// Kopffarbe je Zustand (worksheetTone): orange = Git-Zeit fehlt, gruen = Git-Zeit eingetragen
|
||||
// Randfarbe je Zustand (worksheetTone): orange = Git-Zeit fehlt, gruen = Git-Zeit eingetragen. Der Kopf ist fuer alle Zustaende
|
||||
// grau (Schrift --text auf --bg-subtle); P2 baut die Liste um.
|
||||
const TONES = {
|
||||
'git-open': { border: '#f97316', base: 'linear-gradient(135deg, #f97316 0%, #ea580c 100%)', hover: 'linear-gradient(135deg, #ea580c 0%, #c2410c 100%)' },
|
||||
'git-done': { border: '#10b981', base: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', hover: 'linear-gradient(135deg, #059669 0%, #047857 100%)' },
|
||||
comment: { border: '#3b82f6', base: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', hover: 'linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%)' },
|
||||
work: { border: '#4a5568', base: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)', hover: 'linear-gradient(135deg, #2d3748 0%, #1a202c 100%)' },
|
||||
'git-open': { border: 'var(--orange-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
|
||||
'git-done': { border: 'var(--green-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
|
||||
comment: { border: 'var(--blue-fg)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
|
||||
work: { border: 'var(--text-2)', base: 'var(--bg-subtle)', hover: 'var(--bg-hover)' },
|
||||
}
|
||||
|
||||
// Hinweise im farbigen Kopf: mit der grauen Badge-Schrift waeren sie kaum lesbar
|
||||
const headerBadgeStyle = { background: 'rgba(255,255,255,0.3)', color: 'white', marginLeft: '8px' }
|
||||
// Hinweise im Kopf: graue Badge-Toene
|
||||
const headerBadgeStyle = { background: 'var(--grey-bg)', color: 'var(--grey-fg)', marginLeft: '8px' }
|
||||
|
||||
const headerButtonStyle = {
|
||||
background: 'rgba(255,255,255,0.25)',
|
||||
color: 'white',
|
||||
border: '1px solid rgba(255,255,255,0.4)',
|
||||
background: 'var(--bg)',
|
||||
color: 'var(--text)',
|
||||
border: '1px solid var(--line-strong)',
|
||||
borderRadius: '6px',
|
||||
padding: '2px 10px',
|
||||
fontSize: '0.8rem',
|
||||
@@ -88,10 +89,10 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
transition: 'transform 0.2s ease, box-shadow 0.2s ease'
|
||||
}} onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||
e.currentTarget.style.boxShadow = '0 8px 16px rgba(0,0,0,0.1)'
|
||||
e.currentTarget.style.boxShadow = 'var(--shadow-btn)'
|
||||
}} onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'
|
||||
e.currentTarget.style.boxShadow = 'none'
|
||||
}}>
|
||||
{/* Header - Immer sichtbar, klickbar */}
|
||||
<div
|
||||
@@ -99,7 +100,7 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
onClick={() => toggleWorksheet(ws.wsid)}
|
||||
style={{
|
||||
background: colors.base,
|
||||
color: 'white',
|
||||
color: 'var(--text)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
@@ -141,7 +142,7 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
</span>
|
||||
)}
|
||||
{ws.countsToPlan && !ws.isComment && (
|
||||
<span className="badge ms-2" title="Wird vom Stundenguthaben des Wartungs-Abos abgezogen" style={{ ...headerBadgeStyle, background: '#9d3dfe' }}>
|
||||
<span className="badge ms-2" title="Wird vom Stundenguthaben des Wartungs-Abos abgezogen" style={{ ...headerBadgeStyle, background: 'var(--violet-bg)', color: 'var(--violet-fg)' }}>
|
||||
Abo-Stunden
|
||||
</span>
|
||||
)}
|
||||
@@ -160,8 +161,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
</div>
|
||||
)}
|
||||
<span className="badge" style={{
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
color: 'white',
|
||||
background: 'var(--grey-bg)',
|
||||
color: 'var(--grey-fg)',
|
||||
fontSize: '0.8rem'
|
||||
}}>{serviceTypeLabel(ws.serviceType)}</span>
|
||||
</div>
|
||||
@@ -207,9 +208,9 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
className="btn btn-sm"
|
||||
title={ws.countsToPlan ? 'Nicht mehr vom Abo-Stundenkonto abziehen' : 'Vom Abo-Stundenkonto des Kunden abziehen'}
|
||||
style={{
|
||||
background: ws.countsToPlan ? '#9d3dfe' : 'rgba(255,255,255,0.15)',
|
||||
color: 'white',
|
||||
border: '1px solid rgba(255,255,255,0.4)',
|
||||
background: ws.countsToPlan ? 'var(--violet-bg)' : 'var(--bg)',
|
||||
color: ws.countsToPlan ? 'var(--violet-fg)' : 'var(--text)',
|
||||
border: '1px solid var(--line-strong)',
|
||||
borderRadius: '6px',
|
||||
padding: '2px 10px',
|
||||
fontSize: '0.8rem',
|
||||
@@ -241,12 +242,12 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
<div className="row mb-3">
|
||||
<div className="col-md-6">
|
||||
<div className="d-flex align-items-center">
|
||||
<FaUser className="me-2" style={{ color: '#10b981' }} />
|
||||
<FaUser className="me-2" style={{ color: 'var(--green-fg)' }} />
|
||||
<strong>{ws.employeeName}</strong>
|
||||
{ws.employeeShort && (
|
||||
<span className="badge ms-2" style={{
|
||||
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
||||
color: 'white'
|
||||
background: 'var(--green-bg)',
|
||||
color: 'var(--green-fg)'
|
||||
}}>{ws.employeeShort}</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -254,8 +255,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
<div className="col-md-6 text-md-end">
|
||||
{!ws.isComment && (
|
||||
<div className="d-flex align-items-center justify-content-md-end">
|
||||
<FaClock className="me-2" style={{ color: '#10b981' }} />
|
||||
<strong className="fs-5" style={{ color: '#10b981' }}>{formatTime(ws.totalTime)}</strong>
|
||||
<FaClock className="me-2" style={{ color: 'var(--green-fg)' }} />
|
||||
<strong className="fs-5" style={{ color: 'var(--green-fg)' }}>{formatTime(ws.totalTime)}</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -275,8 +276,8 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
{/* Service Type */}
|
||||
<div className="mb-3">
|
||||
<span className="badge px-3 py-2" style={{
|
||||
background: 'linear-gradient(135deg, #4a5568 0%, #2d3748 100%)',
|
||||
color: 'white',
|
||||
background: 'var(--grey-bg)',
|
||||
color: 'var(--grey-fg)',
|
||||
fontSize: '0.85rem'
|
||||
}}>{serviceTypeLabel(ws.serviceType)}</span>
|
||||
</div>
|
||||
@@ -284,20 +285,20 @@ export default function WorksheetList({ worksheets, totalTime, loading, onToggle
|
||||
{/* Status-Änderung */}
|
||||
{ws.oldStatus !== ws.newStatus && (
|
||||
<div className="mb-3 p-3 rounded-3" style={{
|
||||
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%)'
|
||||
background: 'var(--bg-subtle)'
|
||||
}}>
|
||||
<FaExchangeAlt className="me-2" style={{ color: '#10b981' }} />
|
||||
<FaExchangeAlt className="me-2" style={{ color: 'var(--green-fg)' }} />
|
||||
<span className="text-muted">Status:</span>{' '}
|
||||
<span className="badge" style={{ background: '#6b7280', color: 'white' }}>{statusLabel(ws.oldStatus)}</span>
|
||||
<span className="mx-2" style={{ color: '#10b981' }}>→</span>
|
||||
<span className="badge" style={{ background: '#10b981', color: 'white' }}>{statusLabel(ws.newStatus)}</span>
|
||||
<span className="badge" style={{ background: 'var(--grey-bg)', color: 'var(--grey-fg)' }}>{statusLabel(ws.oldStatus)}</span>
|
||||
<span className="mx-2" style={{ color: 'var(--green-fg)' }}>→</span>
|
||||
<span className="badge" style={{ background: 'var(--green-bg)', color: 'var(--green-fg)' }}>{statusLabel(ws.newStatus)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Details */}
|
||||
<div className="mt-3 p-3 rounded-3" style={{
|
||||
background: 'rgba(16, 185, 129, 0.05)',
|
||||
border: '1px solid rgba(16, 185, 129, 0.1)'
|
||||
background: 'var(--bg-subtle)',
|
||||
border: '1px solid var(--line)'
|
||||
}}>
|
||||
<small className="text-dark" style={{ whiteSpace: 'pre-wrap', lineHeight: '1.6' }}>
|
||||
{ws.details}
|
||||
|
||||
@@ -140,7 +140,7 @@ export default function WorksheetTimesModal({ worksheet, currentUserId, onClose,
|
||||
value={totalTime}
|
||||
onChange={(value) => setManualTotal(value)}
|
||||
/>
|
||||
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||
<small style={{ color: 'var(--text-2)', fontSize: '12px' }}>
|
||||
{manualTotal === null && calculatedTotal !== null
|
||||
? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten'
|
||||
: 'Von Hand gewählt'}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import uPlot from 'uplot'
|
||||
import { formatAxisTick, formatLegendTime } from '../../lib/serverpulsFormat'
|
||||
import { CHART } from './chartColors'
|
||||
import 'uplot/dist/uPlot.min.css'
|
||||
|
||||
/**
|
||||
@@ -25,15 +26,15 @@ export default function Chart({ data, series, height = 280, yFmt, yRange }) {
|
||||
scales: { y: { range: yr || ((u, min, max) => [Math.min(0, min), max === min ? min + 1 : max + (max - min) * 0.08]) } },
|
||||
axes: [
|
||||
{
|
||||
stroke: '#9aa7b4',
|
||||
grid: { stroke: 'rgba(255,255,255,0.06)' },
|
||||
ticks: { stroke: 'rgba(255,255,255,0.1)' },
|
||||
stroke: CHART.axis,
|
||||
grid: { show: false },
|
||||
ticks: { stroke: CHART.tick },
|
||||
values: (u, splits, axisIdx, foundSpace, foundIncr) => splits.map((t) => formatAxisTick(t, foundIncr)),
|
||||
},
|
||||
{
|
||||
stroke: '#9aa7b4',
|
||||
grid: { stroke: 'rgba(255,255,255,0.06)' },
|
||||
ticks: { stroke: 'rgba(255,255,255,0.1)' },
|
||||
stroke: CHART.axis,
|
||||
grid: { stroke: CHART.grid },
|
||||
ticks: { stroke: CHART.tick },
|
||||
size: 70,
|
||||
values: (u, vals) => vals.map(fmtVal),
|
||||
},
|
||||
@@ -42,7 +43,7 @@ export default function Chart({ data, series, height = 280, yFmt, yRange }) {
|
||||
{ label: 'Zeit', value: (u, v) => formatLegendTime(v) },
|
||||
...s.map((x) => ({
|
||||
label: x.label,
|
||||
stroke: x.color || '#10b981',
|
||||
stroke: x.color || CHART.line,
|
||||
width: 2,
|
||||
spanGaps: false,
|
||||
points: { show: false },
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Canvas-Farben fuer uPlot (Serverpuls). Canvas versteht kein var(--...), deshalb stehen die Werte hier als Literale;
|
||||
// sie entsprechen den Tokens: line = --accent, peak = --text-3, axis = --text-2, grid = --line, tick = --line-strong.
|
||||
// Einzige Stelle im Quelltext, an der Farbliterale ausserhalb von CSS erlaubt sind.
|
||||
export const CHART = {
|
||||
line: '#111111',
|
||||
fill: 'rgba(17,17,17,.06)',
|
||||
peak: '#8a93a3',
|
||||
axis: '#5e6678',
|
||||
grid: '#ebeef1',
|
||||
tick: '#d8dde3',
|
||||
}
|
||||
+12
-12
@@ -142,15 +142,15 @@
|
||||
}
|
||||
|
||||
.admin-icon-button:hover {
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
border-color: var(--accent-ring);
|
||||
color: var(--text);
|
||||
background: rgba(16, 185, 129, 0.07);
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
|
||||
.admin-icon-button.danger:hover {
|
||||
border-color: rgba(239, 68, 68, 0.55);
|
||||
color: #fca5a5;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border-color: var(--danger);
|
||||
color: var(--red-fg);
|
||||
background: var(--red-bg);
|
||||
}
|
||||
|
||||
.admin-primary-button {
|
||||
@@ -160,17 +160,17 @@
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 9px 15px;
|
||||
border: 1px solid #34d399;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 6px;
|
||||
background: var(--accent);
|
||||
color: #05251c;
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 750;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-primary-button:hover { background: #34d399; }
|
||||
.admin-primary-button:hover { background: var(--accent-hover); }
|
||||
.admin-primary-button:disabled { opacity: 0.55; cursor: wait; }
|
||||
|
||||
.admin-split-layout {
|
||||
@@ -259,14 +259,14 @@
|
||||
|
||||
.admin-table td {
|
||||
padding: 12px 13px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.065);
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.admin-table tbody tr:last-child td { border-bottom: 0; }
|
||||
.admin-table tbody tr:hover td { background: rgba(255, 255, 255, 0.018); }
|
||||
.admin-table tbody tr:hover td { background: var(--bg-subtle); }
|
||||
.admin-table td > strong,
|
||||
.admin-table td > span { display: block; }
|
||||
.admin-table td > span { margin-top: 3px; color: var(--text-muted); font-size: 12px; }
|
||||
@@ -350,8 +350,8 @@
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 13px 15px;
|
||||
border: 1px solid rgba(16, 185, 129, 0.42);
|
||||
background: rgba(16, 185, 129, 0.065);
|
||||
border: 1px solid var(--line-strong);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
|
||||
.admin-credential-result strong,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ageLabel, formatBytes, formatNumber, formatPct, formatSince, usedPct,
|
||||
} from '../lib/serverpulsFormat'
|
||||
import Chart from '../components/serverpuls/Chart'
|
||||
import { CHART } from '../components/serverpuls/chartColors'
|
||||
import Sparkline from '../components/serverpuls/Sparkline'
|
||||
import AppsTable from '../components/serverpuls/AppsTable'
|
||||
import '../styles/serverpuls.css'
|
||||
@@ -220,10 +221,10 @@ export default function ServerpulsPage() {
|
||||
const avg = pts.map((p) => p.v)
|
||||
const withMax = chart.res !== 'raw' && ['cpu', 'load', 'zombies'].includes(chart.metric)
|
||||
const data = [xs, avg]
|
||||
const series = [{ label: m.label, color: '#10b981', fill: 'rgba(16,185,129,0.10)' }]
|
||||
const series = [{ label: m.label, color: CHART.line, fill: CHART.fill }]
|
||||
if (withMax) {
|
||||
data.push(pts.map((p) => p.max ?? null))
|
||||
series.push({ label: `${m.label} (Spitze)`, color: '#f59e0b', dash: [4, 4] })
|
||||
series.push({ label: `${m.label} (Spitze)`, color: CHART.peak, dash: [4, 4] })
|
||||
}
|
||||
return { data, series }
|
||||
}, [chart, m.label])
|
||||
@@ -232,9 +233,9 @@ export default function ServerpulsPage() {
|
||||
if (!detail) return null
|
||||
const days = (detail.size || []).map((s) => Math.round(new Date(`${s.day}T12:00:00Z`).getTime() / 1000))
|
||||
return {
|
||||
size: { data: [days, (detail.size || []).map((s) => s.bytes)], series: [{ label: 'Größe', color: '#3b82f6', fill: 'rgba(59,130,246,0.10)' }] },
|
||||
mem: { data: [(detail.mem || []).map((p) => Math.round(p.ts / 1000)), (detail.mem || []).map((p) => p.v)], series: [{ label: 'RAM', color: '#10b981' }] },
|
||||
cpu: { data: [(detail.cpu || []).map((p) => Math.round(p.ts / 1000)), (detail.cpu || []).map((p) => p.v)], series: [{ label: 'CPU', color: '#f59e0b' }] },
|
||||
size: { data: [days, (detail.size || []).map((s) => s.bytes)], series: [{ label: 'Größe', color: CHART.line, fill: CHART.fill }] },
|
||||
mem: { data: [(detail.mem || []).map((p) => Math.round(p.ts / 1000)), (detail.mem || []).map((p) => p.v)], series: [{ label: 'RAM', color: CHART.line }] },
|
||||
cpu: { data: [(detail.cpu || []).map((p) => Math.round(p.ts / 1000)), (detail.cpu || []).map((p) => p.v)], series: [{ label: 'CPU', color: CHART.line }] },
|
||||
}
|
||||
}, [detail])
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
.area-row.head { color: var(--text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.area-row.active { background: var(--accent-soft); }
|
||||
.area-muted { color: var(--text-muted); font-size: 13px; }
|
||||
.area-danger { color: #f87171; font-size: 13px; }
|
||||
.area-warn { color: #fbbf24; font-size: 13px; }
|
||||
.area-danger { color: var(--red-fg); font-size: 13px; }
|
||||
.area-warn { color: var(--orange-fg); font-size: 13px; }
|
||||
.area-summary { padding: 12px 16px; border-radius: var(--r-sm); background: var(--surface-2); margin-bottom: var(--sp-3); }
|
||||
.area-summary.warn { background: rgba(245, 158, 11, 0.16); color: #fbbf24; }
|
||||
.area-summary.warn { background: var(--orange-bg); color: var(--orange-fg); }
|
||||
.area-message { padding: 10px 14px; border-radius: var(--r-sm); background: var(--accent-soft); margin-bottom: var(--sp-3); }
|
||||
.area-error { padding: 10px 14px; border-radius: var(--r-sm); background: rgba(239, 68, 68, 0.14); color: #f87171; margin: var(--sp-2) 0; }
|
||||
.area-error { padding: 10px 14px; border-radius: var(--r-sm); background: var(--red-bg); color: var(--red-fg); margin: var(--sp-2) 0; }
|
||||
.area-kv { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; margin: 0; }
|
||||
.area-kv dt { color: var(--text-muted); }
|
||||
.area-kv dd { margin: 0; overflow-wrap: anywhere; }
|
||||
|
||||
+13
-6
@@ -2,25 +2,32 @@
|
||||
chat-composer.css des Kundenportals; hier Farben (--chat-*), Verlauf, Sprechblasen und die Liste. */
|
||||
.woms-chat {
|
||||
--chat-accent: var(--accent);
|
||||
--chat-accent-hover: var(--accent-2);
|
||||
--chat-accent-hover: var(--accent-hover);
|
||||
--chat-accent-soft: var(--accent-soft);
|
||||
--chat-accent-line: rgba(16, 185, 129, 0.5);
|
||||
--chat-accent-line: rgba(17, 17, 17, 0.14);
|
||||
--chat-accent-text: var(--accent);
|
||||
--chat-on-accent: #04130d;
|
||||
--chat-surface: var(--surface-2);
|
||||
--chat-on-accent: #ffffff;
|
||||
--chat-surface: var(--bg);
|
||||
--chat-line: var(--line-strong);
|
||||
--chat-muted: var(--text-2);
|
||||
display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0;
|
||||
}
|
||||
.woms-chat .chat-messages {
|
||||
display: flex; flex-direction: column; gap: 8px; min-height: 280px; max-height: 60vh; overflow-y: auto;
|
||||
padding: 12px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface);
|
||||
padding: 12px; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg);
|
||||
}
|
||||
.woms-chat .chat-bubble {
|
||||
align-self: flex-start; max-width: 78%; padding: 8px 12px; border-radius: 14px;
|
||||
background: var(--surface-2); color: var(--text); overflow-wrap: anywhere;
|
||||
}
|
||||
.woms-chat .chat-bubble.mine { align-self: flex-end; background: var(--accent-soft); border: 1px solid rgba(16, 185, 129, 0.35); }
|
||||
.woms-chat .chat-bubble.mine { align-self: flex-end; background: var(--accent-soft); border: 1px solid var(--line); }
|
||||
.woms-chat .chat-meta { display: block; margin-top: 4px; font-size: 11.5px; color: var(--text-muted); }
|
||||
.woms-chat .chat-notice { font-size: 12px; }
|
||||
/* Meldungen des Eingabebausteins (Stile aus chat-composer.css des Portals): Fehler rot, Info grau, beides lesbar auf Weiss */
|
||||
.woms-chat .chat-composer-note,
|
||||
.woms-chat .chat-tray-item.is-error .chat-file-size,
|
||||
.woms-chat .chat-audio.is-error .chat-audio-time { color: var(--red-fg); }
|
||||
.woms-chat .chat-composer-note.is-info { color: var(--text-2); }
|
||||
/* Eingabefeld: Grundstil wie im frueheren Admin-Panel (chat-composer.css setzt nur das Layout) */
|
||||
.woms-chat .chat-input-row textarea {
|
||||
resize: none; min-height: 46px; max-height: 130px; padding: 11px 14px; border-radius: 14px;
|
||||
|
||||
@@ -676,7 +676,7 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
|
||||
.legal-tone-red td { background: rgba(239, 68, 68, 0.10); }
|
||||
.legal-tone-red .legal-deadline { color: var(--danger); font-weight: 700; }
|
||||
.legal-tone-orange td { background: rgba(245, 158, 11, 0.10); }
|
||||
.legal-tone-orange .legal-deadline { color: #b45309; font-weight: 600; }
|
||||
.legal-tone-orange .legal-deadline { color: var(--orange-fg); font-weight: 600; }
|
||||
.legal-tone-missing .legal-deadline { color: var(--text-faint); font-style: italic; }
|
||||
.legal-tone-grey td { color: var(--text-faint); }
|
||||
.legal-detail td { background: var(--surface-2); cursor: default; }
|
||||
@@ -909,13 +909,13 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
|
||||
.fin-dialog:focus { outline: none; }
|
||||
.fin-dialog-error {
|
||||
margin-bottom: var(--sp-4); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-sm);
|
||||
border: 1px solid var(--danger); background: rgba(239, 68, 68, 0.16); color: #fecaca; overflow-wrap: anywhere;
|
||||
border: 1px solid var(--danger); background: var(--red-bg); color: var(--red-fg); overflow-wrap: anywhere;
|
||||
}
|
||||
.fin-dialog-warn { margin: 6px 0 0; font-size: 13px; color: #fbbf24; overflow-wrap: anywhere; }
|
||||
.fin-dialog-warn { margin: 6px 0 0; font-size: 13px; color: var(--orange-fg); overflow-wrap: anywhere; }
|
||||
.fin-dialog-sub { margin: 6px 0 0; font-size: 13px; color: var(--text-muted); overflow-wrap: anywhere; }
|
||||
.fin-fixed-customer { margin: 0; overflow-wrap: anywhere; }
|
||||
.fin-address-warn { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--sp-3); font-size: 13px; overflow-wrap: anywhere; }
|
||||
.fin-address-warn-dialog { margin: 6px 0 0; color: #fbbf24; }
|
||||
.fin-address-warn-dialog { margin: 6px 0 0; color: var(--orange-fg); }
|
||||
.fin-address-warn-page { margin: 0 0 var(--sp-4); padding: var(--sp-3) var(--sp-4); border: 1px solid var(--warn); border-radius: var(--r-sm); color: var(--warn); }
|
||||
.fin-address-warn .fin-link { font-size: 13px; }
|
||||
.fin-items { border: 0; padding: 0; margin: 0 0 var(--sp-4); min-width: 0; }
|
||||
@@ -932,7 +932,7 @@ a.list-row:hover .cell-title { color: var(--accent-ink); }
|
||||
width: 38px; height: 40px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
|
||||
background: transparent; border: 1px solid var(--border-strong); border-radius: 6px; color: var(--text-muted);
|
||||
}
|
||||
.fin-icon-btn:hover:not(:disabled) { color: #f87171; border-color: #f87171; }
|
||||
.fin-icon-btn:hover:not(:disabled) { color: var(--red-fg); border-color: var(--red-fg); }
|
||||
.fin-icon-btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
||||
.fin-icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.fin-items-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-top: var(--sp-3); }
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/* Serverpuls (Admin) */
|
||||
.sp-page { display: flex; flex-direction: column; gap: var(--sp-4); }
|
||||
.sp-error { background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.4); color: #fca5a5; padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); }
|
||||
.sp-error { background: var(--red-bg); border: 1px solid var(--danger-ring); color: var(--red-fg); padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md); }
|
||||
.sp-muted { color: var(--text-muted); }
|
||||
.sp-small { font-size: 12px; }
|
||||
|
||||
.sp-head { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-4); }
|
||||
.sp-level-medium { border-color: rgba(245,158,11,.5); }
|
||||
.sp-level-critical { border-color: rgba(239,68,68,.6); }
|
||||
.sp-level-medium { border-color: var(--orange-fg); }
|
||||
.sp-level-critical { border-color: var(--danger); }
|
||||
.sp-head-top { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
|
||||
.sp-headline { font-size: 18px; }
|
||||
.sp-age { margin-left: auto; font-size: 13px; color: var(--text-muted); }
|
||||
.sp-age-stale { color: #fbbf24; background: rgba(245,158,11,.14); padding: 2px 10px; border-radius: var(--r-pill); }
|
||||
.sp-age-stale { color: var(--orange-fg); background: var(--orange-bg); padding: 2px 10px; border-radius: var(--r-pill); }
|
||||
.sp-alerts { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.sp-alert { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: baseline; padding: 8px 12px; border-radius: var(--r-sm); background: var(--surface-2); border-left: 3px solid var(--text-faint); }
|
||||
.sp-alert-critical { border-left-color: var(--danger); }
|
||||
|
||||
Reference in new issue
Block a user