forked from Abdulbari/sgeUpdated
Merge pull request 'multi-emission-feature' (#14) from multi-emission-feature into main
Reviewed-on: BLC/sgeUpdated#14
This commit is contained in:
@@ -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,16 +867,13 @@ 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();
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,47 +34,40 @@ 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);
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type DataCenter {
|
|||||||
|
|
||||||
physicalMachines: [PhysicalMachine]
|
physicalMachines: [PhysicalMachine]
|
||||||
area: Area
|
area: Area
|
||||||
|
city: City
|
||||||
number: Int
|
number: Int
|
||||||
|
|
||||||
ayposURL: String
|
ayposURL: String
|
||||||
|
|||||||
@@ -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!]!
|
||||||
}
|
}
|
||||||
@@ -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!
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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(
|
||||||
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">
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user