diff --git a/RELEASE.md b/RELEASE.md index c925ede0..cad16458 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,6 +12,23 @@ the `0` read like a measured "not faulted". Values the gateway reports are unchanged. Telegraf skips `null` fields, and no Powerwall-Dashboard panel uses these flags. Same fix as proxy t115 (pypowerwall#179) +- **Power Flow battery percentage at low state of charge** - the SoE label is + bottom-aligned inside the battery graphic when it would otherwise overflow, + so values such as `4%` are no longer cut off at the bottom of the Power Flow + card. +- **Power Flow grid and home labels stay above their icons on narrow cards** - + the labels were anchored to the edges of the diagram, so the right-hand label + could run past the card. They are now centered over the respective icons, and + the two circles move closer together on narrow iframes instead of running + outside the card. +- **Gateway firmware stays readable on narrow Console cards** - the mode and + firmware badges no longer split across lines or squeeze the connection + status; the status sits with the host/IP line, and gateway cards stay wide + enough when multiple gateways share a row. +- **Console MQTT and control status pills are optically centered** - the + `Connected` and `Control enabled` labels use a centered flex line box instead + of sitting slightly low inside the pill; gateway mode/firmware tags and the + active header link use the same optical alignment. ### [0.16.0] - 2026-11-10 diff --git a/app/static/css/page.css b/app/static/css/page.css index 1af30287..f22f930e 100644 --- a/app/static/css/page.css +++ b/app/static/css/page.css @@ -79,12 +79,18 @@ header { } .header-links a { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 36px; color: var(--text-secondary); text-decoration: none; - padding: 8px 16px; + padding: 0 16px; border-radius: 6px; transition: all 0.2s ease; font-size: 0.9em; + line-height: 1; + box-sizing: border-box; } .header-links a:hover, diff --git a/app/static/index.html b/app/static/index.html index 4f7ef9c5..52c6f642 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -1390,6 +1390,16 @@ background: rgba(248, 81, 73, 0.15); color: var(--accent-red); } + .mqtt-status-badge, + .control-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 23px; + padding: 0 10px; + line-height: 1; + box-sizing: border-box; + } .mqtt-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); @@ -1405,14 +1415,16 @@ .gateway-list { display: grid; - grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 16px; } .gateway-item { - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; align-items: center; + column-gap: 12px; + row-gap: 4px; padding: 16px; background: var(--bg-secondary); border-radius: 8px; @@ -1433,25 +1445,33 @@ } .gateway-info { - display: flex; - flex-direction: column; - gap: 4px; + display: contents; } .gateway-name { + grid-column: 1 / -1; + grid-row: 1; + min-width: 0; font-weight: 600; font-size: 1em; + overflow-wrap: anywhere; } .gateway-host { + grid-column: 1; + grid-row: 2; + min-width: 0; font-size: 0.85em; color: var(--text-muted); + overflow-wrap: anywhere; } .gateway-details { display: flex; + flex-wrap: wrap; + grid-column: 1 / -1; + grid-row: 3; gap: 8px; - margin-top: 4px; } .gateway-mode { @@ -1460,6 +1480,7 @@ border-radius: 4px; background: var(--bg-card); color: var(--text-secondary); + white-space: nowrap; } .gateway-firmware { @@ -1468,10 +1489,23 @@ border-radius: 4px; background: rgba(88, 166, 255, 0.15); color: var(--accent-blue); + white-space: nowrap; } - + + .gateway-mode, + .gateway-firmware { + display: inline-flex; + align-items: center; + min-height: 20px; + padding: 1px 8px 3px; + line-height: 1; + box-sizing: border-box; + } + .gateway-status { display: flex; + grid-column: 2; + grid-row: 2; align-items: center; gap: 8px; } @@ -1503,9 +1537,9 @@ } .gateway-error { + grid-column: 1 / -1; font-size: 0.75em; color: var(--accent-red); - margin-top: 4px; } /* Footer */ diff --git a/app/static/powerflow/app.js b/app/static/powerflow/app.js index 75d685fc..1d55addb 100644 --- a/app/static/powerflow/app.js +++ b/app/static/powerflow/app.js @@ -23722,6 +23722,7 @@ ft(this, "_handleIslandingClick", this._handleIslandingClick.bind(this)), (this.state = { activeIcons: [], + viewportWidth: window.innerWidth, colors: { batteryToHome: et.c, batteryToGrid: et.c, @@ -23739,9 +23740,20 @@ componentWillReceiveProps(e) { this.setState({ activeIcons: bt.getActiveIcons(e) }); } + componentDidMount() { + this._handleViewportResize = () => this.setState({ viewportWidth: window.innerWidth }); + window.addEventListener("resize", this._handleViewportResize); + } + componentWillUnmount() { + window.removeEventListener("resize", this._handleViewportResize); + } + _getEffectiveGridWidth() { + // Keep 68px per side for the grid and home circles, which sit + // outside the diagram's edges, so they fit narrow iframes + return Math.min(this.props.gridWidth, Math.max(0, window.innerWidth - 136)); + } render() { const { - gridWidth: e, gridHeight: t, activeTypes: i, activeGlowRadius: n, @@ -23792,6 +23804,7 @@ soe: K, } = this.props, { activeIcons: Y, colors: Q } = this.state, + e = this._getEffectiveGridWidth(), Z = 0 === i.length, J = e / 2, X = t / 2, @@ -24290,8 +24303,9 @@ case et.h.GRID: ((b = this.props.gridPower && f), (I.top = g ? 0.5 * s + 0.5 * o + 2 * _ : 1.5 * s + 0.5 * o + 3 * _ + T + l + d), - (I.left = 0), - (I.right = a + s + 2 * _), + (I.left = `calc(50% - ${a / 2 + s / 2 + _}px)`), + (I.right = "auto"), + (I.transform = "translateX(-50%)"), (I.color = et.d)); break; case et.h.BATTERY: @@ -24305,8 +24319,9 @@ case et.h.USAGE: ((b = this.props.loadPower && v), (I.top = g ? 0.5 * s + 0.5 * o + 2 * _ : 1.5 * s + 0.5 * o + 3 * _ + T + l + d), - (I.left = a + s + 2 * _), - (I.right = 0), + (I.left = `calc(50% + ${a / 2 + s / 2 + _}px)`), + (I.right = "auto"), + (I.transform = "translateX(-50%)"), (I.color = et.g)); } const O = !t.includes(n), @@ -24386,6 +24401,9 @@ s = e ? 267 : 94, _ = e ? 212 : 94, l = e ? 172 : 157; + const labelTop = n + (_ * (100 - a)) / 100 - i / 2, + labelBottomAligned = labelTop > s - i - 1, + gridWidth = this._getEffectiveGridWidth(); ((d = c.createElement( "svg", { className: "powerwall-soe", width: r, height: s, viewBox: `0 0 ${r} ${s}` }, @@ -24410,10 +24428,12 @@ "data-testid": "234320a0-eab5-4528-bc42-aab6c7282f13", className: "label-container soe-label", style: { - top: Math.max(0, n + (_ * (100 - a)) / 100 - i / 2), + top: labelBottomAligned ? "auto" : Math.max(0, labelTop), + bottom: labelBottomAligned ? 0 : "auto", + lineHeight: labelBottomAligned ? "normal" : void 0, left: e - ? 0.5 * this.props.gridWidth - t + 191 - : r + (0.5 * this.props.gridWidth - t) + 12, + ? 0.5 * gridWidth - t + 191 + : r + (0.5 * gridWidth - t) + 12, color: o ? "white" : "black", }, },