Merge pull request 'multi-emission-feature' (#14) from multi-emission-feature into main

Reviewed-on: BLC/sgeUpdated#14
This commit is contained in:
2025-08-19 09:26:46 +03:00
20 changed files with 881 additions and 421 deletions

View File

@@ -121,7 +121,8 @@ public class SgsApplication implements CommandLineRunner {
@Autowired @Autowired
public SgsApplication(RoleService roleService, PermissionService permissionService, RoleRepo roleRepo, public SgsApplication(RoleService roleService, PermissionService permissionService, RoleRepo roleRepo,
AreaService areaService, NeighborhoodRepo neighborhoodRepo, NeighborhoodService neighborhoodService, CityService cityService, AreaService areaService, NeighborhoodRepo neighborhoodRepo, NeighborhoodService neighborhoodService,
CityService cityService,
CityRepo cityRepo, DistrictRepo districtRepo, DistrictService districtService, CountryRepo countryRepo, CityRepo cityRepo, DistrictRepo districtRepo, DistrictService districtService, CountryRepo countryRepo,
CountryService countryService, OrganizationService organizationService, UserService userService, CountryService countryService, OrganizationService organizationService, UserService userService,
PasswordEncoder passwordEncoder, SectorService sectorService, SubSectorService subSectorService, PasswordEncoder passwordEncoder, SectorService sectorService, SubSectorService subSectorService,
@@ -858,6 +859,7 @@ public class SgsApplication implements CommandLineRunner {
} }
if (cityService.findAll().isEmpty()) { if (cityService.findAll().isEmpty()) {
createCitiesFromJson(); createCitiesFromJson();
createDefaultArea();
} }
if (districtService.findAll().isEmpty()) { if (districtService.findAll().isEmpty()) {
createDistrictFromJson(); createDistrictFromJson();
@@ -865,9 +867,6 @@ public class SgsApplication implements CommandLineRunner {
if (neighborhoodService.findAll().isEmpty()) { if (neighborhoodService.findAll().isEmpty()) {
createNeighborhoodsFromJson(); createNeighborhoodsFromJson();
} }
if (!cityService.findAll().isEmpty()) {
createDefaultArea();
}
} }
void createDefaultArea() { void createDefaultArea() {

View File

@@ -3,6 +3,7 @@ package com.sgs.graphql.dataCenter.domain;
import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference; import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.sgs.graphql.area.domain.Area; import com.sgs.graphql.area.domain.Area;
import com.sgs.graphql.city.domain.City;
import com.sgs.graphql.sector.domain.Sector; import com.sgs.graphql.sector.domain.Sector;
import com.sgs.graphql.subSector.domain.SubSector; import com.sgs.graphql.subSector.domain.SubSector;
import com.sgs.graphql.emissionScope.domain.EmissionScope; import com.sgs.graphql.emissionScope.domain.EmissionScope;
@@ -29,6 +30,7 @@ public class DataCenter extends BaseDomain {
private Integer number; private Integer number;
private Area area; private Area area;
private City city;
private List<PhysicalMachine> physicalMachines = new ArrayList<>(); private List<PhysicalMachine> physicalMachines = new ArrayList<>();
private List<DataCenterEmissionSource> dataCenterEmissionSources = new ArrayList<>(); private List<DataCenterEmissionSource> dataCenterEmissionSources = new ArrayList<>();
@@ -131,6 +133,16 @@ public class DataCenter extends BaseDomain {
this.area = area; this.area = area;
} }
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "city_id")
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "sub_sector_id") @JoinColumn(name = "sub_sector_id")
public SubSector getSubSector() { public SubSector getSubSector() {

View File

@@ -18,7 +18,7 @@ public class DataCenterDto {
private Integer number; private Integer number;
private AreaDto area; private AreaDto area;
@JsonProperty("physical_machine") @JsonProperty("physical_machines")
private Map<String, PhysicalMachineDto> physicalMachine; private Map<String, PhysicalMachineDto> physicalMachine;
// Emission calculation fields // Emission calculation fields

View File

@@ -18,6 +18,8 @@ public class DataCenterCreateInput extends BaseCreateInput {
private UUID areaId; private UUID areaId;
private UUID cityId;
@NotNull(message = "Sektör ID gereklidir") @NotNull(message = "Sektör ID gereklidir")
private UUID sectorId; private UUID sectorId;
@@ -50,6 +52,9 @@ public class DataCenterCreateInput extends BaseCreateInput {
public UUID getAreaId() { return areaId; } public UUID getAreaId() { return areaId; }
public void setAreaId(UUID areaId) { this.areaId = areaId; } public void setAreaId(UUID areaId) { this.areaId = areaId; }
public UUID getCityId() { return cityId; }
public void setCityId(UUID cityId) { this.cityId = cityId; }
public UUID getSectorId() { return sectorId; } public UUID getSectorId() { return sectorId; }
public void setSectorId(UUID sectorId) { this.sectorId = sectorId; } public void setSectorId(UUID sectorId) { this.sectorId = sectorId; }

View File

@@ -13,6 +13,7 @@ public class DataCenterUpdateInput extends BaseUpdateInput {
private Double consuptionAmount; private Double consuptionAmount;
private UUID areaId; private UUID areaId;
private UUID cityId;
@NotNull(message = "Sektör ID gereklidir") @NotNull(message = "Sektör ID gereklidir")
private UUID sectorId; private UUID sectorId;
private UUID subSectorId; private UUID subSectorId;
@@ -44,6 +45,9 @@ public class DataCenterUpdateInput extends BaseUpdateInput {
public UUID getAreaId() { return areaId; } public UUID getAreaId() { return areaId; }
public void setAreaId(UUID areaId) { this.areaId = areaId; } public void setAreaId(UUID areaId) { this.areaId = areaId; }
public UUID getCityId() { return cityId; }
public void setCityId(UUID cityId) { this.cityId = cityId; }
public UUID getSectorId() { return sectorId; } public UUID getSectorId() { return sectorId; }
public void setSectorId(UUID sectorId) { this.sectorId = sectorId; } public void setSectorId(UUID sectorId) { this.sectorId = sectorId; }

View File

@@ -12,6 +12,7 @@ import com.sgs.graphql.emissionSource.service.EmissionSourceService;
import com.sgs.graphql.emissionScope.service.EmissionScopeService; import com.sgs.graphql.emissionScope.service.EmissionScopeService;
import com.sgs.graphql.consuptionUnit.service.ConsuptionUnitService; import com.sgs.graphql.consuptionUnit.service.ConsuptionUnitService;
import com.sgs.graphql.activitySubUnit.service.ActivitySubUnitService; import com.sgs.graphql.activitySubUnit.service.ActivitySubUnitService;
import com.sgs.graphql.city.service.CityService;
import com.sgs.lib.dao.mutation.mapper.BaseCreateUpdateMapper; import com.sgs.lib.dao.mutation.mapper.BaseCreateUpdateMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -29,12 +30,13 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
private final EmissionScopeService emissionScopeService; private final EmissionScopeService emissionScopeService;
private final ConsuptionUnitService consuptionUnitService; private final ConsuptionUnitService consuptionUnitService;
private final ActivitySubUnitService activitySubUnitService; private final ActivitySubUnitService activitySubUnitService;
private final CityService cityService;
@Autowired @Autowired
public DataCenterMapper(AreaService areaService, SectorService sectorService, public DataCenterMapper(AreaService areaService, SectorService sectorService,
SubSectorService subSectorService, EmissionSourceService emissionSourceService, SubSectorService subSectorService, EmissionSourceService emissionSourceService,
EmissionScopeService emissionScopeService, ConsuptionUnitService consuptionUnitService, EmissionScopeService emissionScopeService, ConsuptionUnitService consuptionUnitService,
ActivitySubUnitService activitySubUnitService) { ActivitySubUnitService activitySubUnitService, CityService cityService) {
this.areaService = areaService; this.areaService = areaService;
this.sectorService = sectorService; this.sectorService = sectorService;
this.subSectorService = subSectorService; this.subSectorService = subSectorService;
@@ -42,6 +44,7 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
this.emissionScopeService = emissionScopeService; this.emissionScopeService = emissionScopeService;
this.consuptionUnitService = consuptionUnitService; this.consuptionUnitService = consuptionUnitService;
this.activitySubUnitService = activitySubUnitService; this.activitySubUnitService = activitySubUnitService;
this.cityService = cityService;
} }
@Override @Override
@@ -58,6 +61,9 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
if (input.getAreaId() != null) { if (input.getAreaId() != null) {
entity.setArea(areaService.findById(input.getAreaId()).orElse(null)); entity.setArea(areaService.findById(input.getAreaId()).orElse(null));
} }
if (input.getCityId() != null) {
entity.setCity(cityService.findById(input.getCityId()).orElse(null));
}
if (input.getSectorId() != null) { if (input.getSectorId() != null) {
entity.setSector(sectorService.findById(input.getSectorId()).orElse(null)); entity.setSector(sectorService.findById(input.getSectorId()).orElse(null));
@@ -91,16 +97,19 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
// Set EmissionSource // Set EmissionSource
if (emissionSourceInput.getEmissionSourceId() != null) { if (emissionSourceInput.getEmissionSourceId() != null) {
dcEmissionSource.setEmissionSource(emissionSourceService.findById(UUID.fromString(emissionSourceInput.getEmissionSourceId())).orElse(null)); dcEmissionSource.setEmissionSource(emissionSourceService
.findById(UUID.fromString(emissionSourceInput.getEmissionSourceId())).orElse(null));
} }
// Set ConsuptionUnit // Set ConsuptionUnit
if (emissionSourceInput.getConsuptionUnitId() != null) { if (emissionSourceInput.getConsuptionUnitId() != null) {
dcEmissionSource.setConsuptionUnit(consuptionUnitService.findById(UUID.fromString(emissionSourceInput.getConsuptionUnitId())).orElse(null)); dcEmissionSource.setConsuptionUnit(consuptionUnitService
.findById(UUID.fromString(emissionSourceInput.getConsuptionUnitId())).orElse(null));
} }
// Set optional fields // Set optional fields
dcEmissionSource.setIsDefault(emissionSourceInput.getIsDefault() != null ? emissionSourceInput.getIsDefault() : false); dcEmissionSource.setIsDefault(
emissionSourceInput.getIsDefault() != null ? emissionSourceInput.getIsDefault() : false);
dcEmissionSource.setPercentage(emissionSourceInput.getPercentage()); dcEmissionSource.setPercentage(emissionSourceInput.getPercentage());
dataCenterEmissionSources.add(dcEmissionSource); dataCenterEmissionSources.add(dcEmissionSource);
@@ -135,6 +144,9 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
if (input.getAreaId() != null) { if (input.getAreaId() != null) {
entity.setArea(areaService.findById(input.getAreaId()).orElse(null)); entity.setArea(areaService.findById(input.getAreaId()).orElse(null));
} }
if (input.getCityId() != null) {
entity.setCity(cityService.findById(input.getCityId()).orElse(null));
}
if (input.getSectorId() != null) { if (input.getSectorId() != null) {
entity.setSector(sectorService.findById(input.getSectorId()).orElse(null)); entity.setSector(sectorService.findById(input.getSectorId()).orElse(null));
@@ -154,28 +166,31 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
// Handle multiple emission sources update if provided // Handle multiple emission sources update if provided
if (input.getDataCenterEmissionSources() != null) { if (input.getDataCenterEmissionSources() != null) {
// Clear existing emission sources and add new ones // Clear existing emission sources from the managed collection
entity.getDataCenterEmissionSources().clear(); entity.getDataCenterEmissionSources().clear();
List<DataCenterEmissionSource> dataCenterEmissionSources = new ArrayList<>(); // Add new emission sources to the same managed collection
for (DataCenterEmissionSourceInput emissionSourceInput : input.getDataCenterEmissionSources()) { for (DataCenterEmissionSourceInput emissionSourceInput : input.getDataCenterEmissionSources()) {
DataCenterEmissionSource dcEmissionSource = new DataCenterEmissionSource(); DataCenterEmissionSource dcEmissionSource = new DataCenterEmissionSource();
dcEmissionSource.setDataCenter(entity); dcEmissionSource.setDataCenter(entity);
if (emissionSourceInput.getEmissionSourceId() != null) { if (emissionSourceInput.getEmissionSourceId() != null) {
dcEmissionSource.setEmissionSource(emissionSourceService.findById(UUID.fromString(emissionSourceInput.getEmissionSourceId())).orElse(null)); dcEmissionSource.setEmissionSource(emissionSourceService
.findById(UUID.fromString(emissionSourceInput.getEmissionSourceId())).orElse(null));
} }
if (emissionSourceInput.getConsuptionUnitId() != null) { if (emissionSourceInput.getConsuptionUnitId() != null) {
dcEmissionSource.setConsuptionUnit(consuptionUnitService.findById(UUID.fromString(emissionSourceInput.getConsuptionUnitId())).orElse(null)); dcEmissionSource.setConsuptionUnit(consuptionUnitService
.findById(UUID.fromString(emissionSourceInput.getConsuptionUnitId())).orElse(null));
} }
dcEmissionSource.setIsDefault(emissionSourceInput.getIsDefault() != null ? emissionSourceInput.getIsDefault() : false); dcEmissionSource.setIsDefault(
emissionSourceInput.getIsDefault() != null ? emissionSourceInput.getIsDefault() : false);
dcEmissionSource.setPercentage(emissionSourceInput.getPercentage()); dcEmissionSource.setPercentage(emissionSourceInput.getPercentage());
dataCenterEmissionSources.add(dcEmissionSource); // Add to the existing managed collection instead of creating a new one
entity.getDataCenterEmissionSources().add(dcEmissionSource);
} }
entity.setDataCenterEmissionSources(dataCenterEmissionSources);
} }
// New attributes (partial update - only if provided) // New attributes (partial update - only if provided)

View File

@@ -15,7 +15,7 @@ public class VMEmissionSummary {
private Double totalEmission; // Individual record's total emission private Double totalEmission; // Individual record's total emission
private LocalDateTime createdDate; // When this specific record was created private LocalDateTime createdDate; // When this specific record was created
private String physicalMachine; private String physicalMachine;
private String project; private String cloudSystem; // From physical machine
private String dataCenter; private String dataCenter;
// Individual emission values for this specific record // Individual emission values for this specific record
@@ -31,7 +31,7 @@ public class VMEmissionSummary {
public VMEmissionSummary(UUID vmId, String vmName, Double vmPower, String vmStatus, public VMEmissionSummary(UUID vmId, String vmName, Double vmPower, String vmStatus,
Double totalEmission, LocalDateTime createdDate, Double totalEmission, LocalDateTime createdDate,
String physicalMachine, String project, String dataCenter, String physicalMachine, String cloudSystem, String dataCenter,
Double co2, Double ch4, Double n2o) { Double co2, Double ch4, Double n2o) {
this.vmId = vmId; this.vmId = vmId;
this.vmName = vmName; this.vmName = vmName;
@@ -40,7 +40,7 @@ public class VMEmissionSummary {
this.totalEmission = totalEmission; this.totalEmission = totalEmission;
this.createdDate = createdDate; this.createdDate = createdDate;
this.physicalMachine = physicalMachine; this.physicalMachine = physicalMachine;
this.project = project; this.cloudSystem = cloudSystem;
this.dataCenter = dataCenter; this.dataCenter = dataCenter;
this.co2 = co2; this.co2 = co2;
this.ch4 = ch4; this.ch4 = ch4;
@@ -70,8 +70,8 @@ public class VMEmissionSummary {
public String getPhysicalMachine() { return physicalMachine; } public String getPhysicalMachine() { return physicalMachine; }
public void setPhysicalMachine(String physicalMachine) { this.physicalMachine = physicalMachine; } public void setPhysicalMachine(String physicalMachine) { this.physicalMachine = physicalMachine; }
public String getProject() { return project; } public String getCloudSystem() { return cloudSystem; }
public void setProject(String project) { this.project = project; } public void setCloudSystem(String cloudSystem) { this.cloudSystem = cloudSystem; }
public String getDataCenter() { return dataCenter; } public String getDataCenter() { return dataCenter; }
public void setDataCenter(String dataCenter) { this.dataCenter = dataCenter; } public void setDataCenter(String dataCenter) { this.dataCenter = dataCenter; }

View File

@@ -55,7 +55,7 @@ public class MainDataTableQueryResolver implements GraphQLQueryResolver {
* @param projectId Optional project ID to filter VMs by project * @param projectId Optional project ID to filter VMs by project
* @return List of VM emission summaries including datacenter, project, aggregate, and physical machine info * @return List of VM emission summaries including datacenter, project, aggregate, and physical machine info
*/ */
public List<VMEmissionSummary> vmEmissionSummary(UUID datacenterId, UUID projectId) { public List<VMEmissionSummary> vmEmissionSummary(UUID datacenterId) {
return mainDataTableService.getVMEmissionSummaries(datacenterId, projectId); return mainDataTableService.getVMEmissionSummaries(datacenterId);
} }
} }

View File

@@ -34,25 +34,18 @@ public class MainDataTableService
} }
public List<VMEmissionSummary> getVMEmissionSummaries() { public List<VMEmissionSummary> getVMEmissionSummaries() {
return getVMEmissionSummaries(null, null); return getVMEmissionSummaries(null);
} }
public List<VMEmissionSummary> getVMEmissionSummaries(UUID datacenterId) { public List<VMEmissionSummary> getVMEmissionSummaries(UUID datacenterId) {
return getVMEmissionSummaries(datacenterId, null);
}
public List<VMEmissionSummary> getVMEmissionSummaries(UUID datacenterId, UUID projectId) {
List<String> whereConditions = new ArrayList<>(); List<String> whereConditions = new ArrayList<>();
if (datacenterId != null) { if (datacenterId != null) {
whereConditions.add("dc.id = decode(replace(:datacenterId, '-', ''), 'hex')"); whereConditions.add("dc.id = decode(replace(:datacenterId, '-', ''), 'hex')");
} }
if (projectId != null) { String whereClause = whereConditions.isEmpty() ? "" :
whereConditions.add("v.project = :projectId"); "WHERE " + String.join(" AND ", whereConditions) + " ";
}
String whereClause = whereConditions.isEmpty() ? "" : "WHERE " + String.join(" AND ", whereConditions) + " ";
String sql = """ String sql = """
SELECT SELECT
@@ -63,7 +56,7 @@ public class MainDataTableService
mdt.total_emission, mdt.total_emission,
mdt.created_date, mdt.created_date,
pm.name as physical_machine_name, pm.name as physical_machine_name,
v.project as project_name, pm.cloud_system as cloud_system,
dc.data_center_name as datacenter_name, dc.data_center_name as datacenter_name,
mdt.co2, mdt.co2,
mdt.ch4, mdt.ch4,
@@ -83,10 +76,6 @@ public class MainDataTableService
query.setParameter("datacenterId", datacenterId.toString()); query.setParameter("datacenterId", datacenterId.toString());
} }
if (projectId != null) {
query.setParameter("projectId", projectId.toString());
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList(); List<Object[]> results = query.getResultList();
@@ -110,7 +99,7 @@ public class MainDataTableService
} }
summary.setPhysicalMachine((String) row[6]); summary.setPhysicalMachine((String) row[6]);
summary.setProject((String) row[7]); summary.setCloudSystem((String) row[7]);
summary.setDataCenter((String) row[8]); summary.setDataCenter((String) row[8]);
// Individual emission values // Individual emission values

View File

@@ -217,6 +217,7 @@ public class MessageListener {
pm.setName(pmDto.getName()); pm.setName(pmDto.getName());
pm.setIp(pmIp); // Use the IP from the map key pm.setIp(pmIp); // Use the IP from the map key
pm.setTag(pmDto.getTag()); pm.setTag(pmDto.getTag());
pm.setCloudSystem(pmDto.getCloudSystem());
pm.setPower(pmDto.getPower()); pm.setPower(pmDto.getPower());
pm.setDataCenter(entity); pm.setDataCenter(entity);
@@ -282,16 +283,17 @@ public class MessageListener {
pm.setName(newPm.getName()); pm.setName(newPm.getName());
pm.setIp(newPm.getIp()); pm.setIp(newPm.getIp());
pm.setTag(newPm.getTag()); pm.setTag(newPm.getTag());
pm.setCloudSystem(newPm.getCloudSystem());
pm.setPower(newPm.getPower()); pm.setPower(newPm.getPower());
System.out.println("✅ Updated existing PM: " + pm.getName() + " (IP: " + pm.getIp() + ")"); System.out.println("✅ Updated existing PM: " + pm.getName() + " (IP: " + pm.getIp() + ") - CloudSystem: " + pm.getCloudSystem());
} else { } else {
// Create new PM // Create new PM
pm = newPm; pm = newPm;
pm.setDataCenter(dc); pm.setDataCenter(dc);
dc.getPhysicalMachines().add(pm); dc.getPhysicalMachines().add(pm);
System.out.println("✅ Created new PM: " + pm.getName() + " (IP: " + pm.getIp() + ")"); System.out.println("✅ Created new PM: " + pm.getName() + " (IP: " + pm.getIp() + ") - CloudSystem: " + pm.getCloudSystem());
} }
// Process VMs that are already assigned to this PM // Process VMs that are already assigned to this PM
@@ -547,15 +549,46 @@ public class MessageListener {
return false; return false;
} }
// Find the emission source by name/tag // Find the emission source by name/tag from datacenter's configured emission sources
EmissionSource emissionSource = null;
// First, try to find the emission source from datacenter's configured sources
if (dataCenter.getDataCenterEmissionSources() != null && !dataCenter.getDataCenterEmissionSources().isEmpty()) {
for (DataCenterEmissionSource dces : dataCenter.getDataCenterEmissionSources()) {
if (dces.getEmissionSource() != null &&
emissionSourceName.equalsIgnoreCase(dces.getEmissionSource().getTag())) {
emissionSource = dces.getEmissionSource();
System.out.println("✅ Found emission source '" + emissionSourceName +
"' in datacenter's configured sources (ID: " + emissionSource.getId() + ")");
break;
}
}
}
// If not found in datacenter's sources, fall back to subsector-specific search
if (emissionSource == null && dataCenter.getSubSector() != null) {
emissionSource = emissionSourceRepo.findByTagAndSubSectorIgnoreCase(emissionSourceName, dataCenter.getSubSector());
if (emissionSource != null) {
System.out.println("⚠️ Using subsector fallback for emission source '" + emissionSourceName +
"' (ID: " + emissionSource.getId() + ") - Consider configuring it for datacenter");
}
}
// Last resort: global search
if (emissionSource == null) {
List<EmissionSource> emissionSources = emissionSourceRepo.findByTag(emissionSourceName); List<EmissionSource> emissionSources = emissionSourceRepo.findByTag(emissionSourceName);
if (emissionSources.isEmpty()) { if (!emissionSources.isEmpty()) {
emissionSource = emissionSources.get(0);
System.out.println("⚠️ Using global fallback for emission source '" + emissionSourceName +
"' (ID: " + emissionSource.getId() + ") - This may cause incorrect calculations!");
}
}
if (emissionSource == null) {
System.err.println("❌ Could not find emission source: " + emissionSourceName); System.err.println("❌ Could not find emission source: " + emissionSourceName);
return false; return false;
} }
EmissionSource emissionSource = emissionSources.get(0);
// Calculate power consumption for this emission source (percentage of total VM power) // Calculate power consumption for this emission source (percentage of total VM power)
double sourceSpecificPower = vm.getPower() * (percentage / 100.0); double sourceSpecificPower = vm.getPower() * (percentage / 100.0);
@@ -806,8 +839,17 @@ public class MessageListener {
System.out.println("🔍 Setting VM ID: " + vm.getId()); System.out.println("🔍 Setting VM ID: " + vm.getId());
// Use the source-specific power consumption (percentage of total VM power) // Use the source-specific power consumption (percentage of total VM power)
input.setConsuptionAmount(String.valueOf(sourceSpecificPower)); // Format to 6 decimal places to avoid very long strings
System.out.println("🔍 Setting Consumption Amount: " + sourceSpecificPower + "W"); String formattedPower = String.format("%.6f", sourceSpecificPower);
input.setConsuptionAmount(formattedPower);
System.out.println("🔍 Setting Consumption Amount: " + formattedPower + "W");
// Validate field lengths to prevent database errors
System.out.println("🔍 Field length validation:");
System.out.println(" Year: " + (input.getYear() != null ? input.getYear().length() : "null"));
System.out.println(" Month: " + (input.getMonth() != null ? input.getMonth().length() : "null"));
System.out.println(" ConsuptionAmount: " + (input.getConsuptionAmount() != null ? input.getConsuptionAmount().length() : "null"));
System.out.println("🔍 VM Emission Input for Source:"); System.out.println("🔍 VM Emission Input for Source:");
System.out.println(" VM ID: " + vm.getId()); System.out.println(" VM ID: " + vm.getId());

View File

@@ -11,6 +11,7 @@ input DataCenterCreateInput {
consuptionAmount: Float consuptionAmount: Float
areaId: ID areaId: ID
cityId: ID
number: Int number: Int
ayposURL: String ayposURL: String
address: String address: String
@@ -31,6 +32,7 @@ input DataCenterUpdateInput {
consuptionAmount: Float consuptionAmount: Float
areaId: ID areaId: ID
cityId: ID
number: Int number: Int
ayposURL: String ayposURL: String
address: String address: String

View File

@@ -15,6 +15,7 @@ type DataCenter {
physicalMachines: [PhysicalMachine] physicalMachines: [PhysicalMachine]
area: Area area: Area
city: City
number: Int number: Int
ayposURL: String ayposURL: String

View File

@@ -2,5 +2,5 @@ extend type Query{
mainDataTable(id: ID!): MainDataTable! mainDataTable(id: ID!): MainDataTable!
mainDataTables(criteria: MainDataTableCriteria, sortBy: [SortBy!]): [MainDataTable!] mainDataTables(criteria: MainDataTableCriteria, sortBy: [SortBy!]): [MainDataTable!]
paginateMainDataTables(pagination : Pagination!, criteria: MainDataTableCriteria, sortBy:[SortBy!] ) : MainDataTablePageable! paginateMainDataTables(pagination : Pagination!, criteria: MainDataTableCriteria, sortBy:[SortBy!] ) : MainDataTablePageable!
vmEmissionSummary(datacenterId: ID, projectId: ID): [VMEmissionSummary!]! vmEmissionSummary(datacenterId: ID): [VMEmissionSummary!]!
} }

View File

@@ -60,7 +60,7 @@ type Config {
totalEmission: Float! totalEmission: Float!
createdDate: LocalDateTime! createdDate: LocalDateTime!
physicalMachine: String physicalMachine: String
project: String cloudSystem: String
dataCenter: String dataCenter: String
# Individual emission values per record # Individual emission values per record
co2: Float! co2: Float!

View File

@@ -45,6 +45,10 @@ export const getDataCenters = () => {
name name
} }
} }
city {
id
name
}
emissionScope { emissionScope {
id id
tag tag
@@ -201,6 +205,10 @@ export const createDataCenter = (dataCenterData) => {
name name
} }
} }
city {
id
name
}
emissionScope { emissionScope {
id id
tag tag
@@ -241,6 +249,7 @@ export const createDataCenter = (dataCenterData) => {
ayposURL: dataCenterData.ayposURL || "", ayposURL: dataCenterData.ayposURL || "",
number: parseInt(dataCenterData.number) || 1, number: parseInt(dataCenterData.number) || 1,
areaId: dataCenterData.areaId || null, areaId: dataCenterData.areaId || null,
cityId: dataCenterData.cityId || null,
address: dataCenterData.address || "", address: dataCenterData.address || "",
latitude: dataCenterData.latitude latitude: dataCenterData.latitude
? parseFloat(dataCenterData.latitude) ? parseFloat(dataCenterData.latitude)
@@ -331,6 +340,10 @@ export const updateDataCenter = (id, dataCenterData) => {
name name
} }
} }
city {
id
name
}
emissionScope { emissionScope {
id id
tag tag
@@ -372,6 +385,7 @@ export const updateDataCenter = (id, dataCenterData) => {
ayposURL: dataCenterData.ayposURL || "", ayposURL: dataCenterData.ayposURL || "",
number: parseInt(dataCenterData.number) || 1, number: parseInt(dataCenterData.number) || 1,
areaId: dataCenterData.areaId || null, areaId: dataCenterData.areaId || null,
cityId: dataCenterData.cityId || null,
address: dataCenterData.address || "", address: dataCenterData.address || "",
latitude: dataCenterData.latitude latitude: dataCenterData.latitude
? parseFloat(dataCenterData.latitude) ? parseFloat(dataCenterData.latitude)

View File

@@ -1,6 +1,6 @@
import ApplicationService from "../../../services/ApplicationService"; import ApplicationService from "../../../services/ApplicationService";
export const getVMEmissionSummary = () => { export const getVMEmissionSummary = (datacenterId) => {
return async (dispatch) => { return async (dispatch) => {
try { try {
const response = await ApplicationService.http() const response = await ApplicationService.http()
@@ -8,8 +8,8 @@ export const getVMEmissionSummary = () => {
"/graphql", "/graphql",
{ {
query: ` query: `
{ query GetVMEmissions($datacenterId: ID) {
vmEmissionSummary { vmEmissionSummary(datacenterId: $datacenterId) {
vmId vmId
vmName vmName
vmPower vmPower
@@ -17,7 +17,7 @@ export const getVMEmissionSummary = () => {
totalEmission totalEmission
createdDate createdDate
physicalMachine physicalMachine
project cloudSystem
dataCenter dataCenter
co2 co2
ch4 ch4
@@ -25,7 +25,10 @@ export const getVMEmissionSummary = () => {
reportGeneratedTime reportGeneratedTime
} }
} }
` `,
variables: {
datacenterId: datacenterId
}
}, },
{ {
headers: { headers: {

View File

@@ -18,17 +18,16 @@ const DataCenter = () => {
const [refreshInterval, setRefreshInterval] = useState(null); const [refreshInterval, setRefreshInterval] = useState(null);
const getAllPhysicalMachines = (dataCenter) => { const getAllPhysicalMachines = (dataCenter) => {
if (!dataCenter.projects) return []; // Physical machines are directly in the dataCenter object, not in projects
return dataCenter.projects.flatMap(project => const pms = dataCenter.physicalMachines || [];
project.physicalMachines || [] return pms;
);
}; };
// Table columns following your pattern // Table columns following your pattern
const initialColumns = [ const initialColumns = [
{ {
name: "Number", name: "External ID",
selector: (row) => row.number, selector: (row) => row.externalId,
sortable: true, sortable: true,
minWidth: "100px", minWidth: "100px",
}, },
@@ -38,28 +37,33 @@ const DataCenter = () => {
sortable: true, sortable: true,
minWidth: "200px", minWidth: "200px",
}, },
// Projects // Projects - Based on API response, this field might not exist or be structured differently
{ // {
name: "Projects", // name: "Projects",
selector: (row) => (row.projects || []).length, // selector: (row) => row.projects?.length || 0,
sortable: true, // sortable: true,
minWidth: "200px", // minWidth: "200px",
cell: (row) => ( // cell: (row) => (
<div> // <div>
{(row.projects || []).length > 0 ? ( // {row.projects && row.projects.length > 0 ? (
<div className="d-flex flex-column"> // <div className="d-flex flex-column">
{row.projects.map((project, index) => ( // {row.projects.map((project, index) => (
<div key={project.id} className={`badge badge-light-primary ${index > 0 ? 'mt-1' : ''}`}> // <div
{project.name} // key={project.id}
</div> // className={`badge badge-light-primary ${
))} // index > 0 ? "mt-1" : ""
</div> // }`}
) : ( // >
<span className="text-muted">-</span> // {project.name}
)} // </div>
</div> // ))}
), // </div>
}, // ) : (
// <span className="text-muted">-</span>
// )}
// </div>
// ),
// },
// Physical Machines // Physical Machines
{ {
name: "Physical Machines", name: "Physical Machines",
@@ -80,26 +84,38 @@ const DataCenter = () => {
name: "Virtual Machines", name: "Virtual Machines",
selector: (row) => { selector: (row) => {
const pms = getAllPhysicalMachines(row); const pms = getAllPhysicalMachines(row);
const vms = pms.reduce((acc, pm) => { const vms = pms.reduce(
(acc, pm) => {
if (!pm.vms) return acc; if (!pm.vms) return acc;
return { return {
active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, active:
total: acc.total + pm.vms.length acc.active +
pm.vms.filter((vm) => vm.state?.toLowerCase() === "active")
.length,
total: acc.total + pm.vms.length,
}; };
}, { active: 0, total: 0 }); },
{ active: 0, total: 0 }
);
return vms.total; return vms.total;
}, },
sortable: true, sortable: true,
minWidth: "200px", minWidth: "200px",
cell: (row) => { cell: (row) => {
const pms = getAllPhysicalMachines(row); const pms = getAllPhysicalMachines(row);
const vms = pms.reduce((acc, pm) => { const vms = pms.reduce(
(acc, pm) => {
if (!pm.vms) return acc; if (!pm.vms) return acc;
return { return {
active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, active:
total: acc.total + pm.vms.length acc.active +
pm.vms.filter((vm) => vm.state?.toLowerCase() === "active")
.length,
total: acc.total + pm.vms.length,
}; };
}, { active: 0, total: 0 }); },
{ active: 0, total: 0 }
);
return ( return (
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
@@ -109,7 +125,9 @@ const DataCenter = () => {
<div className="small"> <div className="small">
<span className="text-success">{vms.active} Active</span> <span className="text-success">{vms.active} Active</span>
<span className="text-muted mx-1"></span> <span className="text-muted mx-1"></span>
<span className="text-warning">{vms.total - vms.active} Inactive</span> <span className="text-warning">
{vms.total - vms.active} Inactive
</span>
</div> </div>
</div> </div>
</div> </div>
@@ -183,14 +201,23 @@ const DataCenter = () => {
</thead> </thead>
<tbody> <tbody>
{pm.vms.map((vm) => { {pm.vms.map((vm) => {
const isActive = vm.state && ["ACTIVE", "active"].includes(vm.state); const isActive =
vm.state && ["ACTIVE", "active"].includes(vm.state);
return ( return (
<tr key={vm.id}> <tr key={vm.id}>
<td> <td>
<span className="font-weight-bold">{vm.vmName || vm.vm_name}</span> <span className="font-weight-bold">
{vm.vmName || vm.vm_name}
</span>
</td> </td>
<td> <td>
<div className={`d-inline-block px-2 py-1 rounded-pill ${isActive ? 'bg-light-success text-success' : 'bg-light-warning text-warning'}`}> <div
className={`d-inline-block px-2 py-1 rounded-pill ${
isActive
? "bg-light-success text-success"
: "bg-light-warning text-warning"
}`}
>
{vm.state} {vm.state}
</div> </div>
</td> </td>
@@ -204,23 +231,48 @@ const DataCenter = () => {
<td> <td>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<div className="mr-3"> <div className="mr-3">
<small className="text-muted d-block">CPU</small> <small className="text-muted d-block">
<span>{(vm.config?.cpu || (vm.confg && vm.confg[1])) || '-'}</span> CPU
</small>
<span>
{vm.config?.cpu ||
(vm.confg && vm.confg[1]) ||
"-"}
</span>
</div> </div>
<div className="mr-3"> <div className="mr-3">
<small className="text-muted d-block">RAM</small> <small className="text-muted d-block">
<span>{(vm.config?.ram || (vm.confg && vm.confg[2])) || '-'} GB</span> RAM
</small>
<span>
{vm.config?.ram ||
(vm.confg && vm.confg[2]) ||
"-"}{" "}
GB
</span>
</div> </div>
<div> <div>
<small className="text-muted d-block">Disk</small> <small className="text-muted d-block">
<span>{(vm.config?.disk || (vm.confg && vm.confg[3])) || '-'} GB</span> Disk
</small>
<span>
{vm.config?.disk ||
(vm.confg && vm.confg[3]) ||
"-"}{" "}
GB
</span>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Server size={14} className="mr-1" /> <Server size={14} className="mr-1" />
<span>{vm.host || vm.hostingPm || (vm.confg && vm.confg[4]) || '-'}</span> <span>
{vm.host ||
vm.hostingPm ||
(vm.confg && vm.confg[4]) ||
"-"}
</span>
</div> </div>
</td> </td>
</tr> </tr>
@@ -241,8 +293,6 @@ const DataCenter = () => {
); );
}; };
return ( return (
<div style={{ marginTop: "2%" }}> <div style={{ marginTop: "2%" }}>
<Card> <Card>

View File

@@ -412,11 +412,11 @@ const DataCenterManagement = () => {
number: row.number?.toString(), number: row.number?.toString(),
address: row.address || "", address: row.address || "",
areaId: row.area?.id || null, areaId: row.area?.id || null,
cityId: null, cityId: row.city?.id || null,
latitude: row.latitude, latitude: row.latitude,
longitude: row.longitude, longitude: row.longitude,
ayposURL: row.ayposURL || "", ayposURL: row.ayposURL || "",
city: row.city || "", city: row.city?.name || "",
emissionScopeId: row.emissionScope?.id || null, emissionScopeId: row.emissionScope?.id || null,
sectorId: row.sector?.id || null, sectorId: row.sector?.id || null,
subSectorId: row.subSector?.id || null, subSectorId: row.subSector?.id || null,
@@ -428,6 +428,23 @@ const DataCenterManagement = () => {
setSelectedSector(row.sector?.id); setSelectedSector(row.sector?.id);
setSelectedSubSector(row.subSector?.id); setSelectedSubSector(row.subSector?.id);
// If there are existing emission sources, fetch consumption units for each
if (
row.dataCenterEmissionSources &&
row.dataCenterEmissionSources.length > 0
) {
row.dataCenterEmissionSources.forEach((dces) => {
if (dces.emissionSource && dces.emissionSource.id) {
dispatch(
getConsuptionUnits({
id: dces.emissionSource.id,
sector: row.sector?.id,
})
);
}
});
}
// Only set map position if we have both address and valid coordinates // Only set map position if we have both address and valid coordinates
setMapPosition( setMapPosition(
row.address && row.latitude && row.longitude row.address && row.latitude && row.longitude
@@ -594,6 +611,7 @@ const DataCenterManagement = () => {
number: parseInt(selectedDataCenter.number || "1"), number: parseInt(selectedDataCenter.number || "1"),
address: selectedDataCenter.address, address: selectedDataCenter.address,
areaId: selectedDataCenter.areaId, areaId: selectedDataCenter.areaId,
cityId: selectedDataCenter.cityId,
latitude: selectedDataCenter.latitude latitude: selectedDataCenter.latitude
? parseFloat(selectedDataCenter.latitude) ? parseFloat(selectedDataCenter.latitude)
: null, : null,
@@ -1029,18 +1047,47 @@ const DataCenterManagement = () => {
<Col sm="12"> <Col sm="12">
<FormGroup> <FormGroup>
<Label>Emission Sources & Consumption Units</Label> <Label>Emission Sources & Consumption Units</Label>
<div className="border p-3 rounded bg-light"> <div className="border rounded p-3 bg-light">
<small className="text-muted mb-2 d-block"> <div className="d-flex justify-content-between align-items-center mb-3">
Configure emission sources for this data center. At least <small className="text-muted">
one emission source with consumption unit is required. Configure emission sources for this data center. At
least one emission source with consumption unit is
required.
</small> </small>
<Button
color="primary"
size="sm"
onClick={() => {
setSelectedDataCenter({
...selectedDataCenter,
dataCenterEmissionSources: [
...selectedDataCenter.dataCenterEmissionSources,
{
emissionSourceId: null,
consuptionUnitId: null,
isDefault: false,
},
],
});
}}
disabled={!selectedDataCenter.subSectorId}
className="d-none d-sm-flex"
>
<Plus size={14} className="me-1" />
Add Source
</Button>
</div>
{selectedDataCenter.dataCenterEmissionSources.map( {selectedDataCenter.dataCenterEmissionSources.map(
(source, index) => ( (source, index) => (
<Row key={index} className="mb-2 align-items-end"> <div
<Col sm="5"> key={index}
className="border rounded p-3 mb-3 bg-white"
>
<Row className="g-3">
<Col xs="12" md="6">
<Label <Label
for={`emissionSource-${index}`} for={`emissionSource-${index}`}
className="form-label" className="form-label fw-bold"
> >
Emission Source * Emission Source *
</Label> </Label>
@@ -1084,13 +1131,13 @@ const DataCenterManagement = () => {
color: "#6e6b7b", color: "#6e6b7b",
}), }),
}} }}
menuPlacement="top" menuPlacement="auto"
/> />
</Col> </Col>
<Col sm="4"> <Col xs="12" md="6">
<Label <Label
for={`consuptionUnit-${index}`} for={`consuptionUnit-${index}`}
className="form-label" className="form-label fw-bold"
> >
Consumption Unit * Consumption Unit *
</Label> </Label>
@@ -1124,14 +1171,14 @@ const DataCenterManagement = () => {
color: "#6e6b7b", color: "#6e6b7b",
}), }),
}} }}
menuPlacement="top" menuPlacement="auto"
/> />
</Col> </Col>
<Col sm="3"> </Row>
<Label className="form-label d-block"> <Row className="mt-3">
Actions <Col xs="12">
</Label> <div className="d-flex flex-column flex-sm-row gap-2 justify-content-between align-items-start align-items-sm-center">
<div className="d-flex gap-2"> <div className="d-flex gap-2 flex-wrap">
<Button <Button
color={ color={
source.isDefault source.isDefault
@@ -1151,13 +1198,22 @@ const DataCenterManagement = () => {
updatedSources[index].isDefault = true; updatedSources[index].isDefault = true;
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
dataCenterEmissionSources: updatedSources, dataCenterEmissionSources:
updatedSources,
}); });
}} }}
title="Set as default emission source" title="Set as default emission source"
> >
{source.isDefault ? "★ Default" : "☆ Default"} {source.isDefault
? "★ Default"
: "☆ Set Default"}
</Button> </Button>
{source.isDefault && (
<span className="badge bg-success align-self-center">
Default Source
</span>
)}
</div>
<Button <Button
color="outline-danger" color="outline-danger"
size="sm" size="sm"
@@ -1184,13 +1240,16 @@ const DataCenterManagement = () => {
} }
title="Remove emission source" title="Remove emission source"
> >
<Trash size={14} /> <Trash size={14} className="me-1" />
Remove
</Button> </Button>
</div> </div>
</Col> </Col>
</Row> </Row>
</div>
) )
)} )}
<div className="text-center mt-3">
<Button <Button
color="primary" color="primary"
size="sm" size="sm"
@@ -1208,11 +1267,13 @@ const DataCenterManagement = () => {
}); });
}} }}
disabled={!selectedDataCenter.subSectorId} disabled={!selectedDataCenter.subSectorId}
className="w-100 d-sm-none"
> >
<Plus size={14} className="me-1" /> <Plus size={14} className="me-1" />
Add Emission Source Add Another Emission Source
</Button> </Button>
</div> </div>
</div>
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm="12"> <Col sm="12">

View File

@@ -1,56 +1,157 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { MaterialReactTable } from "material-react-table"; import { MaterialReactTable } from "material-react-table";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardHeader, CardTitle, Alert } from "reactstrap"; import {
Card,
CardHeader,
CardTitle,
Alert,
Row,
Col,
Label,
} from "reactstrap";
import { getVMEmissionSummary } from "../../redux/actions/mainDataTables/index"; import { getVMEmissionSummary } from "../../redux/actions/mainDataTables/index";
import { getDataCenters } from "../../redux/actions/dataCenter";
import { editNumbers } from "../../components/edit-numbers"; import { editNumbers } from "../../components/edit-numbers";
import Select from "react-select";
function MainDataTables() { function MainDataTables() {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const mainDataTablesStore = useSelector((state) => state.mainDataTables); const mainDataTablesStore = useSelector((state) => state.mainDataTables);
const dataCenterStore = useSelector((state) => state.dataCenter);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [selectedDataCenter, setSelectedDataCenter] = useState(null);
const [dataCenterOptions, setDataCenterOptions] = useState([]);
const [loading, setLoading] = useState(false);
// Fetch datacenters on component mount
useEffect(() => { useEffect(() => {
dispatch(getDataCenters());
}, [dispatch]);
// Update datacenter options when datacenters are loaded
useEffect(() => {
if (dataCenterStore?.dataCenters?.length > 0) {
const options = dataCenterStore.dataCenters.map((dataCenter) => ({
value: dataCenter.id,
label: dataCenter.dataCenter,
externalId: dataCenter.externalId,
}));
setDataCenterOptions(options);
}
}, [dataCenterStore?.dataCenters]);
// Fetch VM emission data when datacenter is selected
useEffect(() => {
if (selectedDataCenter?.value) {
const fetchData = async () => { const fetchData = async () => {
try { try {
setLoading(true); setLoading(true);
await dispatch(getVMEmissionSummary()); setError(null);
await dispatch(getVMEmissionSummary(selectedDataCenter.value));
} catch (err) { } catch (err) {
console.error('Error in MainDataTables:', err); console.error("Error in MainDataTables:", err);
setError(err.message); setError(err.message);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
fetchData(); fetchData();
}, [dispatch]); }
}, [selectedDataCenter, dispatch]);
// Debug log for store data // Memoize columns to prevent re-renders
useEffect(() => { const columns = useMemo(
console.log('Current store data:', mainDataTablesStore); () => [
}, [mainDataTablesStore]); {
header: t("VM ID"),
accessorKey: "vmId",
size: 150,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: t("VM Name"),
accessorKey: "vmName",
size: 200,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: t("VM Power"),
accessorKey: "vmPower",
size: 120,
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
},
{
header: t("VM Status"),
accessorKey: "vmStatus",
size: 100,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: t("Total Emission"),
accessorKey: "totalEmission",
size: 150,
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
},
{
header: t("Physical Machine"),
accessorKey: "physicalMachine",
size: 150,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: t("Cloud System"),
accessorKey: "cloudSystem",
size: 150,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: t("Data Center"),
accessorKey: "dataCenter",
size: 150,
Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span>,
},
{
header: "CO2",
accessorKey: "co2",
size: 100,
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
},
{
header: "CH4",
accessorKey: "ch4",
size: 100,
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
},
{
header: "N2O",
accessorKey: "n2o",
size: 100,
Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span>,
},
{
header: t("Created Date"),
accessorKey: "createdDate",
size: 180,
Cell: ({ cell }) => (
<span>
{cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"}
</span>
),
sortable: true,
},
],
[t]
);
const [loading, setLoading] = useState(true); // Memoize table data to prevent unnecessary re-renders
const tableData = useMemo(() => {
const columns = [ const data = mainDataTablesStore?.vmEmissionSummary || [];
{ header: t("VM ID"), accessorKey: "vmId", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, console.log("VM Emission data count:", data.length);
{ header: t("VM Name"), accessorKey: "vmName", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, return data;
{ header: t("VM Power"), accessorKey: "vmPower", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> }, }, [mainDataTablesStore?.vmEmissionSummary]);
{ header: t("VM Status"), accessorKey: "vmStatus", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> },
{ header: t("Total Emission"), accessorKey: "totalEmission", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> },
{ header: t("Created Date"), accessorKey: "createdDate", Cell: ({ cell }) => (<span>{cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"}</span>), sortable: true },
{ header: t("Physical Machine"), accessorKey: "physicalMachine", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> },
{ header: t("Project"), accessorKey: "project", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> },
{ header: t("Data Center"), accessorKey: "dataCenter", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> },
{ header: "CO2", accessorKey: "co2", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> },
{ header: "CH4", accessorKey: "ch4", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> },
{ header: "N2O", accessorKey: "n2o", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> },
];
const tableData = mainDataTablesStore?.vmEmissionSummary || [];
console.log('VM Emission data:', tableData);
if (error) { if (error) {
return ( return (
@@ -65,38 +166,108 @@ function MainDataTables() {
<Card> <Card>
<CardHeader className="border-bottom"> <CardHeader className="border-bottom">
<CardTitle tag="h4">{t("Raw Data")}</CardTitle> <CardTitle tag="h4">{t("Raw Data")}</CardTitle>
{tableData.length > 0 && (
<small className="text-muted">
{tableData.length} records loaded
</small>
)}
</CardHeader> </CardHeader>
{/* Datacenter Selection */}
<div className="p-3 border-bottom">
<Row>
<Col md="6">
<Label for="datacenter-select">{t("Data Center")}</Label>
<Select
id="datacenter-select"
value={selectedDataCenter}
onChange={setSelectedDataCenter}
options={dataCenterOptions}
placeholder={t("Select a data center...")}
isClearable
isSearchable
isLoading={dataCenterStore?.loading}
noOptionsMessage={() => t("No data centers available")}
styles={{
menu: (provided) => ({
...provided,
zIndex: 9999, // Ensure dropdown appears above other elements
}),
}}
menuPortalTarget={document.body} // Render dropdown in body to avoid container overflow
/>
</Col>
</Row>
</div>
{selectedDataCenter ? (
<MaterialReactTable <MaterialReactTable
columns={columns} columns={columns}
data={tableData} data={tableData}
// Performance optimizations for large datasets
enableColumnFilters={true} enableColumnFilters={true}
enableFilters={true} enableFilters={true}
enableGlobalFilter={true} enableGlobalFilter={true}
enablePagination={true} enablePagination={true}
enableColumnResizing={true} enableColumnResizing={true} // Disable resizing for better performance
enableStickyHeader={true} enableStickyHeader={true}
muiTableContainerProps={{ sx: { maxHeight: 'calc(100vh - 180px)' } }} enableRowVirtualization={true} // Enable virtualization for large datasets
muiTableProps={{ enableColumnVirtualization={false} // Keep columns visible
sx: { // Pagination settings for large datasets
tableLayout: 'auto',
},
}}
initialState={{ initialState={{
pagination: { pagination: {
pageSize: 100, pageSize: 100, // Reduce page size for better performance
pageIndex: 0 pageIndex: 0,
}, },
sorting: [ sorting: [{ id: "createdDate", desc: true }],
{ id: 'createdDate', desc: true } density: "compact",
],
density: 'compact'
}} }}
// Performance-optimized table props
muiTableContainerProps={{
sx: {
maxHeight: "calc(100vh - 250px)",
minHeight: "400px",
},
}}
muiTableProps={{
sx: {
tableLayout: "fixed", // Better performance with fixed layout
},
}}
// Pagination options
muiTablePaginationProps={{
rowsPerPageOptions: [10, 25, 50, 100],
showFirstButton: true,
showLastButton: true,
}}
// Loading and error states
state={{ state={{
isLoading: loading, isLoading: loading || mainDataTablesStore?.loading,
showProgressBars: true, showProgressBars: loading || mainDataTablesStore?.loading,
showSkeletons: true, showSkeletons: loading || mainDataTablesStore?.loading,
}}
// Disable features that can slow down large tables
enableRowSelection={false}
enableColumnOrdering={true}
enableColumnDragging={false}
enableDensityToggle={false}
enableFullScreenToggle={false}
// Custom loading overlay
renderProgressBarCell={({ cell }) => (
<div
style={{
width: "100%",
height: "20px",
backgroundColor: "#f0f0f0",
}} }}
/> />
)}
/>
) : (
<div className="p-4 text-center text-muted">
{t("Please select a data center to view raw data")}
</div>
)}
</Card> </Card>
</div> </div>
); );

View File

@@ -41,6 +41,78 @@ import { ChromePicker } from "react-color";
import { customFilterForSelect } from "../utility/Utils"; import { customFilterForSelect } from "../utility/Utils";
import { permissionCheck } from "../components/permission-check"; import { permissionCheck } from "../components/permission-check";
import { getDataCenters } from "../redux/actions/dataCenter"; import { getDataCenters } from "../redux/actions/dataCenter";
import L from "leaflet";
// Custom data center icon
const dataCenterIcon = new L.Icon({
iconUrl:
"data:image/svg+xml;base64," +
btoa(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
<defs>
<linearGradient id="serverGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2E5BBA;stop-opacity:1" />
</linearGradient>
<linearGradient id="rackGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#F5F5F5;stop-opacity:1" />
<stop offset="100%" style="stop-color:#E0E0E0;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Main server rack -->
<rect x="8" y="4" width="32" height="40" rx="2" ry="2" fill="url(#rackGradient)" stroke="#B0B0B0" stroke-width="1"/>
<!-- Server units -->
<rect x="10" y="6" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<rect x="10" y="12" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<rect x="10" y="18" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<rect x="10" y="24" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<rect x="10" y="30" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<rect x="10" y="36" width="28" height="4" rx="1" fill="url(#serverGradient)" stroke="#2E5BBA" stroke-width="0.5"/>
<!-- LED indicators -->
<circle cx="13" cy="8" r="0.8" fill="#00FF00"/>
<circle cx="13" cy="14" r="0.8" fill="#00FF00"/>
<circle cx="13" cy="20" r="0.8" fill="#FFFF00"/>
<circle cx="13" cy="26" r="0.8" fill="#00FF00"/>
<circle cx="13" cy="32" r="0.8" fill="#FF0000"/>
<circle cx="13" cy="38" r="0.8" fill="#00FF00"/>
<!-- Power indicators -->
<circle cx="35" cy="8" r="0.6" fill="#0080FF"/>
<circle cx="35" cy="14" r="0.6" fill="#0080FF"/>
<circle cx="35" cy="20" r="0.6" fill="#0080FF"/>
<circle cx="35" cy="26" r="0.6" fill="#0080FF"/>
<circle cx="35" cy="32" r="0.6" fill="#0080FF"/>
<circle cx="35" cy="38" r="0.6" fill="#0080FF"/>
<!-- Ventilation grilles -->
<rect x="16" y="7" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="8.5" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="13" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="14.5" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="19" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="20.5" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="25" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="26.5" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="31" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="32.5" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="37" width="16" height="0.5" fill="#1A4A8A"/>
<rect x="16" y="38.5" width="16" height="0.5" fill="#1A4A8A"/>
<!-- Base/feet -->
<rect x="6" y="42" width="4" height="2" rx="1" fill="#808080"/>
<rect x="38" y="42" width="4" height="2" rx="1" fill="#808080"/>
<!-- Shadow -->
<ellipse cx="24" cy="45" rx="18" ry="2" fill="#000000" opacity="0.2"/>
</svg>
`),
iconSize: [48, 48],
iconAnchor: [18, 36],
popupAnchor: [0, -36],
});
const ColorPicker = ({ selectedColors, setSelectedColors, index }) => { const ColorPicker = ({ selectedColors, setSelectedColors, index }) => {
const [showColorPicker, setShowColorPicker] = useState(false); const [showColorPicker, setShowColorPicker] = useState(false);
@@ -627,10 +699,25 @@ const Map = () => {
if (!dc.latitude || !dc.longitude) return null; if (!dc.latitude || !dc.longitude) return null;
return ( return (
<Marker key={dc.id} position={[dc.latitude, dc.longitude]}> <Marker
key={dc.id}
position={[dc.latitude, dc.longitude]}
icon={dataCenterIcon}
>
<Popup> <Popup>
<div className="data-center-popup"> <div className="data-center-popup">
<h5 className="mb-2">{dc.dataCenter}</h5> <h5 className="mb-2 text-primary">{dc.dataCenter}</h5>
<div className="mb-2">
<p className="mb-1">
<strong>{t("DataCenter.city")}:</strong>{" "}
<span>{dc.city?.name || "-"}</span>
</p>
{dc.address && (
<p className="mb-1 small text-muted">
<strong>{t("Address")}:</strong> {dc.address}
</p>
)}
</div>
<p className="mb-1"> <p className="mb-1">
<strong>{t("DataCenter.number")}:</strong> {dc.number} <strong>{t("DataCenter.number")}:</strong> {dc.number}
</p> </p>
@@ -638,20 +725,25 @@ const Map = () => {
<strong>{t("DataCenter.externalId")}:</strong>{" "} <strong>{t("DataCenter.externalId")}:</strong>{" "}
{dc.externalId} {dc.externalId}
</p> </p>
<p className="mb-1">
<strong>{t("DataCenter.city")}:</strong>{" "}
{dc.area?.cities?.map((city) => city.name).join(", ") ||
"-"}
</p>
{dc.area && ( {dc.area && (
<p className="mb-1"> <p className="mb-1">
<strong>{t("Area")}:</strong> {dc.area.tag} <strong>{t("Area")}:</strong> {dc.area.tag}
</p> </p>
)} )}
{dc.physicalMachines?.length > 0 && (
<p className="mb-1">
<strong>{t("Physical Machines")}:</strong>{" "}
<span className="badge badge-secondary">
{dc.physicalMachines.length}
</span>
</p>
)}
{dc.dataCenterEmissionSources?.length > 0 && ( {dc.dataCenterEmissionSources?.length > 0 && (
<p className="mb-1"> <p className="mb-1">
<strong>{t("EmissionSources")}:</strong>{" "} <strong>{t("EmissionSources.emissionSources")}:</strong>{" "}
<span className="badge badge-info">
{dc.dataCenterEmissionSources.length} {dc.dataCenterEmissionSources.length}
</span>
</p> </p>
)} )}
{dc.ayposURL && ( {dc.ayposURL && (