diff --git a/csharp/App/SaliMax/deploy_all_installations.sh b/csharp/App/SaliMax/deploy_all_installations.sh index 9368fde0a..8b3c12c57 100755 --- a/csharp/App/SaliMax/deploy_all_installations.sh +++ b/csharp/App/SaliMax/deploy_all_installations.sh @@ -17,6 +17,8 @@ dotnet publish \ echo -e "\n============================ Deploy ============================\n" ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29") +#ip_addresses=("10.2.4.154" "10.2.4.29") + for ip_address in "${ip_addresses[@]}"; do rsync -v \ diff --git a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs index ca8d088a5..07e29161f 100644 --- a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs +++ b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs @@ -1,3 +1,5 @@ +using System.IO.Compression; +using System.Text; using System.Text.Json; using Flurl.Http; using InnovEnergy.App.SaliMax.Devices; @@ -68,7 +70,37 @@ public class AggregatedData var s3Path = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".csv"; var request = _S3Config.CreatePutRequest(s3Path); - var response = await request.PutAsync(new StringContent(csv)); + + // Compress CSV data to a byte array + byte[] compressedBytes; + using (var memoryStream = new MemoryStream()) + { + //Create a zip directory and put the compressed file inside + using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) + { + var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive + using (var entryStream = entry.Open()) + using (var writer = new StreamWriter(entryStream)) + { + writer.Write(csv); + } + } + + compressedBytes = memoryStream.ToArray(); + } + + // Encode the compressed byte array as a Base64 string + string base64String = Convert.ToBase64String(compressedBytes); + + // Create StringContent from Base64 string + var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64"); + + // Upload the compressed data (ZIP archive) to S3 + var response = await request.PutAsync(stringContent); + + // + // var request = _S3Config.CreatePutRequest(s3Path); + // var response = await request.PutAsync(new StringContent(csv)); if (response.StatusCode != 200) { diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx index ccf926149..4feb271c4 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx @@ -31,6 +31,21 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { const [selectedInstallation, setSelectedInstallation] = useState(-1); const currentLocation = useLocation(); + const sortedInstallations = [...props.installations].sort((a, b) => { + // Compare the status field of each installation and sort them based on the status. + //Installations with alarms go first + let a_status = getStatus(a.id); + let b_status = getStatus(b.id); + + if (a_status > b_status) { + return -1; + } + if (a_status < b_status) { + return 1; + } + return 0; + }); + const handleSelectOneInstallation = (installationID: number): void => { if (selectedInstallation != installationID) { setSelectedInstallation(installationID); @@ -100,7 +115,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { - {props.installations.map((installation) => { + {sortedInstallations.map((installation) => { const isInstallationSelected = installation.s3BucketId === selectedInstallation; diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx index 01d0a02fd..a141f0558 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx @@ -58,15 +58,24 @@ export const fetchData = ( if (r.status === 404) { return Promise.resolve(FetchResult.notAvailable); } else if (r.status === 200) { - const base64String = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text - const byteArray = Uint8Array.from(atob(base64String), (c) => + const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text + + //const response = await fetch(url); // Fetch the resource from the server + const contentEncoding = r.headers.get('content-type'); + + if (contentEncoding != 'application/base64; charset=utf-8') { + return parseCsv(csvtext); + } + + const byteArray = Uint8Array.from(atob(csvtext), (c) => c.charCodeAt(0) ); - // Decompress the byte array using JSZip + //Decompress the byte array using JSZip const zip = await JSZip.loadAsync(byteArray); // Assuming the CSV file is named "data.csv" inside the ZIP archive const csvContent = await zip.file('data.csv').async('text'); + return parseCsv(csvContent); } else { return Promise.resolve(FetchResult.notAvailable); diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index b21f0df56..4b96a1343 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -45,14 +45,10 @@ function Overview(props: OverviewProps) { const { currentUser } = context; const [dailyData, setDailyData] = useState(true); - const [weeklyData, setWeeklyData] = useState(false); - const [weeklybalance, setWeeklyBalance] = useState([]); - const [monthlybalance, setMonthlyBalance] = useState([]); - const [monthlyData, setMonthlyData] = useState(false); + const [aggregatedData, setAggregatedData] = useState(false); const [loading, setLoading] = useState(true); const [chartState, setChartState] = useState(0); - const [monthlyDateList, setMonthlyDateList] = useState([]); - const [weeklyDateList, setWeeklyDateList] = useState([]); + const [aggregatedChartState, setAggregatedChartState] = useState(0); const [isDateModalOpen, setIsDateModalOpen] = useState(false); const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false); const [dateSelectionError, setDateSelectionError] = useState(''); @@ -64,19 +60,18 @@ function Overview(props: OverviewProps) { }[] >([]); + const [aggregatedDataArray, setAggregatedDataArray] = useState< + { + chartData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + datelist: any[]; + netbalance: any[]; + }[] + >([]); + const [startDate, setStartDate] = useState(dayjs().add(-1, 'day')); const [endDate, setEndDate] = useState(dayjs()); - const [weeklyDataArray, setWeeklyDataArray] = useState<{ - chartData: chartAggregatedDataInterface; - chartOverview: overviewInterface; - }>({ chartData: null, chartOverview: null }); - - const [monthlyDataArray, setMonthlyDataArray] = useState<{ - chartData: chartAggregatedDataInterface; - chartOverview: overviewInterface; - }>({ chartData: null, chartOverview: null }); - useEffect(() => { const resultPromise: Promise<{ chartData: chartDataInterface; @@ -135,17 +130,19 @@ function Overview(props: OverviewProps) { const handle24HourData = () => { setDailyData(true); - setWeeklyData(false); - setMonthlyData(false); + setAggregatedData(false); setChartState(0); }; const handleWeekData = () => { setDailyData(false); - setWeeklyData(true); - setMonthlyData(false); + setAggregatedData(true); + setAggregatedChartState(0); - if (weeklyDataArray.chartData != null) { + if ( + aggregatedDataArray[aggregatedChartState] && + aggregatedDataArray[aggregatedChartState].chartData != null + ) { return; } setLoading(true); @@ -153,15 +150,15 @@ function Overview(props: OverviewProps) { const resultPromise: Promise<{ chartAggregatedData: chartAggregatedDataInterface; chartOverview: overviewInterface; - }> = transformInputToAggregatedData(props.s3Credentials, 'weekly'); + }> = transformInputToAggregatedData( + props.s3Credentials, + + dayjs().subtract(1, 'week'), + dayjs() + ); resultPromise .then((result) => { - setWeeklyDataArray({ - chartData: result.chartAggregatedData, - chartOverview: result.chartOverview - }); - const powerDifference = []; for ( let i = 0; @@ -174,8 +171,16 @@ function Overview(props: OverviewProps) { ); } - setWeeklyBalance(powerDifference); - setWeeklyDateList(computeLast7Days()); + setAggregatedDataArray((prevData) => + prevData.concat({ + chartData: result.chartAggregatedData, + chartOverview: result.chartOverview, + datelist: computeLast7Days(), + netbalance: powerDifference + }) + ); + + setAggregatedChartState(aggregatedDataArray.length); setLoading(false); }) .catch((error) => { @@ -212,88 +217,100 @@ function Overview(props: OverviewProps) { } setLoading(true); - const resultPromise: Promise<{ - chartData: chartDataInterface; - chartOverview: overviewInterface; - }> = transformInputToDailyData( - props.s3Credentials, - UnixTime.fromTicks(startDate.unix()), - UnixTime.fromTicks(endDate.unix()) - ); - resultPromise - .then((result) => { - setDailyDataArray((prevData) => - prevData.concat({ - chartData: result.chartData, - chartOverview: result.chartOverview - }) - ); + if (dailyData) { + const resultPromise: Promise<{ + chartData: chartDataInterface; + chartOverview: overviewInterface; + }> = transformInputToDailyData( + props.s3Credentials, + UnixTime.fromTicks(startDate.unix()), + UnixTime.fromTicks(endDate.unix()) + ); - setLoading(false); - setChartState(dailyDataArray.length); - }) - .catch((error) => { - console.error('Error:', error); - }); + resultPromise + .then((result) => { + setDailyDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setLoading(false); + setChartState(dailyDataArray.length); + }) + .catch((error) => { + console.error('Error:', error); + }); + } else { + setDailyData(false); + setAggregatedData(true); + + setLoading(true); + + const resultPromise: Promise<{ + chartAggregatedData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + dateList: string[]; + }> = transformInputToAggregatedData( + props.s3Credentials, + startDate, + endDate + ); + + resultPromise + .then((result) => { + const powerDifference = []; + + for ( + let i = 0; + i < result.chartAggregatedData.gridImportPower.data.length; + i++ + ) { + powerDifference.push( + result.chartAggregatedData.gridImportPower.data[i] - + Math.abs(result.chartAggregatedData.gridExportPower.data[i]) + ); + } + + setAggregatedDataArray((prevData) => + prevData.concat({ + chartData: result.chartAggregatedData, + chartOverview: result.chartOverview, + datelist: result.dateList, + netbalance: powerDifference + }) + ); + + setAggregatedChartState(aggregatedDataArray.length); + setLoading(false); + }) + .catch((error) => { + console.error('Error:', error); + }); + } }; const handleGoBack = () => { - if (chartState > 0) { + if (dailyData && chartState > 0) { setChartState(chartState - 1); } + if (aggregatedData && aggregatedChartState > 0) { + setAggregatedChartState(aggregatedChartState - 1); + } }; const handleGoForward = () => { - if (chartState + 1 < dailyDataArray.length) { + if (dailyData && chartState + 1 < dailyDataArray.length) { setChartState(chartState + 1); } - }; - - const handleMonthData = () => { - setDailyData(false); - setWeeklyData(false); - setMonthlyData(true); - - if (monthlyDataArray.chartData != null) { - return; + if ( + aggregatedData && + aggregatedChartState + 1 < aggregatedDataArray.length + ) { + setAggregatedChartState(aggregatedChartState + 1); } - setLoading(true); - - const resultPromise: Promise<{ - chartAggregatedData: chartAggregatedDataInterface; - chartOverview: overviewInterface; - dateList: string[]; - }> = transformInputToAggregatedData(props.s3Credentials, 'monthly'); - - resultPromise - .then((result) => { - setMonthlyDataArray({ - chartData: result.chartAggregatedData, - chartOverview: result.chartOverview - }); - - const powerDifference = []; - - for ( - let i = 0; - i < result.chartAggregatedData.gridImportPower.data.length; - i++ - ) { - powerDifference.push( - result.chartAggregatedData.gridImportPower.data[i] - - Math.abs(result.chartAggregatedData.gridExportPower.data[i]) - ); - } - - setMonthlyBalance(powerDifference); - - setMonthlyDateList(result.dateList); - setLoading(false); - }) - .catch((error) => { - console.error('Error:', error); - }); }; const renderGraphs = () => { @@ -438,86 +455,74 @@ function Overview(props: OverviewProps) { sx={{ marginTop: '20px', marginLeft: '10px', - backgroundColor: weeklyData ? '#808080' : '#ffc04d', + backgroundColor: aggregatedData ? '#808080' : '#ffc04d', color: '#000000', '&:hover': { bgcolor: '#f7b34d' } }} > - {/**/} - {/* */} - {/**/} - {dailyData && ( - <> - - - )} - - {dailyData && ( - - - - - )} + + + + + + + + {loading && ( )} - {(weeklyData || monthlyData) && ( + {aggregatedData && ( - {weeklyData && ( - - )} - - {monthlyData && ( - - )} + @@ -806,48 +776,26 @@ function Overview(props: OverviewProps) { /> )} - {weeklyData && ( + {aggregatedData && ( - )} - - {monthlyData && ( - )} - {weeklyData && currentUser.userType == UserType.admin && ( + {aggregatedData && currentUser.userType == UserType.admin && ( )} - {weeklyData && currentUser.userType == UserType.client && ( + {aggregatedData && currentUser.userType == UserType.client && ( - )} - - {monthlyData && currentUser.userType == UserType.admin && ( - - )} - - {monthlyData && currentUser.userType == UserType.client && ( - )} - {weeklyData && ( + {aggregatedData && ( - )} - - {monthlyData && ( - )} - {weeklyData && ( + {aggregatedData && ( - )} - - {monthlyData && ( -