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,
@@ -789,7 +790,7 @@ public class SgsApplication implements CommandLineRunner {
paginate_datacenters_get.setDescription(PermissionDescription.PAGINATE_DATACENTERS_GET); paginate_datacenters_get.setDescription(PermissionDescription.PAGINATE_DATACENTERS_GET);
permissionService.save(paginate_datacenters_get); permissionService.save(paginate_datacenters_get);
} }
// Ensure SUPER_ADMIN has all permissions // Ensure SUPER_ADMIN has all permissions
Optional<Role> superAdminRole = roleRepo.findByTag("SUPER_ADMIN"); Optional<Role> superAdminRole = roleRepo.findByTag("SUPER_ADMIN");
if (superAdminRole.isPresent()) { if (superAdminRole.isPresent()) {
@@ -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,33 +867,30 @@ public class SgsApplication implements CommandLineRunner {
if (neighborhoodService.findAll().isEmpty()) { if (neighborhoodService.findAll().isEmpty()) {
createNeighborhoodsFromJson(); createNeighborhoodsFromJson();
} }
if (!cityService.findAll().isEmpty()) {
createDefaultArea();
}
} }
void createDefaultArea() { void createDefaultArea() {
// Check if default area already exists // Check if default area already exists
List<Area> existingAreas = areaService.findAll(); List<Area> existingAreas = areaService.findAll();
boolean defaultAreaExists = existingAreas.stream() boolean defaultAreaExists = existingAreas.stream()
.anyMatch(area -> "Turkiye".equals(area.getTag()) && area.isDefaultArea()); .anyMatch(area -> "Turkiye".equals(area.getTag()) && area.isDefaultArea());
if (!defaultAreaExists) { if (!defaultAreaExists) {
Area defaultArea = new Area(); Area defaultArea = new Area();
defaultArea.setTag("Turkiye"); defaultArea.setTag("Turkiye");
defaultArea.setDefaultArea(true); defaultArea.setDefaultArea(true);
defaultArea.setDeleted(false); defaultArea.setDeleted(false);
// Get all cities to add to the default area // Get all cities to add to the default area
List<City> allCities = cityService.findAll(); List<City> allCities = cityService.findAll();
defaultArea.setCities(allCities); defaultArea.setCities(allCities);
// Get the Turkey country to add to the default area // Get the Turkey country to add to the default area
List<Country> countries = countryService.findAll(); List<Country> countries = countryService.findAll();
if (!countries.isEmpty()) { if (!countries.isEmpty()) {
defaultArea.setCountries(countries); defaultArea.setCountries(countries);
} }
areaService.save(defaultArea); areaService.save(defaultArea);
} }
} }

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

@@ -17,10 +17,10 @@ public class DataCenterDto {
private Integer externalId; private Integer externalId;
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
private SectorDto sector; private SectorDto sector;
private SubSectorDto subSector; private SubSectorDto subSector;
@@ -62,12 +62,12 @@ public class DataCenterDto {
this.number = number; this.number = number;
} }
public AreaDto getArea() { public AreaDto getArea() {
return area; return area;
} }
public void setArea(AreaDto area) { public void setArea(AreaDto area) {
this.area = area; this.area = area;
} }
public Map<String, PhysicalMachineDto> getPhysicalMachine() { public Map<String, PhysicalMachineDto> getPhysicalMachine() {

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,73 +44,80 @@ 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
public DataCenter toEntity(DataCenterCreateInput input) { public DataCenter toEntity(DataCenterCreateInput input) {
DataCenter entity = new DataCenter(); DataCenter entity = new DataCenter();
// Basic fields // Basic fields
entity.setDataCenter(input.getDataCenter()); entity.setDataCenter(input.getDataCenter());
entity.setExternalId(input.getExternalId()); entity.setExternalId(input.getExternalId());
entity.setNumber(input.getNumber()); entity.setNumber(input.getNumber());
entity.setConsuptionAmount(input.getConsuptionAmount()); entity.setConsuptionAmount(input.getConsuptionAmount());
// Convert ID references to entities // Convert ID references to entities
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));
} }
if (input.getSubSectorId() != null) { if (input.getSubSectorId() != null) {
entity.setSubSector(subSectorService.findById(input.getSubSectorId()).orElse(null)); entity.setSubSector(subSectorService.findById(input.getSubSectorId()).orElse(null));
} }
if (input.getEmissionScopeId() != null) { if (input.getEmissionScopeId() != null) {
entity.setEmissionScope(emissionScopeService.findById(input.getEmissionScopeId()).orElse(null)); entity.setEmissionScope(emissionScopeService.findById(input.getEmissionScopeId()).orElse(null));
} }
if (input.getActivitySubUnitId() != null) { if (input.getActivitySubUnitId() != null) {
entity.setActivitySubUnit(activitySubUnitService.findById(input.getActivitySubUnitId()).orElse(null)); entity.setActivitySubUnit(activitySubUnitService.findById(input.getActivitySubUnitId()).orElse(null));
} }
// New attributes // New attributes
entity.setAyposURL(input.getAyposURL()); entity.setAyposURL(input.getAyposURL());
entity.setAddress(input.getAddress()); entity.setAddress(input.getAddress());
entity.setLatitude(input.getLatitude()); entity.setLatitude(input.getLatitude());
entity.setLongitude(input.getLongitude()); entity.setLongitude(input.getLongitude());
// Handle multiple emission sources if provided // Handle multiple emission sources if provided
if (input.getDataCenterEmissionSources() != null && !input.getDataCenterEmissionSources().isEmpty()) { if (input.getDataCenterEmissionSources() != null && !input.getDataCenterEmissionSources().isEmpty()) {
List<DataCenterEmissionSource> dataCenterEmissionSources = new ArrayList<>(); List<DataCenterEmissionSource> dataCenterEmissionSources = new ArrayList<>();
for (DataCenterEmissionSourceInput emissionSourceInput : input.getDataCenterEmissionSources()) { for (DataCenterEmissionSourceInput emissionSourceInput : input.getDataCenterEmissionSources()) {
DataCenterEmissionSource dcEmissionSource = new DataCenterEmissionSource(); DataCenterEmissionSource dcEmissionSource = new DataCenterEmissionSource();
dcEmissionSource.setDataCenter(entity); dcEmissionSource.setDataCenter(entity);
// 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);
} }
entity.setDataCenterEmissionSources(dataCenterEmissionSources); entity.setDataCenterEmissionSources(dataCenterEmissionSources);
} }
return entity; return entity;
} }
@@ -118,83 +127,89 @@ public class DataCenterMapper extends BaseCreateUpdateMapper<DataCenter, DataCen
if (input.getDataCenter() != null) { if (input.getDataCenter() != null) {
entity.setDataCenter(input.getDataCenter()); entity.setDataCenter(input.getDataCenter());
} }
if (input.getExternalId() != null) { if (input.getExternalId() != null) {
entity.setExternalId(input.getExternalId()); entity.setExternalId(input.getExternalId());
} }
if (input.getNumber() != null) { if (input.getNumber() != null) {
entity.setNumber(input.getNumber()); entity.setNumber(input.getNumber());
} }
if (input.getConsuptionAmount() != null) { if (input.getConsuptionAmount() != null) {
entity.setConsuptionAmount(input.getConsuptionAmount()); entity.setConsuptionAmount(input.getConsuptionAmount());
} }
// Update relationships only if provided // Update relationships only if provided
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));
} }
if (input.getSubSectorId() != null) { if (input.getSubSectorId() != null) {
entity.setSubSector(subSectorService.findById(input.getSubSectorId()).orElse(null)); entity.setSubSector(subSectorService.findById(input.getSubSectorId()).orElse(null));
} }
if (input.getEmissionScopeId() != null) { if (input.getEmissionScopeId() != null) {
entity.setEmissionScope(emissionScopeService.findById(input.getEmissionScopeId()).orElse(null)); entity.setEmissionScope(emissionScopeService.findById(input.getEmissionScopeId()).orElse(null));
} }
if (input.getActivitySubUnitId() != null) { if (input.getActivitySubUnitId() != null) {
entity.setActivitySubUnit(activitySubUnitService.findById(input.getActivitySubUnitId()).orElse(null)); entity.setActivitySubUnit(activitySubUnitService.findById(input.getActivitySubUnitId()).orElse(null));
} }
// 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)
if (input.getAyposURL() != null) { if (input.getAyposURL() != null) {
entity.setAyposURL(input.getAyposURL()); entity.setAyposURL(input.getAyposURL());
} }
if (input.getAddress() != null) { if (input.getAddress() != null) {
entity.setAddress(input.getAddress()); entity.setAddress(input.getAddress());
} }
if (input.getLatitude() != null) { if (input.getLatitude() != null) {
entity.setLatitude(input.getLatitude()); entity.setLatitude(input.getLatitude());
} }
if (input.getLongitude() != null) { if (input.getLongitude() != null) {
entity.setLongitude(input.getLongitude()); entity.setLongitude(input.getLongitude());
} }
return entity; return entity;
} }
} }

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,59 +34,48 @@ 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
CAST(v.id AS VARCHAR) as vm_id, CAST(v.id AS VARCHAR) as vm_id,
v.vm_name as vm_name, v.vm_name as vm_name,
v.power as vm_power, v.power as vm_power,
v.state as vm_status, v.state as vm_status,
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,
mdt.n2o mdt.n2o
FROM main_data_table mdt FROM main_data_table mdt
JOIN vm v ON mdt.vm_id = v.id JOIN vm v ON mdt.vm_id = v.id
LEFT JOIN physical_machine pm ON v.physical_machine_id = pm.id LEFT JOIN physical_machine pm ON v.physical_machine_id = pm.id
LEFT JOIN data_center dc ON pm.data_center_id = dc.id LEFT JOIN data_center dc ON pm.data_center_id = dc.id
""" + whereClause + """ """ + whereClause + """
ORDER BY mdt.created_date DESC, v.vm_name ORDER BY mdt.created_date DESC, v.vm_name
"""; """;
Query query = entityManager.createNativeQuery(sql); Query query = entityManager.createNativeQuery(sql);
// Add parameters if provided // Add parameters if provided
if (datacenterId != null) { if (datacenterId != null) {
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
List<EmissionSource> emissionSources = emissionSourceRepo.findByTag(emissionSourceName); EmissionSource emissionSource = null;
if (emissionSources.isEmpty()) {
// 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);
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);
@@ -805,10 +838,19 @@ public class MessageListener {
input.setVmId(vm.getId()); input.setVmId(vm.getId());
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());
System.out.println(" VM Name: " + vm.getVmName()); System.out.println(" VM Name: " + vm.getVmName());

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,27 +84,39 @@ 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(
if (!pm.vms) return acc; (acc, pm) => {
return { if (!pm.vms) return acc;
active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, return {
total: acc.total + pm.vms.length active:
}; acc.active +
}, { active: 0, total: 0 }); pm.vms.filter((vm) => vm.state?.toLowerCase() === "active")
.length,
total: acc.total + pm.vms.length,
};
},
{ 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(
if (!pm.vms) return acc; (acc, pm) => {
return { if (!pm.vms) return acc;
active: acc.active + pm.vms.filter(vm => vm.state?.toLowerCase() === "active").length, return {
total: acc.total + pm.vms.length active:
}; acc.active +
}, { active: 0, total: 0 }); pm.vms.filter((vm) => vm.state?.toLowerCase() === "active")
.length,
total: acc.total + pm.vms.length,
};
},
{ active: 0, total: 0 }
);
return ( return (
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Monitor size={16} className="mr-2" /> <Monitor size={16} className="mr-2" />
@@ -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,189 +1047,232 @@ 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
</small> least one emission source with consumption unit is
required.
</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}
<Label className="border rounded p-3 mb-3 bg-white"
for={`emissionSource-${index}`} >
className="form-label" <Row className="g-3">
> <Col xs="12" md="6">
Emission Source * <Label
</Label> for={`emissionSource-${index}`}
<Select className="form-label fw-bold"
id={`emissionSource-${index}`} >
placeholder="Select emission source..." Emission Source *
options={emissionSourcesOptions} </Label>
value={emissionSourcesOptions?.find( <Select
(option) => id={`emissionSource-${index}`}
option.value === source.emissionSourceId placeholder="Select emission source..."
)} options={emissionSourcesOptions}
onChange={(option) => { value={emissionSourcesOptions?.find(
const updatedSources = [ (option) =>
...selectedDataCenter.dataCenterEmissionSources, option.value === source.emissionSourceId
]; )}
updatedSources[index] = { onChange={(option) => {
...updatedSources[index],
emissionSourceId: option?.value,
consuptionUnitId: null, // Reset consumption unit when emission source changes
};
setSelectedDataCenter({
...selectedDataCenter,
dataCenterEmissionSources: updatedSources,
});
// Fetch consumption units for the selected emission source
if (option?.value) {
dispatch(
getConsuptionUnits({
id: option.value,
sector: selectedDataCenter?.sectorId,
})
);
}
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.subSectorId}
styles={{
placeholder: (provided) => ({
...provided,
color: "#6e6b7b",
}),
}}
menuPlacement="top"
/>
</Col>
<Col sm="4">
<Label
for={`consuptionUnit-${index}`}
className="form-label"
>
Consumption Unit *
</Label>
<Select
id={`consuptionUnit-${index}`}
placeholder="Select consumption unit..."
options={consuptionUnitsOptions}
value={consuptionUnitsOptions?.find(
(option) =>
option.value === source.consuptionUnitId
)}
onChange={(option) => {
const updatedSources = [
...selectedDataCenter.dataCenterEmissionSources,
];
updatedSources[index] = {
...updatedSources[index],
consuptionUnitId: option?.value,
};
setSelectedDataCenter({
...selectedDataCenter,
dataCenterEmissionSources: updatedSources,
});
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!source.emissionSourceId}
styles={{
placeholder: (provided) => ({
...provided,
color: "#6e6b7b",
}),
}}
menuPlacement="top"
/>
</Col>
<Col sm="3">
<Label className="form-label d-block">
Actions
</Label>
<div className="d-flex gap-2">
<Button
color={
source.isDefault
? "primary"
: "outline-secondary"
}
size="sm"
onClick={() => {
const updatedSources = [ const updatedSources = [
...selectedDataCenter.dataCenterEmissionSources, ...selectedDataCenter.dataCenterEmissionSources,
]; ];
// First, set all sources to not default updatedSources[index] = {
updatedSources.forEach( ...updatedSources[index],
(s) => (s.isDefault = false) emissionSourceId: option?.value,
); consuptionUnitId: null, // Reset consumption unit when emission source changes
// Then set the selected one as default };
updatedSources[index].isDefault = true;
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
dataCenterEmissionSources: updatedSources, dataCenterEmissionSources: updatedSources,
}); });
}} // Fetch consumption units for the selected emission source
title="Set as default emission source" if (option?.value) {
> dispatch(
{source.isDefault ? "★ Default" : "☆ Default"} getConsuptionUnits({
</Button> id: option.value,
<Button sector: selectedDataCenter?.sectorId,
color="outline-danger" })
size="sm"
onClick={() => {
const updatedSources =
selectedDataCenter.dataCenterEmissionSources.filter(
(_, i) => i !== index
); );
// If we're removing the default source and there are other sources, make the first one default
if (
source.isDefault &&
updatedSources.length > 0
) {
updatedSources[0].isDefault = true;
} }
}}
isClearable
filterOption={customFilterForSelect}
isDisabled={!selectedDataCenter.subSectorId}
styles={{
placeholder: (provided) => ({
...provided,
color: "#6e6b7b",
}),
}}
menuPlacement="auto"
/>
</Col>
<Col xs="12" md="6">
<Label
for={`consuptionUnit-${index}`}
className="form-label fw-bold"
>
Consumption Unit *
</Label>
<Select
id={`consuptionUnit-${index}`}
placeholder="Select consumption unit..."
options={consuptionUnitsOptions}
value={consuptionUnitsOptions?.find(
(option) =>
option.value === source.consuptionUnitId
)}
onChange={(option) => {
const updatedSources = [
...selectedDataCenter.dataCenterEmissionSources,
];
updatedSources[index] = {
...updatedSources[index],
consuptionUnitId: option?.value,
};
setSelectedDataCenter({ setSelectedDataCenter({
...selectedDataCenter, ...selectedDataCenter,
dataCenterEmissionSources: updatedSources, dataCenterEmissionSources: updatedSources,
}); });
}} }}
disabled={ isClearable
selectedDataCenter.dataCenterEmissionSources filterOption={customFilterForSelect}
.length === 1 isDisabled={!source.emissionSourceId}
} styles={{
title="Remove emission source" placeholder: (provided) => ({
> ...provided,
<Trash size={14} /> color: "#6e6b7b",
</Button> }),
</div> }}
</Col> menuPlacement="auto"
</Row> />
</Col>
</Row>
<Row className="mt-3">
<Col xs="12">
<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 flex-wrap">
<Button
color={
source.isDefault
? "primary"
: "outline-secondary"
}
size="sm"
onClick={() => {
const updatedSources = [
...selectedDataCenter.dataCenterEmissionSources,
];
// First, set all sources to not default
updatedSources.forEach(
(s) => (s.isDefault = false)
);
// Then set the selected one as default
updatedSources[index].isDefault = true;
setSelectedDataCenter({
...selectedDataCenter,
dataCenterEmissionSources:
updatedSources,
});
}}
title="Set as default emission source"
>
{source.isDefault
? "★ Default"
: "☆ Set Default"}
</Button>
{source.isDefault && (
<span className="badge bg-success align-self-center">
Default Source
</span>
)}
</div>
<Button
color="outline-danger"
size="sm"
onClick={() => {
const updatedSources =
selectedDataCenter.dataCenterEmissionSources.filter(
(_, i) => i !== index
);
// If we're removing the default source and there are other sources, make the first one default
if (
source.isDefault &&
updatedSources.length > 0
) {
updatedSources[0].isDefault = true;
}
setSelectedDataCenter({
...selectedDataCenter,
dataCenterEmissionSources: updatedSources,
});
}}
disabled={
selectedDataCenter.dataCenterEmissionSources
.length === 1
}
title="Remove emission source"
>
<Trash size={14} className="me-1" />
Remove
</Button>
</div>
</Col>
</Row>
</div>
) )
)} )}
<Button <div className="text-center mt-3">
color="primary" <Button
size="sm" color="primary"
onClick={() => { size="sm"
setSelectedDataCenter({ onClick={() => {
...selectedDataCenter, setSelectedDataCenter({
dataCenterEmissionSources: [ ...selectedDataCenter,
...selectedDataCenter.dataCenterEmissionSources, dataCenterEmissionSources: [
{ ...selectedDataCenter.dataCenterEmissionSources,
emissionSourceId: null, {
consuptionUnitId: null, emissionSourceId: null,
isDefault: false, consuptionUnitId: null,
}, isDefault: false,
], },
}); ],
}} });
disabled={!selectedDataCenter.subSectorId} }}
> disabled={!selectedDataCenter.subSectorId}
<Plus size={14} className="me-1" /> className="w-100 d-sm-none"
Add Emission Source >
</Button> <Plus size={14} className="me-1" />
Add Another Emission Source
</Button>
</div>
</div> </div>
</FormGroup> </FormGroup>
</Col> </Col>

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(() => {
const fetchData = async () => { dispatch(getDataCenters());
try {
setLoading(true);
await dispatch(getVMEmissionSummary());
} catch (err) {
console.error('Error in MainDataTables:', err);
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [dispatch]); }, [dispatch]);
// Debug log for store data // Update datacenter options when datacenters are loaded
useEffect(() => { useEffect(() => {
console.log('Current store data:', mainDataTablesStore); if (dataCenterStore?.dataCenters?.length > 0) {
}, [mainDataTablesStore]); const options = dataCenterStore.dataCenters.map((dataCenter) => ({
value: dataCenter.id,
label: dataCenter.dataCenter,
externalId: dataCenter.externalId,
}));
setDataCenterOptions(options);
}
}, [dataCenterStore?.dataCenters]);
const [loading, setLoading] = useState(true); // Fetch VM emission data when datacenter is selected
useEffect(() => {
if (selectedDataCenter?.value) {
const fetchData = async () => {
try {
setLoading(true);
setError(null);
await dispatch(getVMEmissionSummary(selectedDataCenter.value));
} catch (err) {
console.error("Error in MainDataTables:", err);
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}
}, [selectedDataCenter, dispatch]);
const columns = [ // Memoize columns to prevent re-renders
{ header: t("VM ID"), accessorKey: "vmId", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, const columns = useMemo(
{ header: t("VM Name"), accessorKey: "vmName", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, () => [
{ header: t("VM Power"), accessorKey: "vmPower", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> }, {
{ header: t("VM Status"), accessorKey: "vmStatus", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, header: t("VM ID"),
{ header: t("Total Emission"), accessorKey: "totalEmission", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> }, accessorKey: "vmId",
{ header: t("Created Date"), accessorKey: "createdDate", Cell: ({ cell }) => (<span>{cell.getValue() ? new Date(cell.getValue()).toLocaleString() : "-"}</span>), sortable: true }, size: 150,
{ header: t("Physical Machine"), accessorKey: "physicalMachine", Cell: ({ cell }) => <span>{cell.getValue() || "-"}</span> }, 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: t("VM Name"),
{ header: "CH4", accessorKey: "ch4", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> }, accessorKey: "vmName",
{ header: "N2O", accessorKey: "n2o", Cell: ({ cell }) => <span>{editNumbers(cell.getValue()) || "-"}</span> }, 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 tableData = mainDataTablesStore?.vmEmissionSummary || []; // Memoize table data to prevent unnecessary re-renders
console.log('VM Emission data:', tableData); const tableData = useMemo(() => {
const data = mainDataTablesStore?.vmEmissionSummary || [];
console.log("VM Emission data count:", data.length);
return data;
}, [mainDataTablesStore?.vmEmissionSummary]);
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>
<MaterialReactTable
columns={columns} {/* Datacenter Selection */}
data={tableData} <div className="p-3 border-bottom">
enableColumnFilters={true} <Row>
enableFilters={true} <Col md="6">
enableGlobalFilter={true} <Label for="datacenter-select">{t("Data Center")}</Label>
enablePagination={true} <Select
enableColumnResizing={true} id="datacenter-select"
enableStickyHeader={true} value={selectedDataCenter}
muiTableContainerProps={{ sx: { maxHeight: 'calc(100vh - 180px)' } }} onChange={setSelectedDataCenter}
muiTableProps={{ options={dataCenterOptions}
sx: { placeholder={t("Select a data center...")}
tableLayout: 'auto', isClearable
}, isSearchable
}} isLoading={dataCenterStore?.loading}
initialState={{ noOptionsMessage={() => t("No data centers available")}
pagination: { styles={{
pageSize: 100, menu: (provided) => ({
pageIndex: 0 ...provided,
}, zIndex: 9999, // Ensure dropdown appears above other elements
sorting: [ }),
{ id: 'createdDate', desc: true } }}
], menuPortalTarget={document.body} // Render dropdown in body to avoid container overflow
density: 'compact' />
}} </Col>
state={{ </Row>
isLoading: loading, </div>
showProgressBars: true,
showSkeletons: true, {selectedDataCenter ? (
}} <MaterialReactTable
/> columns={columns}
data={tableData}
// Performance optimizations for large datasets
enableColumnFilters={true}
enableFilters={true}
enableGlobalFilter={true}
enablePagination={true}
enableColumnResizing={true} // Disable resizing for better performance
enableStickyHeader={true}
enableRowVirtualization={true} // Enable virtualization for large datasets
enableColumnVirtualization={false} // Keep columns visible
// Pagination settings for large datasets
initialState={{
pagination: {
pageSize: 100, // Reduce page size for better performance
pageIndex: 0,
},
sorting: [{ id: "createdDate", desc: true }],
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={{
isLoading: loading || mainDataTablesStore?.loading,
showProgressBars: loading || mainDataTablesStore?.loading,
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>{" "}
{dc.dataCenterEmissionSources.length} <span className="badge badge-info">
{dc.dataCenterEmissionSources.length}
</span>
</p> </p>
)} )}
{dc.ayposURL && ( {dc.ayposURL && (