refactor(theme): SFTP/监控/设置/高亮/锁屏面板颜色语义变量化;MonitorPanel 图表色随主题明暗派生(canvas 需实色)

This commit is contained in:
Bill committed 2026-10-16 20:29:53 +08:00
1 parent 83384b7233
commit beaa6f1f04
6 files changed
+72 -64

No files matched your search

+2 -2
View File
@@ -37,7 +37,7 @@
border: 1px solid var(--chrome-border, #2d2d2d);
border-radius: 10px;
background: var(--chrome-bg, #181818);
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.45);
box-shadow: 0 18px 48px var(--ot-shadow, rgba(0, 0, 0, 0.45));
text-align: center;
}
@@ -70,7 +70,7 @@
min-height: 18px;
font-size: 12px;
line-height: 18px;
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
.lock-screen-hint {
+21 -13
View File
@@ -1,7 +1,8 @@
import { useEffect, useRef, useState } from 'react'
import type { SysinfoMeta, SysinfoSample } from '@shared/sysinfo'
import { DEFAULT_LANGUAGE, tFor, type Language } from '@shared/i18n'
import { useSettingsStore } from '@renderer/settings/store'
import { useResolvedTheme, useSettingsStore } from '@renderer/settings/store'
import { isLightTheme, semanticVars } from '@renderer/theme/semantic'
import { useWorkspaceModeStore } from '../workspace/workspaceModeStore'
import './monitor.css'
@@ -105,11 +106,13 @@ function HistoryChart({ data, lineColor, fillColor, height, maxY }: HistoryChart
)
}
/** Red/green/yellow threshold bar color for CPU usage. */
function cpuColor(usage: number): string {
if (usage > 80) return '#f85149'
if (usage > 60) return '#e3b341'
return '#3fb950'
/** Red/green/yellow threshold bar color for CPU usage. Takes the semantic
* var map so light themes get the darkened (WCAG-compliant) variants —
* canvas fills need actual color strings, CSS vars don't resolve there. */
function cpuColor(usage: number, vars: Record<string, string>): string {
if (usage > 80) return vars.dangerFg
if (usage > 60) return vars.warningFg
return vars.successFg
}
/** Format a size in MB: show GB (1 decimal) once over 1024 MB. */
@@ -156,6 +159,11 @@ export function MonitorPanel({ sessionId }: MonitorPanelProps): React.JSX.Elemen
/** Switching workspace mode only hides the dockview (panels stay mounted), so
* polling has to stop explicitly while the SSH workspace is not visible. */
const workspaceMode = useWorkspaceModeStore((s) => s.mode)
const theme = useResolvedTheme()
const vars = semanticVars(theme.colors, isLightTheme(theme.colors.background))
// #rrggbb + alpha → 'rgba(r, g, b, a)' — canvas fill needs a solid color.
const withAlpha = (hex: string, a: number): string =>
`rgba(${[1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(', ')}, ${a})`
useEffect(() => {
if (workspaceMode !== 'ssh') return
@@ -226,15 +234,15 @@ export function MonitorPanel({ sessionId }: MonitorPanelProps): React.JSX.Elemen
<section className="mm-block" aria-label="CPU">
<div className="mm-block-row">
<span className="mm-label">CPU</span>
<span className="mm-value" style={{ color: cpuColor(cpu.usage) }}>
<span className="mm-value" style={{ color: cpuColor(cpu.usage, vars) }}>
{cpu.usage.toFixed(0)}%<span className="mm-value-sub"> · {tFor(language, 'ssh.monitor.cores', { n: cpu.cores })}</span>
</span>
</div>
<Bar pct={cpu.usage} color={cpuColor(cpu.usage)} />
<Bar pct={cpu.usage} color={cpuColor(cpu.usage, vars)} />
<div className="mm-sub mm-load">
{tFor(language, 'ssh.monitor.loadavg', { value: cpu.loadavg.map((v) => v.toFixed(1)).join(' / ') })}
</div>
<HistoryChart data={cpuHist} lineColor="#3fb950" fillColor="rgba(63,185,80,0.15)" height={48} maxY={100} />
<HistoryChart data={cpuHist} lineColor={vars.successFg} fillColor={withAlpha(vars.success, 0.15)} height={48} maxY={100} />
</section>
<section className="mm-block" aria-label={tFor(language, 'ssh.monitor.mem')}>
@@ -246,7 +254,7 @@ export function MonitorPanel({ sessionId }: MonitorPanelProps): React.JSX.Elemen
</div>
<div className="mm-mem-row">
<div className="mm-mem-bar">
<Bar pct={memPct} color="#d29922" />
<Bar pct={memPct} color={vars.warningFg} />
</div>
<span className="mm-mem-pct">{memPct.toFixed(0)}%</span>
</div>
@@ -266,7 +274,7 @@ export function MonitorPanel({ sessionId }: MonitorPanelProps): React.JSX.Elemen
{d.mount}
</span>
<div className="mm-disk-bar">
<Bar pct={pct} color={cpuColor(pct)} />
<Bar pct={pct} color={cpuColor(pct, vars)} />
</div>
<span className="mm-disk-size">
{formatSize(d.usedMb)} / {formatSize(d.totalMb)}
@@ -295,8 +303,8 @@ export function MonitorPanel({ sessionId }: MonitorPanelProps): React.JSX.Elemen
</div>
</div>
<div className="mm-net-charts">
<HistoryChart data={rxHist} lineColor="#58a6ff" fillColor="rgba(88,166,255,0.15)" height={40} />
<HistoryChart data={txHist} lineColor="#f2cc60" fillColor="rgba(242,204,96,0.15)" height={40} />
<HistoryChart data={rxHist} lineColor={vars.infoFg} fillColor={withAlpha(vars.info, 0.15)} height={40} />
<HistoryChart data={txHist} lineColor={vars.warningFg} fillColor={withAlpha(vars.warning, 0.15)} height={40} />
</div>
</section>
+15 -15
View File
@@ -19,10 +19,10 @@
}
.mm-error {
border: 1px solid #f85149;
border: 1px solid var(--ot-danger, #f85149);
border-radius: 6px;
background: rgba(248, 81, 73, 0.12);
color: #f85149;
background: color-mix(in srgb, var(--ot-danger, #f85149) 12%, transparent);
color: var(--ot-danger-fg, #f85149);
padding: 5px 8px;
font-size: 12px;
}
@@ -49,7 +49,7 @@
.mm-os {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -78,7 +78,7 @@
.mm-label {
font-size: 11px;
font-weight: 500;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
text-transform: uppercase;
letter-spacing: 0.04em;
user-select: none;
@@ -86,19 +86,19 @@
.mm-value {
font-size: 13px;
color: #3fb950;
color: var(--ot-success-fg, #3fb950);
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
.mm-value-sub {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.mm-sub {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.mm-load {
@@ -141,7 +141,7 @@
.mm-mem-pct {
flex: 0 0 auto;
font-size: 11px;
color: #d29922;
color: var(--ot-warning-fg, #d29922);
font-variant-numeric: tabular-nums;
min-width: 32px;
text-align: right;
@@ -177,7 +177,7 @@
.mm-disk-size {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
@@ -221,7 +221,7 @@
.mm-net-unit {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.mm-arrow {
@@ -231,17 +231,17 @@
}
.mm-up {
color: #58a6ff;
color: var(--ot-info-fg, #58a6ff);
}
.mm-down {
color: #f0a55a;
color: var(--ot-warning-fg, #f0a55a);
}
/* ---------- footer ---------- */
.mm-footer {
font-size: 11px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.mm-uptime {
@@ -253,7 +253,7 @@
align-items: center;
justify-content: center;
min-height: 120px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.mm-dots {
+6 -6
View File
@@ -79,12 +79,12 @@
}
.hl-pattern-bad {
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
.hl-warn-icon {
flex: 0 0 auto;
color: #f85149;
color: var(--ot-danger-fg, #f85149);
font-size: 12px;
line-height: 1;
cursor: help;
@@ -289,17 +289,17 @@
.hl-editor-error {
font-size: 12px;
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
.hl-editor-input-bad {
border-color: #f85149;
border-color: var(--ot-danger, #f85149);
}
.hl-editor-input-bad:hover,
.hl-editor-input-bad:focus {
border-color: #f85149;
box-shadow: 0 0 0 2px rgba(248, 81, 73, 0.2);
border-color: var(--ot-danger, #f85149);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ot-danger, #f85149) 20%, transparent);
}
.hl-editor-sub-label {
+10 -10
View File
@@ -200,12 +200,12 @@
antd's own checked colour loses to our text overrides, so pin it here. */
.settings-radio-group .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled),
.settings-language-group .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) {
color: #fff;
color: var(--tab-accent-fg, #fff);
}
.settings-radio-group .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover,
.settings-language-group .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover {
color: #fff;
color: var(--tab-accent-fg, #fff);
}
/* Own positive spacing: the shared `.settings-render-hint` uses a negative
@@ -299,8 +299,8 @@
}
.theme-card-selected {
border-color: #3b8eea;
box-shadow: 0 0 0 1px #3b8eea inset;
border-color: var(--ot-focus-ring, #3b8eea);
box-shadow: 0 0 0 1px var(--ot-focus-ring, #3b8eea) inset;
}
.theme-card-bg {
@@ -333,7 +333,7 @@
line-height: 1;
padding: 2px 5px;
border-radius: 4px;
background: rgba(59, 142, 234, 0.2);
background: var(--ot-focus-ring, rgba(59, 142, 234, 0.2));
color: color-mix(in srgb, var(--chrome-accent, #3b8eea) 55%, var(--chrome-fg, #cccccc));
}
@@ -510,11 +510,11 @@
}
.about-status.is-new {
color: #3fd06a;
color: var(--ot-success-fg, #3fd06a);
}
.about-error {
color: #f85149;
color: var(--ot-danger-fg, #f85149);
margin-top: 4px;
font-size: 12px;
word-break: break-all;
@@ -596,11 +596,11 @@
}
.settings-lock-feedback.is-ok {
color: #3fb950;
color: var(--ot-success-fg, #3fb950);
}
.settings-lock-feedback.is-error {
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
.settings-lock-note {
@@ -627,5 +627,5 @@
margin: -4px 0 10px;
padding-left: 2px;
font-size: 12px;
color: #d29922;
color: var(--ot-warning-fg, #d29922);
}
+18 -18
View File
@@ -24,7 +24,7 @@
.sftp-breadcrumb {
padding: 3px 8px;
font-size: 12px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
border-bottom: 1px solid var(--chrome-border);
overflow-x: auto;
white-space: nowrap;
@@ -152,17 +152,17 @@
}
.sftp-row-selected {
background: rgba(59, 142, 234, 0.18);
background: var(--ot-focus-ring, rgba(59, 142, 234, 0.18));
}
.sftp-icon {
display: inline-flex;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
font-size: calc(14px * var(--sftp-scale, 1));
}
.sftp-icon-dir {
color: #e3b341;
color: var(--ot-warning-fg, #e3b341);
}
.sftp-name {
@@ -175,7 +175,7 @@
.sftp-mode {
text-align: center;
color: #9a9a9a;
color: var(--ot-text-muted, #9a9a9a);
font-family: Consolas, 'Courier New', monospace;
font-size: var(--sftp-fs-meta, 12px);
white-space: nowrap;
@@ -184,7 +184,7 @@
.sftp-owner {
text-align: right;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
font-size: var(--sftp-fs-meta, 12px);
font-variant-numeric: tabular-nums;
white-space: nowrap;
@@ -192,7 +192,7 @@
.sftp-size {
text-align: right;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
font-size: var(--sftp-fs-meta, 12px);
font-variant-numeric: tabular-nums;
white-space: nowrap;
@@ -200,7 +200,7 @@
.sftp-time {
text-align: right;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
font-size: var(--sftp-fs-meta, 12px);
font-variant-numeric: tabular-nums;
white-space: nowrap;
@@ -210,12 +210,12 @@
.sftp-error {
padding: 12px;
text-align: center;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
font-size: 12px;
}
.sftp-error {
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
/* ---- permission / owner dialog ---- */
@@ -248,7 +248,7 @@
}
.sftp-perm-octal {
color: #3fb950;
color: var(--ot-success-fg, #3fb950);
}
.sftp-perm-grid {
@@ -264,7 +264,7 @@
align-items: center;
column-gap: 8px;
font-size: 12px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.sftp-perm-grid-head span {
@@ -305,7 +305,7 @@
.sftp-perm-label {
font-size: 12px;
color: #8a8a8a;
color: var(--ot-text-muted, #8a8a8a);
}
.sftp-perm-own-field .ant-input {
@@ -314,7 +314,7 @@
.sftp-perm-hint {
font-size: 11px;
color: #6a6a6a;
color: var(--ot-text-muted, #6a6a6a);
}
/* ---- global transfer overlay ---- */
@@ -339,7 +339,7 @@
border: 1px solid var(--chrome-border);
border-radius: 6px;
padding: 8px 10px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
box-shadow: 0 4px 16px var(--ot-shadow, rgba(0, 0, 0, 0.45));
}
.sftp-transfer-head {
@@ -357,11 +357,11 @@
}
.sftp-transfer-done .sftp-transfer-title {
color: #3fb950;
color: var(--ot-success-fg, #3fb950);
}
.sftp-transfer-error .sftp-transfer-title {
color: #f85149;
color: var(--ot-danger-fg, #f85149);
}
.sftp-transfer-cancelled .sftp-transfer-title {
@@ -423,7 +423,7 @@
.sftp-transfer-err {
font-size: 11px;
color: #f85149;
color: var(--ot-danger-fg, #f85149);
margin-top: 3px;
word-break: break-all;
}