From c525619f5b575d6b07a47b3185a63501f3fd5def Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Nov 2024 20:30:44 -0800 Subject: [PATCH 1/2] Power and Thermal Specification for RPE - Front end --- src/components/Tables/PowerSummaryTable.js | 302 ++++++++++++++++----- src/components/style/PowerSummaryTable.css | 186 ++++++++++++- 2 files changed, 405 insertions(+), 83 deletions(-) diff --git a/src/components/Tables/PowerSummaryTable.js b/src/components/Tables/PowerSummaryTable.js index a25d9cbb..8330f2a2 100644 --- a/src/components/Tables/PowerSummaryTable.js +++ b/src/components/Tables/PowerSummaryTable.js @@ -1,11 +1,11 @@ -import React from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { IoMdCloseCircleOutline } from 'react-icons/io'; import { Tooltip } from 'antd'; import { PowerCell } from './TableCells'; import { fixed, color } from '../../utils/common'; import { State } from '../ComponentsLib'; - +import { api, PATCH, GET } from '../../utils/serverAPI'; import '../style/PowerSummaryTable.css'; function PowerSummaryTableToolTip({ title, statusColor }) { @@ -17,91 +17,254 @@ function PowerSummaryTableToolTip({ title, statusColor }) { ); } + function PowerSummaryTable({ - title, data, total, percent, + title, data = [], total = 0, percent = 0, deviceId = 'MPW1' }) { - function getErrors(messages) { - if (messages === undefined) return []; - const errors = messages.filter((item) => item.filter((inner) => inner.type === 'error').length > 0); - return errors; - } - function getWarning(messages) { - if (messages === undefined) return []; - const warnings = messages.filter((item) => item.filter((inner) => inner.type === 'warn').length > 0); - return warnings; - } - function buildMessage(messages) { - return messages.reduce((sum, item, currentIndex) => { - item.forEach((i, index) => sum.push( - // eslint-disable-next-line react/no-array-index-key - - {i.text} -
-
, - )); - return sum; - }, []); - } - function message(messages) { - const errors = getErrors(messages); - if (errors.length > 0) { - return buildMessage(errors); + const [thermalData, setThermalData] = useState({ + ambientTypical: 25, + ambientWorstCase: 50, + thetaJa: 10, + }); + + const [powerData, setPowerData] = useState({ + powerBudget: 1.00, + fpgaScaling: 25, + pcScaling: 25, + }); + + const ambientTypicalRef = useRef(null); + const ambientWorstCaseRef = useRef(null); + const thetaJaRef = useRef(null); + const powerBudgetRef = useRef(null); + const fpgaScalingRef = useRef(null); + const pcScalingRef = useRef(null); + + useEffect(() => { + const fetchData = async () => { + try { + GET(api.consumption('consumption', deviceId), (result) => { + if (result && result.specification) { + const { specification } = result; + setThermalData({ + ambientTypical: specification.thermal.ambient.typical, + ambientWorstCase: specification.thermal.ambient.worstcase, + thetaJa: specification.thermal.theta_ja, + }); + setPowerData({ + powerBudget: specification.power.budget, + fpgaScaling: specification.power.typical_dynamic_scaling.fpga_complex * 100, + pcScaling: specification.power.typical_dynamic_scaling.processing_complex * 100, + }); + } + }); + } catch (error) { + console.error("Error fetching data:", error); + } + }; + + fetchData(); + }, [deviceId]); + + const updateBackend = async (updatedData) => { + try { + PATCH(api.deviceInfo(deviceId), updatedData, () => { + console.log('Backend updated successfully'); + fetchData(); // refetching to sync UI with backend + }); + } catch (error) { + console.error("Error updating backend:", error); } - const warnings = getWarning(messages); - if (warnings.length > 0) { - return buildMessage(warnings); + }; + + const handleFieldUpdate = (field, value) => { + const updatedThermalData = { ...thermalData }; + const updatedPowerData = { ...powerData }; + + if (field in thermalData) { + updatedThermalData[field] = value; + } else { + updatedPowerData[field] = value; } - return ''; - } - - function isError(messages) { return getErrors(messages).length > 0; } - function isWarning(messages) { return getWarning(messages).length > 0; } - function statusColor(messages) { - return color(isError(messages), isWarning(messages)); - } + + setThermalData(updatedThermalData); + setPowerData(updatedPowerData); + + const updatedData = { + specification: { + thermal: updatedThermalData, + power: updatedPowerData, + }, + }; + + updateBackend(updatedData); + }; + + const enforceNumericInput = (e) => { + const value = e.target.value; + const valid = /^-?\d*\.?\d*%?$/.test(value); + if (!valid) { + e.target.value = value.slice(0, -1); + } + }; + + const handleKeyDown = (e, nextFieldRef) => { + if (e.key === 'Enter' && nextFieldRef && nextFieldRef.current) { + nextFieldRef.current.focus(); + } + }; + + const getErrors = (messages) => messages?.filter((item) => item.some((inner) => inner.type === 'error')) || []; + const getWarnings = (messages) => messages?.filter((item) => item.some((inner) => inner.type === 'warn')) || []; + + const buildMessage = (messages) => + messages.reduce((acc, item, currentIndex) => { + item.forEach((i, index) => acc.push({i.text}
)); + return acc; + }, []); + + const message = (messages) => { + const errors = getErrors(messages); + return errors.length > 0 ? buildMessage(errors) : buildMessage(getWarnings(messages)); + }; + + const statusColor = (messages) => color(getErrors(messages).length > 0, getWarnings(messages).length > 0); + return (
-
{title}
+ {title === 'FPGA Complex and Core Power' && ( +
+
Thermal Specification
+ + + + + + + + + + + + + + + + + + +
TypicalWorst-Case
Ambient + handleFieldUpdate('ambientTypical', e.target.value)} + onInput={enforceNumericInput} + ref={ambientTypicalRef} + onKeyDown={(e) => handleKeyDown(e, ambientWorstCaseRef)} + /> °C + + handleFieldUpdate('ambientWorstCase', e.target.value)} + onInput={enforceNumericInput} + ref={ambientWorstCaseRef} + onKeyDown={(e) => handleKeyDown(e, thetaJaRef)} + /> °C +
+ ΘJA: + handleFieldUpdate('thetaJa', e.target.value)} + onInput={enforceNumericInput} + ref={thetaJaRef} + onKeyDown={(e) => handleKeyDown(e, powerBudgetRef)} + /> °C/W +
+ +
Power Specification
+ + + + + + + + + + + + +
Power Budget + handleFieldUpdate('powerBudget', e.target.value)} + onInput={enforceNumericInput} + ref={powerBudgetRef} + onKeyDown={(e) => handleKeyDown(e, fpgaScalingRef)} + /> W +
Typical Dynamic Scaling % + FPGA: + handleFieldUpdate('fpgaScaling', e.target.value)} + onInput={enforceNumericInput} + ref={fpgaScalingRef} + onKeyDown={(e) => handleKeyDown(e, pcScalingRef)} + /> % + + PC: + handleFieldUpdate('pcScaling', e.target.value)} + onInput={enforceNumericInput} + ref={pcScalingRef} + /> % +
+
+ )} + +
{title || 'FPGA Complex and Core Power'}
- { - data.map((item, index) => ( - // eslint-disable-next-line react/no-array-index-key - - - - - - - - )) - } + {data.map((item, index) => ( + + + + + + + + ))}
{item.text} - {`${fixed(item.percent, 0)} %`} - - { - (isError(item.messages) || isWarning(item.messages)) && ( - - ) - } -
{item.text || 'N/A'} + {`${fixed(item.percent || 0, 0)} %`} + + {(getErrors(item.messages).length > 0 || getWarnings(item.messages).length > 0) && ( + + )} +
+
- +
Total - {` ${fixed(total)} W`} + {` ${fixed(total || 0)} W`}
@@ -110,11 +273,10 @@ function PowerSummaryTable({ PowerSummaryTable.propTypes = { title: PropTypes.string.isRequired, - data: PropTypes.oneOfType([ - PropTypes.array, - ]).isRequired, + data: PropTypes.array.isRequired, total: PropTypes.number.isRequired, percent: PropTypes.number.isRequired, + deviceId: PropTypes.string.isRequired, }; export default PowerSummaryTable; diff --git a/src/components/style/PowerSummaryTable.css b/src/components/style/PowerSummaryTable.css index c98c0c5e..e8e04e20 100644 --- a/src/components/style/PowerSummaryTable.css +++ b/src/components/style/PowerSummaryTable.css @@ -1,36 +1,179 @@ .pst-container { - padding: 8px; + padding: 4px; display: flex; flex-direction: column; - row-gap: 5px; + row-gap: 2px; height: 100%; + width: 100%; + box-sizing: border-box; +} + +/* Styling for the Thermal and Power Specification section */ +.thermal-power-specification { + padding: 4px; + background-color: #f0f7f5; + border: 1px solid #d1d1d1; + border-radius: 6px; + margin-bottom: 4px; + display: flex; + flex-direction: column; + row-gap: 4px; + width: 100%; + margin-left: auto; + margin-right: auto; + box-sizing: border-box; +} + +/* Header styling for the Thermal and Power Specification */ +.spec-header { + font-weight: bold; + font-size: 11px; + text-align: center; + color: #333; + background-color: #e1e1e1; + padding: 4px; + border: 1px solid #c0c0c0; + border-radius: 4px; +} + +/* Table styling for the specification sections */ +.spec-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 4px; +} + +.spec-table th, .spec-table td { + padding: 3px; + text-align: center; + border: 1px solid #d1d1d1; + font-size: 10px; +} + +/* Styling for headers in the Typical and Worst-Case columns */ +.spec-table .typical-header, .spec-table .worst-header { + font-weight: bold; + background-color: #f0f0f0; + border-bottom: 1px solid #c0c0c0; +} + +/* Special handling for ΘJA row that spans across two columns */ +.spec-table .theta-row td { + text-align: center; + font-weight: bold; + color: #444; + padding: 3px; + border-right: 1px solid #d1d1d1; + border-left: 1px solid #d1d1d1; +} + +/* Input field styling */ +.spec-table td input { + width: 45px; + padding: 2px; + font-size: 10px; + text-align: center; +} + +/* Bottom section for Power Specification */ +.power-spec-section { + padding: 4px; + background-color: #f0f7f5; + border: 1px solid #d1d1d1; + border-radius: 6px; + margin-bottom: 4px; + width: 100%; + margin-left: auto; + margin-right: auto; +} + +/* Table for Power Specification */ +.power-spec-table { + width: 100%; + border-collapse: collapse; +} + +.power-spec-table th, .power-spec-table td { + padding: 3px; + text-align: center; + border: 1px solid #d1d1d1; + font-size: 10px; +} + +/* Styling for power budget and dynamic scaling */ +.power-spec-table .scaling-cell { + font-weight: bold; + color: #444; } +/* Input fields in Power Specification */ +.power-spec-table td input { + width: 45px; + padding: 2px; + text-align: center; + font-size: 10px; +} + +/* Header styling for both Thermal and Power sections */ +.bold-text-title { + font-weight: bold; + font-size: 11px; + color: #333; + text-align: left; +} + +/* FPGA Complex and Core Power table */ .pst-table { width: 100%; border-collapse: collapse; + box-sizing: border-box; + height: auto; /* Changed height to auto to fit content */ + display: table; + table-layout: fixed; /* Ensure equal column widths */ + margin-bottom: 0; /* Removed extra margin for symmetrical alignment */ } .pst-table td { - padding-top: 0.5em; - padding-bottom: 0.5em; + padding: 0.20em 0.99em; /* Adjusted padding for consistent spacing */ + border-bottom: 1px solid #e1e1e1; + text-align: left; /* Align content to the left */ + font-size: 10px; + width: 100%; } -.pst-table td { +/* Processing Complex (SOC) Power table */ +.pst-table-soc { + width: 100%; + border-collapse: collapse; + box-sizing: border-box; + height: auto; /* Changed height to auto to fit content */ + display: table; + table-layout: fixed; + margin-bottom: 0; /* Ensures symmetrical gap with the FPGA table */ +} + +.pst-table-soc td { + padding: 0.80em 0.99em; /* Same padding adjustments for consistency */ border-bottom: 1px solid #e1e1e1; + text-align: left; + font-size: 20px; + width: 100%; } -.pst-table tbody>tr:last-child>td { +.pst-table tbody>tr:last-child>td, +.pst-table-soc tbody>tr:last-child>td { border-bottom: 0; } +/* Bottom section */ .pst-bottom { display: flex; flex-direction: row; + justify-content: space-between; border-top: 1px solid #e1e1e1; - margin-top: 10px; - padding-top: 3px; - padding-bottom: 3px; + margin-top: 0; + padding-top: 2px; + padding-bottom: 2px; align-items: flex-end; } @@ -40,23 +183,40 @@ .pst-bottom-progress { text-align: left; + width: 50%; } .pst-bottom-total { text-align: right; - width: 100%; + width: 50%; color: #7c7c7c; - font-size: 17px; + font-size: 10px; } +/* Styling for dot icons */ .dot-td { text-align: center; } .dot { - width: 10px; - height: 10px; + width: 8px; + height: 8px; border-radius: 50%; display: inline-block; vertical-align: middle; } + +/* Remove unnecessary bottom space */ +.pst-container, +.pst-table, +.pst-table-soc { + margin-bottom: 0; + height: 100%; +} + +/* Ensure table fills the entire container */ +.pst-table, .pst-table-soc { + display: flex; + flex-direction: column; + justify-content: space-between; +} From d82f088dcb73f76027d42a161152c11e56d9a44b Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Nov 2024 20:54:40 -0800 Subject: [PATCH 2/2] fixing eslint error --- src/components/Tables/PowerSummaryTable.js | 128 +++++++++++++-------- 1 file changed, 77 insertions(+), 51 deletions(-) diff --git a/src/components/Tables/PowerSummaryTable.js b/src/components/Tables/PowerSummaryTable.js index 8330f2a2..160d2dcf 100644 --- a/src/components/Tables/PowerSummaryTable.js +++ b/src/components/Tables/PowerSummaryTable.js @@ -5,7 +5,7 @@ import { Tooltip } from 'antd'; import { PowerCell } from './TableCells'; import { fixed, color } from '../../utils/common'; import { State } from '../ComponentsLib'; -import { api, PATCH, GET } from '../../utils/serverAPI'; +import { api, PATCH, GET } from '../../utils/serverAPI'; import '../style/PowerSummaryTable.css'; function PowerSummaryTableToolTip({ title, statusColor }) { @@ -18,8 +18,13 @@ function PowerSummaryTableToolTip({ title, statusColor }) { ); } +PowerSummaryTableToolTip.propTypes = { + title: PropTypes.string.isRequired, + statusColor: PropTypes.string.isRequired, +}; + function PowerSummaryTable({ - title, data = [], total = 0, percent = 0, deviceId = 'MPW1' + title, data = [], total = 0, percent = 0, deviceId = 'MPW1', }) { const [thermalData, setThermalData] = useState({ ambientTypical: 25, @@ -28,7 +33,7 @@ function PowerSummaryTable({ }); const [powerData, setPowerData] = useState({ - powerBudget: 1.00, + powerBudget: 1.0, fpgaScaling: 25, pcScaling: 25, }); @@ -43,23 +48,23 @@ function PowerSummaryTable({ useEffect(() => { const fetchData = async () => { try { - GET(api.consumption('consumption', deviceId), (result) => { - if (result && result.specification) { - const { specification } = result; - setThermalData({ - ambientTypical: specification.thermal.ambient.typical, - ambientWorstCase: specification.thermal.ambient.worstcase, - thetaJa: specification.thermal.theta_ja, - }); - setPowerData({ - powerBudget: specification.power.budget, - fpgaScaling: specification.power.typical_dynamic_scaling.fpga_complex * 100, - pcScaling: specification.power.typical_dynamic_scaling.processing_complex * 100, - }); - } - }); + const result = await GET(api.consumption('consumption', deviceId)); + if (result && result.specification) { + const { specification } = result; + setThermalData({ + ambientTypical: specification.thermal.ambient.typical, + ambientWorstCase: specification.thermal.ambient.worstcase, + thetaJa: specification.thermal.theta_ja, + }); + setPowerData({ + powerBudget: specification.power.budget, + fpgaScaling: specification.power.typical_dynamic_scaling.fpga_complex * 100, + pcScaling: specification.power.typical_dynamic_scaling.processing_complex * 100, + }); + } } catch (error) { - console.error("Error fetching data:", error); + // Log error for debugging purposes + console.error('Error fetching data:', error.message); } }; @@ -68,12 +73,10 @@ function PowerSummaryTable({ const updateBackend = async (updatedData) => { try { - PATCH(api.deviceInfo(deviceId), updatedData, () => { - console.log('Backend updated successfully'); - fetchData(); // refetching to sync UI with backend - }); + await PATCH(api.deviceInfo(deviceId), updatedData); } catch (error) { - console.error("Error updating backend:", error); + // Log error for debugging purposes + console.error('Error updating backend:', error.message); } }; @@ -96,39 +99,49 @@ function PowerSummaryTable({ power: updatedPowerData, }, }; - updateBackend(updatedData); }; const enforceNumericInput = (e) => { - const value = e.target.value; - const valid = /^-?\d*\.?\d*%?$/.test(value); - if (!valid) { + const { value } = e.target; + if (!/^-?\d*\.?\d*%?$/.test(value)) { e.target.value = value.slice(0, -1); } }; const handleKeyDown = (e, nextFieldRef) => { - if (e.key === 'Enter' && nextFieldRef && nextFieldRef.current) { + if (e.key === 'Enter' && nextFieldRef?.current) { nextFieldRef.current.focus(); } }; - const getErrors = (messages) => messages?.filter((item) => item.some((inner) => inner.type === 'error')) || []; - const getWarnings = (messages) => messages?.filter((item) => item.some((inner) => inner.type === 'warn')) || []; + const getErrors = (messages) => ( + messages?.filter((item) => item.some((inner) => inner.type === 'error')) || [] + ); + const getWarnings = (messages) => ( + messages?.filter((item) => item.some((inner) => inner.type === 'warn')) || [] + ); - const buildMessage = (messages) => - messages.reduce((acc, item, currentIndex) => { - item.forEach((i, index) => acc.push({i.text}
)); - return acc; - }, []); + const buildMessage = (messages) => messages.reduce((acc, item, currentIndex) => { + item.forEach((i, index) => acc.push( + // + + {i.text} +
+
, + )); + return acc; + }, []); const message = (messages) => { const errors = getErrors(messages); return errors.length > 0 ? buildMessage(errors) : buildMessage(getWarnings(messages)); }; - const statusColor = (messages) => color(getErrors(messages).length > 0, getWarnings(messages).length > 0); + const statusColor = (messages) => color( + getErrors(messages).length > 0, + getWarnings(messages).length > 0, + ); return (
@@ -138,7 +151,7 @@ function PowerSummaryTable({ - + @@ -154,7 +167,8 @@ function PowerSummaryTable({ onInput={enforceNumericInput} ref={ambientTypicalRef} onKeyDown={(e) => handleKeyDown(e, ambientWorstCaseRef)} - /> °C + /> + °C @@ -177,12 +192,12 @@ function PowerSummaryTable({ onInput={enforceNumericInput} ref={thetaJaRef} onKeyDown={(e) => handleKeyDown(e, powerBudgetRef)} - /> °C/W + /> + °C/W
Typical Worst-Case
handleKeyDown(e, thetaJaRef)} - /> °C + /> + °C
-
Power Specification
@@ -196,7 +211,8 @@ function PowerSummaryTable({ onInput={enforceNumericInput} ref={powerBudgetRef} onKeyDown={(e) => handleKeyDown(e, fpgaScalingRef)} - /> W + /> + W @@ -210,7 +226,8 @@ function PowerSummaryTable({ onInput={enforceNumericInput} ref={fpgaScalingRef} onKeyDown={(e) => handleKeyDown(e, pcScalingRef)} - /> % + /> + %
PC: @@ -220,20 +237,20 @@ function PowerSummaryTable({ onChange={(e) => handleFieldUpdate('pcScaling', e.target.value)} onInput={enforceNumericInput} ref={pcScalingRef} - /> % + /> + %
)} -
{title || 'FPGA Complex and Core Power'}
- {data.map((item, index) => ( - + {data.map((item) => ( + @@ -241,7 +258,8 @@ function PowerSummaryTable({ {`${fixed(item.percent || 0, 0)} %`}
{item.text || 'N/A'} - {(getErrors(item.messages).length > 0 || getWarnings(item.messages).length > 0) && ( + {(getErrors(item.messages).length > 0 + || getWarnings(item.messages).length > 0) && (
-
@@ -273,7 +290,16 @@ function PowerSummaryTable({ PowerSummaryTable.propTypes = { title: PropTypes.string.isRequired, - data: PropTypes.array.isRequired, + data: PropTypes.arrayOf(PropTypes.shape({ + uniqueId: PropTypes.string.isRequired, + text: PropTypes.string, + power: PropTypes.number, + percent: PropTypes.number, + messages: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + text: PropTypes.string, + })), + })).isRequired, total: PropTypes.number.isRequired, percent: PropTypes.number.isRequired, deviceId: PropTypes.string.isRequired,