Use env vars in front and back end projects
Change-Id: Id093976032f079c62efd9fcaaf0617e05ceef3b0
This commit is contained in:
parent
ac6e1c1300
commit
40d9f11047
@ -2,7 +2,7 @@ NEBULOUS_BROKER_URL=158.37.63.86
|
|||||||
NEBULOUS_BROKER_PORT=31609
|
NEBULOUS_BROKER_PORT=31609
|
||||||
NEBULOUS_BROKER_USERNAME=admin
|
NEBULOUS_BROKER_USERNAME=admin
|
||||||
NEBULOUS_BROKER_PASSWORD=admin
|
NEBULOUS_BROKER_PASSWORD=admin
|
||||||
POSTGRES_DB_HOST=localhost
|
POSTGRES_DB_HOST=db
|
||||||
POSTGRES_DB_NAME=fog_broker
|
POSTGRES_DB_NAME=fog_broker
|
||||||
POSTGRES_DB_PORT=5432
|
POSTGRES_DB_PORT=5432
|
||||||
POSTGRES_DB_USER=dbuser
|
POSTGRES_DB_USER=dbuser
|
||||||
|
@ -6,7 +6,7 @@ from scipy.stats import rankdata
|
|||||||
def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids):
|
def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids):
|
||||||
print("Evaluation begun with perform_evaluation():")
|
print("Evaluation begun with perform_evaluation():")
|
||||||
# print("Data Table:", data_table)
|
# print("Data Table:", data_table)
|
||||||
# Identify the boolean criteria columns by checking if all values are either 0 or 1
|
# Identify the boolean criteria columns by checking if all values are either 0 or 1
|
||||||
# boolean_criteria = [criterion for criterion in data_table if set(data_table[criterion]) <= {0, 1}]
|
# boolean_criteria = [criterion for criterion in data_table if set(data_table[criterion]) <= {0, 1}]
|
||||||
boolean_criteria = [criterion for criterion in data_table if 'boolean' in criterion.lower()]
|
boolean_criteria = [criterion for criterion in data_table if 'boolean' in criterion.lower()]
|
||||||
# print("Boolean Criteria:", boolean_criteria)
|
# print("Boolean Criteria:", boolean_criteria)
|
||||||
@ -18,10 +18,10 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
# The first category is for the all False and the last for the all True values
|
# The first category is for the all False and the last for the all True values
|
||||||
fog_node_categories = {i: [] for i in range(len(boolean_criteria) + 1)}
|
fog_node_categories = {i: [] for i in range(len(boolean_criteria) + 1)}
|
||||||
|
|
||||||
# Iterate over the list of fog nodes to count the '1' (True) values and assign categories
|
# Iterate over the list of nodes to count the '1' (True) values and assign categories
|
||||||
for i in range(len(node_names)):
|
for i in range(len(node_ids)):
|
||||||
true_count = sum(data_table[boolean][i] for boolean in boolean_criteria)
|
true_count = sum(data_table[boolean][i] for boolean in boolean_criteria)
|
||||||
fog_node_categories[true_count].append(node_names[i])
|
fog_node_categories[true_count].append(node_ids[i])
|
||||||
|
|
||||||
# Remove the boolean criteria from the data_table
|
# Remove the boolean criteria from the data_table
|
||||||
for boolean in boolean_criteria:
|
for boolean in boolean_criteria:
|
||||||
@ -41,8 +41,8 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
for fog_node_high in fog_node_categories[sorted_categories[higher_cat]]:
|
for fog_node_high in fog_node_categories[sorted_categories[higher_cat]]:
|
||||||
for fog_node_low in fog_node_categories[sorted_categories[higher_cat + 1]]:
|
for fog_node_low in fog_node_categories[sorted_categories[higher_cat + 1]]:
|
||||||
# Create a constraint for each pair of fog nodes (high > low)
|
# Create a constraint for each pair of fog nodes (high > low)
|
||||||
high_scores = [-data_table[criterion][node_names.index(fog_node_high)] for criterion in data_table]
|
high_scores = [-data_table[criterion][node_ids.index(fog_node_high)] for criterion in data_table]
|
||||||
low_scores = [-data_table[criterion][node_names.index(fog_node_low)] for criterion in data_table]
|
low_scores = [-data_table[criterion][node_ids.index(fog_node_low)] for criterion in data_table]
|
||||||
constraint = [h - l for h, l in zip(high_scores, low_scores)]
|
constraint = [h - l for h, l in zip(high_scores, low_scores)]
|
||||||
A_boolean.append(constraint)
|
A_boolean.append(constraint)
|
||||||
b_boolean.append(0) # The score difference must be greater than 0
|
b_boolean.append(0) # The score difference must be greater than 0
|
||||||
@ -124,8 +124,8 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
num_of_dmus = len(next(iter(data_table.values())))
|
num_of_dmus = len(next(iter(data_table.values())))
|
||||||
Cols_No = len(criteria_list)
|
Cols_No = len(criteria_list)
|
||||||
DEA_Scores = []
|
DEA_Scores = []
|
||||||
epsilon = 0.00000 # Lower bound of the variables
|
# epsilon = 0.000001 # Lower bound of the variables
|
||||||
|
epsilon = 0
|
||||||
# Iterating over each DMU to Perform DEA
|
# Iterating over each DMU to Perform DEA
|
||||||
for dmu_index in range(num_of_dmus):
|
for dmu_index in range(num_of_dmus):
|
||||||
# Gathering values for the current DMU
|
# Gathering values for the current DMU
|
||||||
@ -171,7 +171,7 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
"DEA Score": DEA_Scores[i],
|
"DEA Score": DEA_Scores[i],
|
||||||
"Rank": int(DEA_Scores_Ranked[i])
|
"Rank": int(DEA_Scores_Ranked[i])
|
||||||
}
|
}
|
||||||
for i in range(len(node_names))
|
for i in range(len(node_ids))
|
||||||
]
|
]
|
||||||
|
|
||||||
# Return successful results
|
# Return successful results
|
||||||
@ -184,9 +184,9 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
# relative_wr_data: [{'LHSCriterion': 'Accountability', 'Operator': 1, 'Intense': 2, 'RHSCriterion': 'Compliance'}]
|
# relative_wr_data: [{'LHSCriterion': 'Accountability', 'Operator': 1, 'Intense': 2, 'RHSCriterion': 'Compliance'}]
|
||||||
# immediate_wr_data: [{'Criterion': 'Compliance', 'Operator': 1, 'Value': 0.5}]
|
# immediate_wr_data: [{'Criterion': 'Compliance', 'Operator': 1, 'Value': 0.5}]
|
||||||
#
|
#
|
||||||
# node_names = ['2ad4bd97-d932-42a5-860e-e607a50f161d', 'e917581d-1a62-496b-9d2e-05972fe309e9', '78aca9a8-8c14-4c7d-af34-72cef0da992d', 'd2bddce9-4118-41a9-b528-3bac32b13312']
|
# node_ids = ['2ad4bd97-d932-42a5-860e-e607a50f161d', 'e917581d-1a62-496b-9d2e-05972fe309e9', '78aca9a8-8c14-4c7d-af34-72cef0da992d', 'd2bddce9-4118-41a9-b528-3bac32b13312']
|
||||||
#
|
#
|
||||||
# Evaluation_JSON = perform_evaluation(data_table, [], [], node_names)
|
# Evaluation_JSON = perform_evaluation(data_table, [], [], node_ids)
|
||||||
# pretty_json = json.dumps(Evaluation_JSON)
|
# pretty_json = json.dumps(Evaluation_JSON)
|
||||||
|
|
||||||
|
|
||||||
@ -218,13 +218,13 @@ def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_nam
|
|||||||
# # # "immediate_wr_data":[{"Criterion":"Accountability","Operator":1,"Value":0.2}]}
|
# # # "immediate_wr_data":[{"Criterion":"Accountability","Operator":1,"Value":0.2}]}
|
||||||
# # # w1>=0.2 and w1<=0.5
|
# # # w1>=0.2 and w1<=0.5
|
||||||
# #
|
# #
|
||||||
# node_names = ['Fog Node 1', 'Fog Node 2', 'Fog Node 3', 'Fog Node 4', 'Fog Node 5']
|
# node_ids = ['Fog Node 1', 'Fog Node 2', 'Fog Node 3', 'Fog Node 4', 'Fog Node 5']
|
||||||
#
|
#
|
||||||
# Evaluation_JSON = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names)
|
# Evaluation_JSON = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_ids)
|
||||||
# print("Evaluation_JSON:", Evaluation_JSON)
|
# print("Evaluation_JSON:", Evaluation_JSON)
|
||||||
|
|
||||||
|
|
||||||
# Evaluation_JSON = perform_evaluation(data_table, [], [], node_names)
|
# Evaluation_JSON = perform_evaluation(data_table, [], [], node_ids)
|
||||||
# pretty_json = json.dumps(Evaluation_JSON)
|
# pretty_json = json.dumps(Evaluation_JSON)
|
||||||
# print(pretty_json)
|
# print(pretty_json)
|
||||||
# print("Evaluation_JSON:", Evaluation_JSON)
|
# print("Evaluation_JSON:", Evaluation_JSON)
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
# import read_file
|
|
||||||
import get_data as file
|
import get_data as file
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import data_types as attr_data_types
|
|
||||||
from Evaluation import perform_evaluation
|
|
||||||
from data_types import get_attr_data_type
|
|
||||||
import db.db_functions as db_functions
|
import db.db_functions as db_functions
|
||||||
|
|
||||||
# Boolean_Variables = ['Extend offered network capacity', 'Extend offered processing capacity', 'Extend offered memory capacity',
|
# Boolean_Variables = ['Extend offered network capacity', 'Extend offered processing capacity', 'Extend offered memory capacity',
|
||||||
@ -16,48 +12,93 @@ Boolean_Variables = [
|
|||||||
"0cf00a53-fd33-4887-bb38-e0bbb04e3f3e", "d95c1dae-1e22-4fb4-9cdc-743e96d0dddc",
|
"0cf00a53-fd33-4887-bb38-e0bbb04e3f3e", "d95c1dae-1e22-4fb4-9cdc-743e96d0dddc",
|
||||||
"8cd09fe9-c119-4ccd-b651-0f18334dbbe4", "7147995c-8e68-4106-ab24-f0a7673eb5f5", "c1c5b3c9-6178-4d67-a7e3-0285c2bf98ef"]
|
"8cd09fe9-c119-4ccd-b651-0f18334dbbe4", "7147995c-8e68-4106-ab24-f0a7673eb5f5", "c1c5b3c9-6178-4d67-a7e3-0285c2bf98ef"]
|
||||||
|
|
||||||
# Used to transform SAL's response before sending to DataGrid
|
# Used to extract_SAL_node_candidate_data from Use Side for DataGrid
|
||||||
# This version is designed to read the structure of SAL's response obtained from POSTMAN
|
def extract_SAL_node_candidate_data_Front(json_data):
|
||||||
def extract_node_candidate_data(json_file_path):
|
default_criteria_list = ["cores", "ram", "disk", "memoryPrice", "price"]
|
||||||
with open(json_file_path, 'r') as file:
|
|
||||||
json_data = json.load(file)
|
if isinstance(json_data, dict): # Single node dictionary
|
||||||
|
json_data = [json_data] # Wrap it in a list
|
||||||
|
|
||||||
extracted_data = []
|
extracted_data = []
|
||||||
node_ids = []
|
node_ids = []
|
||||||
node_names = []
|
node_names = []
|
||||||
|
|
||||||
for item in json_data:
|
for item in json_data:
|
||||||
hardware_info = item.get("nodeCandidate", {}).get("hardware", {})
|
hardware_info = item.get("hardware", {})
|
||||||
|
# Extract default criteria values
|
||||||
|
default_criteria_values = {criteria: hardware_info.get(criteria, 0.0) if criteria in hardware_info else item.get(criteria, 0.0) for criteria in default_criteria_list}
|
||||||
|
|
||||||
|
# Correctly extract the providerName from the cloud information
|
||||||
|
cloud_info = item.get("cloud", {}) # get the cloud info or default to an empty dict
|
||||||
|
api_info = cloud_info.get("api", {})
|
||||||
|
provider_name = api_info.get("providerName", "Unknown Provider")
|
||||||
|
|
||||||
|
# each item is now a dictionary
|
||||||
node_data = {
|
node_data = {
|
||||||
"name": item['name'],
|
"nodeId": item.get("nodeId", ''),
|
||||||
"id": item['id'],
|
"id": item.get('id', ''),
|
||||||
"nodeId": item.get("nodeCandidate", {}).get("nodeId"),
|
"nodeCandidateType": item.get("nodeCandidateType", ''),
|
||||||
"nodeCandidateType": item.get("nodeCandidate", {}).get("nodeCandidateType"),
|
**default_criteria_values, # Unpack default criteria values into node_data
|
||||||
"price": item.get("nodeCandidate", {}).get("price", 0.0),
|
"hardware": hardware_info,
|
||||||
"pricePerInvocation": item.get("nodeCandidate", {}).get("pricePerInvocation", 0.0),
|
"location": item.get("location", {}),
|
||||||
"memoryPrice": item.get("nodeCandidate", {}).get("memoryPrice", 0.0),
|
"image": item.get("image", {}),
|
||||||
"hardware": {
|
"providerName": provider_name
|
||||||
"id": hardware_info.get("id"),
|
|
||||||
"name": hardware_info.get("name"),
|
|
||||||
"providerId": hardware_info.get("providerId"),
|
|
||||||
"cores": hardware_info.get("cores"),
|
|
||||||
"ram": hardware_info.get("ram") * 1024 if hardware_info.get("ram") else None, # Assuming RAM needs conversion from GB to MB
|
|
||||||
"disk": hardware_info.get("disk"),
|
|
||||||
"fpga": hardware_info.get("fpga")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
extracted_data.append(node_data)
|
extracted_data.append(node_data)
|
||||||
node_ids.append(item['id'])
|
node_ids.append(node_data["id"])
|
||||||
node_names.append(item.get('name', ''))
|
|
||||||
|
|
||||||
number_of_nodes = len(json_data)
|
# print("Before create_node_name")
|
||||||
|
node_names.append(create_node_name(node_data)) # call create_node_name function
|
||||||
|
# print("After create_node_name")
|
||||||
|
|
||||||
return extracted_data, number_of_nodes, node_ids, node_names
|
return extracted_data, node_ids, node_names
|
||||||
|
|
||||||
|
# Used to create node names for DataGrid
|
||||||
|
def create_node_name(node_data):
|
||||||
|
node_type = node_data.get("nodeCandidateType", "UNKNOWN_TYPE")
|
||||||
|
|
||||||
|
# Initialize default values
|
||||||
|
node_city = ""
|
||||||
|
node_country = ""
|
||||||
|
node_os_family = "Unknown OS"
|
||||||
|
provider_name = node_data.get("providerName", "")
|
||||||
|
|
||||||
# Only 50 nodes
|
# Safely access nested properties for city and country
|
||||||
|
location = node_data.get("location")
|
||||||
|
if location and "geoLocation" in location and location["geoLocation"]:
|
||||||
|
geo_location = location["geoLocation"]
|
||||||
|
node_city = geo_location.get("city", "")
|
||||||
|
node_country = geo_location.get("country", "")
|
||||||
|
|
||||||
|
image = node_data.get("image")
|
||||||
|
if image and "operatingSystem" in image and image["operatingSystem"]:
|
||||||
|
operating_system = image["operatingSystem"]
|
||||||
|
node_os_family = operating_system.get("operatingSystemFamily", node_os_family)
|
||||||
|
|
||||||
|
cores = node_data.get("cores", "")
|
||||||
|
ram = node_data.get("ram", "")
|
||||||
|
|
||||||
|
# Construct the node name with conditional inclusions
|
||||||
|
node_name_parts = [node_type]
|
||||||
|
if node_city and node_country:
|
||||||
|
node_name_parts.append(f"{node_city}, {node_country}")
|
||||||
|
|
||||||
|
if provider_name:
|
||||||
|
node_name_parts.append(f"Provider: {provider_name}")
|
||||||
|
|
||||||
|
node_name_parts.append(f"OS: {node_os_family}")
|
||||||
|
|
||||||
|
if cores:
|
||||||
|
node_name_parts.append(f"Cores: {cores} ")
|
||||||
|
if ram:
|
||||||
|
node_name_parts.append(f"RAM: {ram} ")
|
||||||
|
|
||||||
|
node_name = " - ".join(part for part in node_name_parts if part) # Only include non-empty parts
|
||||||
|
return node_name
|
||||||
|
|
||||||
|
# Used to extract_SAL_node_candidate_data from App Side working with Optimizer
|
||||||
def extract_SAL_node_candidate_data(json_string):
|
def extract_SAL_node_candidate_data(json_string):
|
||||||
|
# print("Entered in extract_SAL_node_candidate_data")
|
||||||
try:
|
try:
|
||||||
json_data = json.loads(json_string) # Ensure json_data is a list of dictionaries
|
json_data = json.loads(json_string) # Ensure json_data is a list of dictionaries
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@ -73,9 +114,9 @@ def extract_SAL_node_candidate_data(json_string):
|
|||||||
"nodeId": item.get("nodeId", ''),
|
"nodeId": item.get("nodeId", ''),
|
||||||
"id": item.get('id', ''),
|
"id": item.get('id', ''),
|
||||||
"nodeCandidateType": item.get("nodeCandidateType", ''),
|
"nodeCandidateType": item.get("nodeCandidateType", ''),
|
||||||
"price": item.get("price", 0.0),
|
"price": item.get("price", ''),
|
||||||
"pricePerInvocation": item.get("pricePerInvocation", 0.0),
|
"pricePerInvocation": item.get("pricePerInvocation", ''),
|
||||||
"memoryPrice": item.get("memoryPrice", 0.0),
|
"memoryPrice": item.get("memoryPrice", ''),
|
||||||
"hardware": item.get("hardware", {})
|
"hardware": item.get("hardware", {})
|
||||||
}
|
}
|
||||||
extracted_data.append(node_data)
|
extracted_data.append(node_data)
|
||||||
@ -85,48 +126,11 @@ def extract_SAL_node_candidate_data(json_string):
|
|||||||
number_of_nodes = len(extracted_data)
|
number_of_nodes = len(extracted_data)
|
||||||
node_ids = [node['id'] for node in extracted_data]
|
node_ids = [node['id'] for node in extracted_data]
|
||||||
node_names = [node['id'] for node in extracted_data]
|
node_names = [node['id'] for node in extracted_data]
|
||||||
|
return extracted_data, node_ids, node_names
|
||||||
return extracted_data, number_of_nodes, node_ids, node_names
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Used to transform SAL's response all nodes
|
|
||||||
# def extract_SAL_node_candidate_data(sal_reply):
|
|
||||||
# # Parse the JSON string in the body of the SAL reply
|
|
||||||
# body = sal_reply.get('body', '')
|
|
||||||
# extracted_data = []
|
|
||||||
#
|
|
||||||
# try:
|
|
||||||
# json_data = json.loads(body)
|
|
||||||
# except json.JSONDecodeError as e:
|
|
||||||
# print(f"Error parsing JSON: {e}")
|
|
||||||
# return extracted_data
|
|
||||||
#
|
|
||||||
# for item in json_data:
|
|
||||||
# node_data = {
|
|
||||||
# "name": item.get('name', ''),
|
|
||||||
# "name": item.get('id', ''),
|
|
||||||
# "id": item.get('id', ''),
|
|
||||||
# "nodeId": item.get("nodeId", ''),
|
|
||||||
# "nodeCandidateType": item.get("nodeCandidateType", ''),
|
|
||||||
# "price": item.get("price", 0.0),
|
|
||||||
# "pricePerInvocation": item.get("pricePerInvocation", 0.0),
|
|
||||||
# "memoryPrice": item.get("memoryPrice", 0.0),
|
|
||||||
# "hardware": item.get("hardware", {})
|
|
||||||
# }
|
|
||||||
# extracted_data.append(node_data)
|
|
||||||
#
|
|
||||||
# number_of_nodes = len(extracted_data)
|
|
||||||
# node_ids = [node['id'] for node in extracted_data]
|
|
||||||
# node_names = [node['name'] for node in extracted_data]
|
|
||||||
# if not node_names:
|
|
||||||
# node_names = node_ids
|
|
||||||
#
|
|
||||||
# return extracted_data, number_of_nodes, node_ids, node_names
|
|
||||||
|
|
||||||
|
|
||||||
# Used to map the criteria from SAL's response with the selected criteria (from frontend)
|
# Used to map the criteria from SAL's response with the selected criteria (from frontend)
|
||||||
def create_criteria_mapping(selected_items, extracted_data):
|
def create_criteria_mapping():
|
||||||
field_mapping = {
|
field_mapping = {
|
||||||
# "Cost": "price",
|
# "Cost": "price",
|
||||||
"Operating cost": "price",
|
"Operating cost": "price",
|
||||||
@ -137,7 +141,8 @@ def create_criteria_mapping(selected_items, extracted_data):
|
|||||||
}
|
}
|
||||||
return field_mapping
|
return field_mapping
|
||||||
|
|
||||||
# Used to create the required structure for the Evaluation
|
|
||||||
|
# Used to create the required structure for the Evaluation in process_evaluation_data endpoint
|
||||||
def transform_grid_data_to_table(json_data):
|
def transform_grid_data_to_table(json_data):
|
||||||
grid_data = json_data.get('gridData', [])
|
grid_data = json_data.get('gridData', [])
|
||||||
relative_wr_data = json_data.get('relativeWRData', [])
|
relative_wr_data = json_data.get('relativeWRData', [])
|
||||||
@ -153,8 +158,9 @@ def transform_grid_data_to_table(json_data):
|
|||||||
boolean_value_mapping = {"True": 1, "False": 0}
|
boolean_value_mapping = {"True": 1, "False": 0}
|
||||||
|
|
||||||
for node in grid_data:
|
for node in grid_data:
|
||||||
node_name = node.get('name')
|
# node_name = node.get('name')
|
||||||
node_ids.append(node.get('id'))
|
node_ids.append(node.get('id'))
|
||||||
|
node_id = node.get('id')
|
||||||
|
|
||||||
criteria_data = {}
|
criteria_data = {}
|
||||||
for criterion in node.get('criteria', []):
|
for criterion in node.get('criteria', []):
|
||||||
@ -177,7 +183,7 @@ def transform_grid_data_to_table(json_data):
|
|||||||
# Handle or log the error for values that can't be converted to float
|
# Handle or log the error for values that can't be converted to float
|
||||||
pass
|
pass
|
||||||
|
|
||||||
temp_data_table[node_name] = criteria_data
|
temp_data_table[node_id] = criteria_data
|
||||||
|
|
||||||
# Collect all criteria titles
|
# Collect all criteria titles
|
||||||
criteria_titles.extend(criteria_data.keys())
|
criteria_titles.extend(criteria_data.keys())
|
||||||
@ -189,7 +195,7 @@ def transform_grid_data_to_table(json_data):
|
|||||||
data_table = {title: [] for title in criteria_titles}
|
data_table = {title: [] for title in criteria_titles}
|
||||||
|
|
||||||
# Populate the final data table
|
# Populate the final data table
|
||||||
for node_name, criteria_data in temp_data_table.items():
|
for node_id, criteria_data in temp_data_table.items():
|
||||||
for title, value in criteria_data.items():
|
for title, value in criteria_data.items():
|
||||||
data_table[title].append(value)
|
data_table[title].append(value)
|
||||||
|
|
||||||
@ -254,64 +260,6 @@ def check_json_file_exists(app_id):
|
|||||||
|
|
||||||
return os.path.exists(file_path)
|
return os.path.exists(file_path)
|
||||||
|
|
||||||
|
|
||||||
# Used to read ALL the saved Data for an Application
|
|
||||||
# def read_application_data(app_id):
|
|
||||||
# # Directory path and file path
|
|
||||||
# app_dir = os.path.join("app_dirs", app_id)
|
|
||||||
# file_path = os.path.join(app_dir, "data.json")
|
|
||||||
#
|
|
||||||
# # Check if the file exists
|
|
||||||
# if os.path.exists(file_path):
|
|
||||||
# # Read and parse the JSON file
|
|
||||||
# with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
# data = json.load(f)
|
|
||||||
# # Extract specific parts of the data
|
|
||||||
# # selected_criteria = data.get("selectedCriteria", None)
|
|
||||||
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = transform_grid_data_to_table(data)
|
|
||||||
# else:
|
|
||||||
# print(f"No data found for application ID {app_id}.") # Return everything empty
|
|
||||||
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = [], [], [], [], []
|
|
||||||
#
|
|
||||||
# return data_table, relative_wr_data, immediate_wr_data, node_names, node_ids
|
|
||||||
|
|
||||||
# Used to read the saved Data of the Application ONLY for the Nodes returned by SAL
|
|
||||||
def read_application_data(app_id, node_ids_SAL):
|
|
||||||
# Directory path and file path
|
|
||||||
app_dir = os.path.join("app_dirs", app_id)
|
|
||||||
file_path = os.path.join(app_dir, f"{app_id}_data.json")
|
|
||||||
|
|
||||||
# Initialize variables to return in case of no data or an error
|
|
||||||
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = [], [], [], [], []
|
|
||||||
|
|
||||||
# Check if the file exists
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
# Read and parse the JSON file
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Filter gridData based on node_ids_SAL
|
|
||||||
filtered_grid_data = [node for node in data['gridData'] if node['id'] in node_ids_SAL]
|
|
||||||
|
|
||||||
# Create a new JSON structure with filtered gridData
|
|
||||||
filtered_json_data = {
|
|
||||||
"gridData": filtered_grid_data,
|
|
||||||
"relativeWRData": data['relativeWRData'],
|
|
||||||
"immediateWRData": data['immediateWRData'],
|
|
||||||
"nodeNames": [node['name'] for node in filtered_grid_data], # Assuming you want to filter nodeNames as well
|
|
||||||
"nodeIds": node_ids_SAL # Assuming you want to include nodeIds from the filtered list
|
|
||||||
}
|
|
||||||
|
|
||||||
# Call transform_grid_data_to_table with the new filtered JSON data
|
|
||||||
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = transform_grid_data_to_table(filtered_json_data)
|
|
||||||
else:
|
|
||||||
print(f"No data found for application ID {app_id}.")
|
|
||||||
|
|
||||||
return data_table, relative_wr_data, immediate_wr_data, node_names
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Used to create data table from SAL's response in app_side
|
|
||||||
def create_data_table(selected_criteria, extracted_data, field_mapping):
|
def create_data_table(selected_criteria, extracted_data, field_mapping):
|
||||||
# Initialize the data table with lists for each criterion
|
# Initialize the data table with lists for each criterion
|
||||||
data_table = {criterion: [] for criterion in selected_criteria}
|
data_table = {criterion: [] for criterion in selected_criteria}
|
||||||
@ -339,59 +287,23 @@ def create_data_table(selected_criteria, extracted_data, field_mapping):
|
|||||||
|
|
||||||
return data_table
|
return data_table
|
||||||
|
|
||||||
|
# Used to Append "Score" and "Rank" for each node in SAL's response JSON
|
||||||
|
|
||||||
import random
|
|
||||||
|
|
||||||
# def append_evaluation_results(sal_reply_body, scores_and_ranks):
|
|
||||||
# # Check if sal_reply_body is a string and convert it to a Python object
|
|
||||||
# if isinstance(sal_reply_body, str):
|
|
||||||
# sal_reply_body = json.loads(sal_reply_body)
|
|
||||||
#
|
|
||||||
# if scores_and_ranks:
|
|
||||||
# # Create a dictionary mapping Ids to scores and ranks
|
|
||||||
# eval_results_dict = {result['Id']: (result['DEA Score'], result['Rank'])
|
|
||||||
# for result in scores_and_ranks}
|
|
||||||
#
|
|
||||||
# # Iterate over each node in sal_reply_body and append Score and Rank
|
|
||||||
# for node in sal_reply_body:
|
|
||||||
# node_id = node.get('id') # Assuming the ID is directly under the node
|
|
||||||
# if node_id in eval_results_dict:
|
|
||||||
# score, rank = eval_results_dict[node_id]
|
|
||||||
# node["score"] = score
|
|
||||||
# node["rank"] = rank
|
|
||||||
# else:
|
|
||||||
# # If scores_and_ranks is empty
|
|
||||||
# for index, node in enumerate(sal_reply_body):
|
|
||||||
# if index == 0:
|
|
||||||
# # First node gets a score of 1 and rank of 1
|
|
||||||
# node["score"] = 1
|
|
||||||
# node["rank"] = 1
|
|
||||||
# else:
|
|
||||||
# # Assign random scores between 0.33 and 0.93 to the rest
|
|
||||||
# node["score"] = random.uniform(0.33, 0.93)
|
|
||||||
#
|
|
||||||
# # Sort nodes by score in descending order to calculate ranks
|
|
||||||
# sorted_nodes = sorted(sal_reply_body[1:], key=lambda x: x["score"], reverse=True)
|
|
||||||
#
|
|
||||||
# # Assign ranks based on sorted order, starting from 2 since the first node is ranked 1
|
|
||||||
# for rank, node in enumerate(sorted_nodes, start=2):
|
|
||||||
# node["rank"] = rank
|
|
||||||
#
|
|
||||||
# # Combine the first node with the rest
|
|
||||||
# sal_reply_body = [sal_reply_body[0]] + sorted_nodes
|
|
||||||
#
|
|
||||||
# return sal_reply_body
|
|
||||||
|
|
||||||
|
|
||||||
def append_evaluation_results(sal_reply_body, scores_and_ranks):
|
def append_evaluation_results(sal_reply_body, scores_and_ranks):
|
||||||
# Check if sal_reply_body is a string and convert it to a Python object
|
# Check if sal_reply_body is a string and convert it to a Python object
|
||||||
if isinstance(sal_reply_body, str):
|
if isinstance(sal_reply_body, str):
|
||||||
sal_reply_body = json.loads(sal_reply_body)
|
sal_reply_body = json.loads(sal_reply_body)
|
||||||
|
|
||||||
|
# Check if there is only one node and scores_and_ranks are empty
|
||||||
|
if len(sal_reply_body) == 1 and not scores_and_ranks:
|
||||||
|
# Directly assign score and rank to the single node
|
||||||
|
sal_reply_body[0]["score"] = 1
|
||||||
|
sal_reply_body[0]["rank"] = 1
|
||||||
|
return sal_reply_body
|
||||||
|
|
||||||
|
# Proceed if there are multiple nodes or scores_and_ranks is not empty
|
||||||
# Create a dictionary mapping Ids to scores and ranks
|
# Create a dictionary mapping Ids to scores and ranks
|
||||||
eval_results_dict = {result['Id']: (result['DEA Score'], result['Rank'])
|
eval_results_dict = {result['Id']: (result['DEA Score'], result['Rank'])
|
||||||
for result in scores_and_ranks}
|
for result in scores_and_ranks if scores_and_ranks}
|
||||||
|
|
||||||
# Iterate over each node in sal_reply_body and append Score and Rank
|
# Iterate over each node in sal_reply_body and append Score and Rank
|
||||||
for node in sal_reply_body:
|
for node in sal_reply_body:
|
||||||
@ -404,37 +316,387 @@ def append_evaluation_results(sal_reply_body, scores_and_ranks):
|
|||||||
return sal_reply_body
|
return sal_reply_body
|
||||||
|
|
||||||
|
|
||||||
|
def convert_value(value, criterion_info, is_matched):
|
||||||
|
if criterion_info['type'] == 5: # Boolean type
|
||||||
|
return 1 if value else 0
|
||||||
|
elif criterion_info['type'] == 1: # Ordinal type
|
||||||
|
if is_matched: # For matched nodes, use the mapping
|
||||||
|
ordinal_value_mapping = {"High": 3, "Medium": 2, "Low": 1}
|
||||||
|
return ordinal_value_mapping.get(value, value) # Use the value from mapping, or keep it as is if not found
|
||||||
|
else: # For unmatched nodes, assign default value
|
||||||
|
return 1
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# Used to read the saved application data CFSB when triggered by Optimizer
|
||||||
|
def read_application_data(app_id, sal_reply_body):
|
||||||
|
app_dir = os.path.join("app_dirs", app_id)
|
||||||
|
file_path = os.path.join(app_dir, f"{app_id}_data.json")
|
||||||
|
|
||||||
|
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = {}, [], [], [], []
|
||||||
|
|
||||||
|
if isinstance(sal_reply_body, str):
|
||||||
|
sal_reply_body = json.loads(sal_reply_body)
|
||||||
|
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
print(f"JSON file found for application ID {app_id}.")
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
selected_criteria = {criterion['title']: criterion for criterion in data.get('selectedCriteria', [])}
|
||||||
|
|
||||||
|
# Define the default list criteria mapping
|
||||||
|
default_list_criteria_mapping = {
|
||||||
|
"Operating cost": "price",
|
||||||
|
"Memory Price": "memoryPrice",
|
||||||
|
"Number of CPU Cores": "cores",
|
||||||
|
"Memory Size": "ram",
|
||||||
|
"Storage Capacity": "disk"
|
||||||
|
}
|
||||||
|
|
||||||
|
for criterion in selected_criteria:
|
||||||
|
data_table[criterion] = []
|
||||||
|
|
||||||
|
matched_node_ids = [node['id'] for node in data.get('gridData', []) if node['id'] in [n['id'] for n in sal_reply_body]]
|
||||||
|
unmatched_node_ids = [n['id'] for n in sal_reply_body if n['id'] not in matched_node_ids]
|
||||||
|
|
||||||
|
# Process MATCHED nodes
|
||||||
|
for node in data.get('gridData', []):
|
||||||
|
if node['id'] in matched_node_ids:
|
||||||
|
node_ids.append(node['id'])
|
||||||
|
# node_names.append(node.get('name', 'Unknown'))
|
||||||
|
for crit, criterion_info in selected_criteria.items():
|
||||||
|
value = next((criterion['value'] for criterion in node['criteria'] if criterion['title'] == crit), None)
|
||||||
|
converted_value = convert_value(value, criterion_info, is_matched=True)
|
||||||
|
data_table[crit].append(converted_value)
|
||||||
|
|
||||||
|
# Process UNMATCHED nodes
|
||||||
|
for node_id in unmatched_node_ids:
|
||||||
|
node_data = next((node for node in sal_reply_body if node['id'] == node_id), {})
|
||||||
|
node_ids.append(node_id)
|
||||||
|
for criterion, crit_info in selected_criteria.items():
|
||||||
|
mapped_field = default_list_criteria_mapping.get(criterion, '')
|
||||||
|
value = node_data.get(mapped_field, 0.001 if crit_info['type'] == 2 else False)
|
||||||
|
converted_value = convert_value(value, crit_info, is_matched=False)
|
||||||
|
data_table[criterion].append(converted_value)
|
||||||
|
|
||||||
|
node_names = node_ids
|
||||||
|
relative_wr_data, immediate_wr_data = data.get('relativeWRData', []), data.get('immediateWRData', [])
|
||||||
|
|
||||||
|
else: # There is not any node id match - Proceed only with the nodes from SAL's reply
|
||||||
|
print(f"No JSON file found for application ID {app_id}. Proceed only with data from SAL.")
|
||||||
|
extracted_data_SAL, node_ids_SAL, node_names_SAL = extract_SAL_node_candidate_data(sal_reply_body)
|
||||||
|
selected_criteria = ["Number of CPU Cores", "Memory Size"]
|
||||||
|
field_mapping = create_criteria_mapping()
|
||||||
|
data_table = create_data_table(selected_criteria, extracted_data_SAL, field_mapping)
|
||||||
|
# Assign relativeWRData and immediateWRData regardless of node ID matches
|
||||||
|
relative_wr_data = []
|
||||||
|
immediate_wr_data = []
|
||||||
|
node_ids = node_ids_SAL
|
||||||
|
node_names = node_ids
|
||||||
|
|
||||||
|
return data_table, relative_wr_data, immediate_wr_data, node_names, node_ids
|
||||||
|
|
||||||
|
|
||||||
|
# Used to generate random values for DataGrid
|
||||||
|
def random_value_based_on_type(data_type, criterion_info=None):
|
||||||
|
if data_type == 1: # Ordinal
|
||||||
|
# Assuming 'values' are part of criterion_info for ordinal types
|
||||||
|
return random.choice(criterion_info.get('values', ["High", "Medium", "Low"]))
|
||||||
|
elif data_type == 5: # Boolean
|
||||||
|
return random.choice([True, False])
|
||||||
|
else: # Numeric
|
||||||
|
# Default case for numeric types
|
||||||
|
return round(random.uniform(1, 100), 2)
|
||||||
|
|
||||||
|
|
||||||
|
# Used to parse Patini's JSON
|
||||||
|
def parse_device_info_from_file(file_path):
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
json_data = json.load(file)
|
||||||
|
device_names = []
|
||||||
|
device_info = {
|
||||||
|
'id': json_data['_id'],
|
||||||
|
'name': json_data['name'], # Save the device name
|
||||||
|
'deviceInfo': json_data['deviceInfo'],
|
||||||
|
'creationDate': json_data['creationDate'],
|
||||||
|
'lastUpdateDate': json_data['lastUpdateDate'],
|
||||||
|
'status': json_data['status'],
|
||||||
|
'metrics': {
|
||||||
|
'cpu': json_data['metrics']['metrics']['cpu'],
|
||||||
|
'uptime': json_data['metrics']['metrics']['uptime'],
|
||||||
|
'disk': json_data['metrics']['metrics']['disk'],
|
||||||
|
'ram': json_data['metrics']['metrics']['ram']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Example of converting and handling ISODate strings, adjust accordingly
|
||||||
|
device_info['creationDate'] = datetime.fromisoformat(device_info['creationDate'].replace("ISODate('", "").replace("')", ""))
|
||||||
|
device_info['lastUpdateDate'] = datetime.fromisoformat(device_info['lastUpdateDate'].replace("ISODate('", "").replace("')", ""))
|
||||||
|
device_info['creationDate'] = device_info['creationDate'].isoformat()
|
||||||
|
device_info['lastUpdateDate'] = device_info['lastUpdateDate'].isoformat()
|
||||||
|
|
||||||
|
# Update the global device_names list
|
||||||
|
device_names.append({'id': device_info['id'], 'name': device_info['name']})
|
||||||
|
return device_names, device_info
|
||||||
|
|
||||||
|
|
||||||
|
#---------------Read Application Data
|
||||||
|
|
||||||
|
# Used to read the saved Data of the Application ONLY for the Nodes returned by SAL
|
||||||
|
# def read_application_data(app_id, sal_reply_body):
|
||||||
|
# # Directory path and file path
|
||||||
|
# app_dir = os.path.join("app_dirs", app_id)
|
||||||
# Example usage
|
# file_path = os.path.join(app_dir, f"{app_id}_data.json")
|
||||||
# extracted_data, NUMBER_OF_FOG_NODES, node_names = extract_node_candidate_data('dummy_data_node_candidates.json')
|
|
||||||
# print(NUMBER_OF_FOG_NODES)
|
|
||||||
# print(node_names)
|
|
||||||
|
|
||||||
# app_id = 'd535cf554ea66fbebfc415ac837a5828'
|
|
||||||
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = read_app_specific_data(app_id)
|
|
||||||
#
|
#
|
||||||
# print("Node Names:", node_names)
|
# # Initialize variables to return in case of no data or an error
|
||||||
# print("data_table:", data_table)
|
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = [], [], [], [], []
|
||||||
# print("Relative WR Data:", relative_wr_data)
|
# # Read data from SAL's reply
|
||||||
# print("Immediate WR Data:", immediate_wr_data)
|
# extracted_data_SAL, node_ids_SAL, node_names_SAL = extract_SAL_node_candidate_data(sal_reply_body)
|
||||||
#
|
#
|
||||||
# evaluation_results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids)
|
# # Check if the file exists
|
||||||
# print("evaluation_results:", evaluation_results)
|
# if os.path.exists(file_path):
|
||||||
|
# # Read and parse the JSON file
|
||||||
|
# with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
# data = json.load(f)
|
||||||
#
|
#
|
||||||
# # Extracting the results and saving them into a variable
|
# # Filter gridData based on Nodes returned by SAL
|
||||||
# ScoresAndRanks = evaluation_results['results']
|
# filtered_grid_data = [node for node in data.get('gridData', []) if node.get('id') in node_ids_SAL]
|
||||||
# print("ScoresAndRanks:", ScoresAndRanks)
|
#
|
||||||
|
# if filtered_grid_data: # if there's at least 1 match
|
||||||
|
# # Create a new JSON structure and call transform_grid_data_to_table
|
||||||
|
# filtered_json_data = {
|
||||||
|
# "gridData": filtered_grid_data,
|
||||||
|
# "relativeWRData": relative_wr_data,
|
||||||
|
# "immediateWRData": immediate_wr_data,
|
||||||
|
# "nodeNames": [node.get('name') for node in filtered_grid_data],
|
||||||
|
# "nodeIds": node_ids_SAL
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# # Call transform_grid_data_to_table with the filtered JSON data
|
||||||
|
# # data_table, _, _, node_names, _ = transform_grid_data_to_table(filtered_json_data)
|
||||||
|
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = transform_grid_data_to_table(filtered_json_data)
|
||||||
|
# if not node_names:
|
||||||
|
# node_names = node_ids
|
||||||
|
#
|
||||||
|
# else: # There is not any node id match - Proceed only with the nodes from SAL's reply
|
||||||
|
# print("No matching node IDs found in the saved data. Proceed only with data from SAL")
|
||||||
|
# selected_criteria = ["Number of CPU Cores", "Memory Size"]
|
||||||
|
# field_mapping = create_criteria_mapping(selected_criteria, extracted_data_SAL)
|
||||||
|
# data_table = create_data_table(selected_criteria, extracted_data_SAL, field_mapping)
|
||||||
|
# # Assign relativeWRData and immediateWRData regardless of node ID matches
|
||||||
|
# relative_wr_data = []
|
||||||
|
# immediate_wr_data = []
|
||||||
|
# node_ids = node_ids_SAL
|
||||||
|
# node_names = node_ids
|
||||||
|
# if not node_names_SAL:
|
||||||
|
# node_names = node_ids
|
||||||
|
# else:
|
||||||
|
# print(f"No JSON file found for application ID {app_id}.")
|
||||||
|
#
|
||||||
|
# # Note: relative_wr_data and immediate_wr_data are returned regardless of the node IDs match
|
||||||
|
# return data_table, relative_wr_data, immediate_wr_data, node_names, node_ids
|
||||||
|
|
||||||
# append_evaluation_results('SAL_Response_11EdgeDevs.json', ScoresAndRanks)
|
|
||||||
|
|
||||||
|
#Used to create data table from SAL's response in app_side
|
||||||
|
|
||||||
|
# def read_application_data(app_id, sal_reply_body):
|
||||||
|
# app_dir = os.path.join("app_dirs", app_id)
|
||||||
|
# file_path = os.path.join(app_dir, f"{app_id}_data.json")
|
||||||
|
# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = {}, [], [], [], []
|
||||||
|
#
|
||||||
|
# default_list_criteria_mapping = {
|
||||||
|
# "Operating cost": "price",
|
||||||
|
# "Memory Price": "memoryPrice",
|
||||||
|
# "Number of CPU Cores": "cores",
|
||||||
|
# "Memory Size": "ram",
|
||||||
|
# "Storage Capacity": "disk"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# if isinstance(sal_reply_body, str):
|
||||||
|
# try:
|
||||||
|
# sal_reply_body = json.loads(sal_reply_body)
|
||||||
|
# except json.JSONDecodeError as e:
|
||||||
|
# print(f"Error parsing JSON: {e}")
|
||||||
|
# return data_table, relative_wr_data, immediate_wr_data, node_names, node_ids
|
||||||
|
#
|
||||||
|
# if os.path.exists(file_path):
|
||||||
|
# with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
# data = json.load(f)
|
||||||
|
# selected_criteria = {criterion['title']: criterion for criterion in data.get('selectedCriteria', [])}
|
||||||
|
#
|
||||||
|
# for criterion in selected_criteria.keys():
|
||||||
|
# data_table[criterion] = []
|
||||||
|
#
|
||||||
|
# matched_node_ids = set(node['id'] for node in data.get('gridData', [])) & set(node['id'] for node in sal_reply_body)
|
||||||
|
# unmatched_node_ids = set(node['id'] for node in sal_reply_body) - matched_node_ids
|
||||||
|
#
|
||||||
|
# # Ordinal value mapping for MATCHED nodes
|
||||||
|
# ordinal_value_mapping = {"High": 3, "Medium": 2, "Low": 1}
|
||||||
|
#
|
||||||
|
# # Process MATCHED nodes from JSON file
|
||||||
|
# for node in data.get('gridData', []):
|
||||||
|
# if node['id'] in matched_node_ids:
|
||||||
|
# node_ids.append(node['id'])
|
||||||
|
# # node_names.append(node.get('name', 'Unknown'))
|
||||||
|
# for criterion, crit_info in selected_criteria.items():
|
||||||
|
# value = next((c['value'] for c in node['criteria'] if c['title'] == criterion), None)
|
||||||
|
# if value is not None:
|
||||||
|
# value = 1 if value is True else (0 if value is False else value)
|
||||||
|
# else: # Apply default if criterion not found
|
||||||
|
# value = 0.00001 if crit_info['type'] == 2 else 0
|
||||||
|
# data_table[criterion].append(value)
|
||||||
|
#
|
||||||
|
# # Process UNMATCHED nodes from sal_reply_body
|
||||||
|
# for node_id in unmatched_node_ids:
|
||||||
|
# node_data = next((node for node in sal_reply_body if node['id'] == node_id), {})
|
||||||
|
# node_ids.append(node_id)
|
||||||
|
# for criterion, crit_info in selected_criteria.items():
|
||||||
|
# mapped_field = default_list_criteria_mapping.get(criterion, '')
|
||||||
|
# value = node_data.get(mapped_field, 0.00001 if crit_info['type'] == 2 else False)
|
||||||
|
# value = 1 if value is True else (0 if value is False else value)
|
||||||
|
# data_table[criterion].append(value)
|
||||||
|
#
|
||||||
|
# # convert True/False to 1/0 in data_table for both boolean and string representations
|
||||||
|
# for criterion, values in data_table.items():
|
||||||
|
# data_table[criterion] = [convert_bool(value) for value in values]
|
||||||
|
# node_names = node_ids
|
||||||
|
# relative_wr_data, immediate_wr_data = data.get('relativeWRData', []), data.get('immediateWRData', [])
|
||||||
|
#
|
||||||
|
# else: # There is not any node id match - Proceed only with the nodes from SAL's reply
|
||||||
|
# print(f"No JSON file found for application ID {app_id}. Proceed only with data from SAL.")
|
||||||
|
# extracted_data_SAL, node_ids_SAL, node_names_SAL = extract_SAL_node_candidate_data(sal_reply_body)
|
||||||
|
# selected_criteria = ["Number of CPU Cores", "Memory Size"]
|
||||||
|
# field_mapping = create_criteria_mapping(selected_criteria, extracted_data_SAL)
|
||||||
|
# data_table = create_data_table(selected_criteria, extracted_data_SAL, field_mapping)
|
||||||
|
# # Assign relativeWRData and immediateWRData regardless of node ID matches
|
||||||
|
# relative_wr_data = []
|
||||||
|
# immediate_wr_data = []
|
||||||
|
# node_ids = node_ids_SAL
|
||||||
|
# node_names = node_ids
|
||||||
|
#
|
||||||
|
# return data_table, relative_wr_data, immediate_wr_data, node_names, node_ids
|
||||||
|
|
||||||
|
|
||||||
|
# Used to transform SAL's response before sending to DataGrid
|
||||||
|
# This version is designed to read the structure of SAL's response obtained from POSTMAN
|
||||||
|
def extract_node_candidate_data(json_file_path):
|
||||||
|
with open(json_file_path, 'r') as file:
|
||||||
|
json_data = json.load(file)
|
||||||
|
|
||||||
|
extracted_data = []
|
||||||
|
node_ids = []
|
||||||
|
node_names = []
|
||||||
|
|
||||||
|
for item in json_data:
|
||||||
|
hardware_info = item.get("nodeCandidate", {}).get("hardware", {})
|
||||||
|
node_data = {
|
||||||
|
"name": item['name'],
|
||||||
|
"id": item['id'],
|
||||||
|
"nodeId": item.get("nodeCandidate", {}).get("nodeId"),
|
||||||
|
"nodeCandidateType": item.get("nodeCandidate", {}).get("nodeCandidateType"),
|
||||||
|
"price": item.get("nodeCandidate", {}).get("price", 0.0),
|
||||||
|
"pricePerInvocation": item.get("nodeCandidate", {}).get("pricePerInvocation", 0.0),
|
||||||
|
"memoryPrice": item.get("nodeCandidate", {}).get("memoryPrice", 0.0),
|
||||||
|
"hardware": {
|
||||||
|
"id": hardware_info.get("id"),
|
||||||
|
"name": hardware_info.get("name"),
|
||||||
|
"providerId": hardware_info.get("providerId"),
|
||||||
|
"cores": hardware_info.get("cores"),
|
||||||
|
"ram": hardware_info.get("ram") * 1024 if hardware_info.get("ram") else None, # Assuming RAM needs conversion from GB to MB
|
||||||
|
"disk": hardware_info.get("disk"),
|
||||||
|
"fpga": hardware_info.get("fpga")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extracted_data.append(node_data)
|
||||||
|
node_ids.append(item['id'])
|
||||||
|
node_names.append(item.get('name', ''))
|
||||||
|
|
||||||
|
return extracted_data, node_ids, node_names
|
||||||
|
|
||||||
|
|
||||||
|
# Works for dummy_node_data
|
||||||
|
# def create_node_name(node_data):
|
||||||
|
# # dummy_node_data = '''{
|
||||||
|
# # "id": "8a7481d98e702b64018e702cbe070000",
|
||||||
|
# # "nodeCandidateType": "EDGE",
|
||||||
|
# # "jobIdForByon": null,
|
||||||
|
# # "jobIdForEdge": "FCRnewLight0",
|
||||||
|
# # "price": 0.0,
|
||||||
|
# # "cloud": {
|
||||||
|
# # "id": "edge",
|
||||||
|
# # "endpoint": null,
|
||||||
|
# # "cloudType": "EDGE",
|
||||||
|
# # "api": null,
|
||||||
|
# # "credential": null,
|
||||||
|
# # "cloudConfiguration": {
|
||||||
|
# # "nodeGroup": null,
|
||||||
|
# # "properties": {}
|
||||||
|
# # },
|
||||||
|
# # "owner": "EDGE",
|
||||||
|
# # "state": null,
|
||||||
|
# # "diagnostic": null
|
||||||
|
# # },
|
||||||
|
# # "location": {
|
||||||
|
# # "id": "edge-location-KmVf4xDJKL7acBGc",
|
||||||
|
# # "name": null,
|
||||||
|
# # "providerId": null,
|
||||||
|
# # "locationScope": null,
|
||||||
|
# # "isAssignable": null,
|
||||||
|
# # "geoLocation": {
|
||||||
|
# # "city": "Warsaw",
|
||||||
|
# # "country": "Poland",
|
||||||
|
# # "latitude": 52.237049,
|
||||||
|
# # "longitude": 21.017532
|
||||||
|
# # },
|
||||||
|
# # "parent": null,
|
||||||
|
# # "state": null,
|
||||||
|
# # "owner": null
|
||||||
|
# # },
|
||||||
|
# # "image": {
|
||||||
|
# # "id": "edge-image-KmVf4xDJKL7acBGc",
|
||||||
|
# # "name": "edge-image-name-UBUNTU-UNKNOWN",
|
||||||
|
# # "providerId": null,
|
||||||
|
# # "operatingSystem": {
|
||||||
|
# # "operatingSystemFamily": "UBUNTU",
|
||||||
|
# # "operatingSystemArchitecture": "UNKNOWN",
|
||||||
|
# # "operatingSystemVersion": 1804.00
|
||||||
|
# # },
|
||||||
|
# # "location": null,
|
||||||
|
# # "state": null,
|
||||||
|
# # "owner": null
|
||||||
|
# # },
|
||||||
|
# # "hardware": {
|
||||||
|
# # "id": "edge-hardware-KmVf4xDJKL7acBGc",
|
||||||
|
# # "name": null,
|
||||||
|
# # "providerId": null,
|
||||||
|
# # "cores": 1,
|
||||||
|
# # "ram": 1,
|
||||||
|
# # "disk": 1.0,
|
||||||
|
# # "fpga": 0,
|
||||||
|
# # "location": null,
|
||||||
|
# # "state": null,
|
||||||
|
# # "owner": null
|
||||||
|
# # },
|
||||||
|
# # "pricePerInvocation": 0.0,
|
||||||
|
# # "memoryPrice": 0.0,
|
||||||
|
# # "nodeId": null,
|
||||||
|
# # "environment": null
|
||||||
|
# # }'''
|
||||||
|
# # node_data = json.loads(dummy_node_data)
|
||||||
|
# # print("node_data in create node name")
|
||||||
|
# # print(node_data)
|
||||||
|
# node_type = node_data["nodeCandidateType"]
|
||||||
|
# # print(node_type)
|
||||||
|
# if node_data["location"]:
|
||||||
|
# node_location = node_data["location"]["geoLocation"]
|
||||||
|
# # print(json.dumps(node_location))
|
||||||
|
# node_city = node_location["city"]
|
||||||
|
# node_country = node_location["country"]
|
||||||
|
# else:
|
||||||
|
# node_city = ""
|
||||||
|
# node_country = ""
|
||||||
|
# node_os = node_data["image"]["operatingSystem"]["operatingSystemFamily"]
|
||||||
|
# node_name = node_type + " - " + node_city + " , " + node_country + " - " + node_os
|
||||||
|
# # print("node name crated: " + node_name)
|
||||||
|
# return node_name
|
@ -1,8 +1,6 @@
|
|||||||
# ActiveMQ communication logic
|
# ActiveMQ communication logic
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import json
|
|
||||||
import time
|
|
||||||
sys.path.insert(0,'../exn')
|
sys.path.insert(0,'../exn')
|
||||||
import logging
|
import logging
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@ -18,9 +16,10 @@ from exn.core.handler import Handler
|
|||||||
from exn.handler.connector_handler import ConnectorHandler
|
from exn.handler.connector_handler import ConnectorHandler
|
||||||
from User_Functions import *
|
from User_Functions import *
|
||||||
import uuid
|
import uuid
|
||||||
|
from Evaluation import perform_evaluation
|
||||||
|
|
||||||
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
# logging.getLogger('exn.connector').setLevel(logging.CRITICAL)
|
logging.getLogger('exn.connector').setLevel(logging.CRITICAL)
|
||||||
|
|
||||||
class SyncedHandler(Handler):
|
class SyncedHandler(Handler):
|
||||||
def on_message(self, key, address, body, message: Message, context=None):
|
def on_message(self, key, address, body, message: Message, context=None):
|
||||||
@ -34,77 +33,58 @@ class SyncedHandler(Handler):
|
|||||||
# logging.info("Entered in OPT-triggering'")
|
# logging.info("Entered in OPT-triggering'")
|
||||||
|
|
||||||
# Save the correlation_id (We do not have it from the app_side)
|
# Save the correlation_id (We do not have it from the app_side)
|
||||||
uuid.uuid4().hex.encode("utf-8") # for Correlation id
|
uuid.uuid4().hex.encode("utf-8") # for Correlation id
|
||||||
|
|
||||||
correlation_id_optimizer = message.correlation_id
|
correlation_id_optimizer = message.correlation_id
|
||||||
if not correlation_id_optimizer:
|
if not correlation_id_optimizer:
|
||||||
correlation_id_optimizer = '88334290cad34ad9b21eb468a9f8ff11' # dummy correlation_id
|
correlation_id_optimizer = '88334290cad34ad9b21eb468a9f8ff11' # dummy correlation_id
|
||||||
|
|
||||||
# logging.info(f"Optimizer_correlation_id {message.correlation_id}")
|
# logging.info(f"Optimizer_correlation_id {message.correlation_id}")
|
||||||
print("Optimizer Correlation Id: ", correlation_id_optimizer)
|
# print("Optimizer Correlation Id: ", correlation_id_optimizer)
|
||||||
|
|
||||||
# application_id_optimizer = message.properties.application # can be taken also from message.annotations.application
|
# application_id_optimizer = message.properties.application # can be taken also from message.annotations.application
|
||||||
application_id_optimizer = message.subject
|
application_id_optimizer = message.subject
|
||||||
# application_id_optimizer = 'd535cf554ea66fbebfc415ac837a5828' #dummy application_id_optimizer
|
# application_id_optimizer = 'd535cf554ea66fbebfc415ac837a5828' #dummy application_id_optimizer
|
||||||
print("Application Id: ", application_id_optimizer)
|
# print("Application Id: ", application_id_optimizer)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Read the Message Sent from Optimizer
|
# Read the Message Sent from Optimizer
|
||||||
opt_message_data = body
|
opt_message_data = body
|
||||||
print("Whole Message Sent from Optimizer:", opt_message_data)
|
# print("Whole Message Sent from Optimizer:", opt_message_data)
|
||||||
|
|
||||||
# Extract 'body' from opt_message_data
|
# Extract 'body' from opt_message_data
|
||||||
body_sent_from_optimizer = opt_message_data.get('body', {})
|
body_sent_from_optimizer = opt_message_data.get('body', {})
|
||||||
|
|
||||||
# 100 Nodes
|
## Example body
|
||||||
# body_sent_from_optimizer = [
|
# body_sent_from_optimizer = [
|
||||||
# {
|
# {
|
||||||
# "type": "NodeTypeRequirement",
|
# "type": "NodeTypeRequirement",
|
||||||
# "nodeTypes": ["IAAS"],
|
# # "nodeTypes": ["EDGES"]
|
||||||
# "jobIdForByon": "dummy-app-id",
|
# "nodeTypes": ["IAAS", "PAAS", "FAAS", "BYON", "EDGE", "SIMULATION"]
|
||||||
# "jobIdForEDGE": "dummy-app-id"
|
# # ,"jobIdForEDGE": "FCRnewLight0"
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
|
|
||||||
|
|
||||||
# 58 Nodes
|
|
||||||
# body_sent_from_optimizer = [
|
|
||||||
# {
|
|
||||||
# "type": "NodeTypeRequirement",
|
|
||||||
# "nodeTypes": ["IAAS"],
|
|
||||||
# "jobIdForByon": "dummy-app-id",
|
|
||||||
# "jobIdForEDGE": "dummy-app-id"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "type": "AttributeRequirement",
|
|
||||||
# "requirementClass": "hardware",
|
|
||||||
# "requirementAttribute": "cores",
|
|
||||||
# "requirementOperator": "EQ",
|
|
||||||
# "value": "2"
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "type": "AttributeRequirement",
|
|
||||||
# "requirementClass": "hardware",
|
|
||||||
# "requirementAttribute": "ram",
|
|
||||||
# "requirementOperator": "EQ",
|
|
||||||
# "value": "4096"
|
|
||||||
# }
|
# }
|
||||||
|
# # ,{
|
||||||
|
# # "type": "AttributeRequirement",
|
||||||
|
# # "requirementClass": "hardware",
|
||||||
|
# # "requirementAttribute": "ram",
|
||||||
|
# # "requirementOperator": "EQ",
|
||||||
|
# # "value": "2"
|
||||||
|
# # }
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
# logging.info(body_sent_from_optimizer)
|
# logging.info(body_sent_from_optimizer)
|
||||||
# print("Extracted body from Optimizer Message:", body_sent_from_optimizer)
|
print("Extracted body from Optimizer Message:", body_sent_from_optimizer)
|
||||||
|
|
||||||
## Prepare message to be send to SAL
|
## Prepare message to be send to SAL
|
||||||
# Convert the body data to a JSON string
|
# Convert the body data to a JSON string
|
||||||
|
# body_json_string = json.dumps(body_sent_from_optimizer) # For Sender
|
||||||
|
body_json_string = body_sent_from_optimizer # For Optimizer
|
||||||
|
|
||||||
# body_json_string = json.dumps(body_sent_from_optimizer)
|
|
||||||
body_json_string = body_sent_from_optimizer
|
|
||||||
RequestToSal = { # Dictionary
|
RequestToSal = { # Dictionary
|
||||||
"metaData": {"user": "admin"}, # key [String "metaData"] value [dictionary]
|
"metaData": {"user": "admin"}, # key [String "metaData"] value [dictionary]
|
||||||
"body": body_json_string # key [String "body"] value [JSON String]
|
"body": body_json_string # key [String "body"] value [JSON String]
|
||||||
}
|
}
|
||||||
# logging.info("RequestToSal: %s", RequestToSal)
|
# logging.info("RequestToSal: %s", RequestToSal)
|
||||||
print("RequestToSal:", RequestToSal)
|
# print("RequestToSal:", RequestToSal)
|
||||||
|
|
||||||
# print("Is RequestToSal a valid dictionary:", isinstance(RequestToSal, dict))
|
# print("Is RequestToSal a valid dictionary:", isinstance(RequestToSal, dict))
|
||||||
# print("Is the 'body' string in RequestToSal a valid JSON string:", is_json(RequestToSal["body"]))
|
# print("Is the 'body' string in RequestToSal a valid JSON string:", is_json(RequestToSal["body"]))
|
||||||
|
|
||||||
@ -112,128 +92,106 @@ class SyncedHandler(Handler):
|
|||||||
sal_reply = context.publishers['SAL-GET'].send_sync(RequestToSal)
|
sal_reply = context.publishers['SAL-GET'].send_sync(RequestToSal)
|
||||||
|
|
||||||
## Process SAL's Reply
|
## Process SAL's Reply
|
||||||
# sal_reply_body = sal_reply.get('body')
|
|
||||||
sal_body = sal_reply.get('body') # Get the 'body' as a JSON string
|
sal_body = sal_reply.get('body') # Get the 'body' as a JSON string
|
||||||
|
|
||||||
# try:
|
|
||||||
# # Parse the JSON string to a Python object
|
|
||||||
# nodes_data = json.loads(sal_body)
|
|
||||||
# total_nodes = len(nodes_data) # Get the total number of nodes
|
|
||||||
#
|
|
||||||
# # Check if more than 51 nodes exist
|
|
||||||
# if total_nodes > 58:
|
|
||||||
# print("More than 58 nodes exist. Only the first 51 nodes will be processed.")
|
|
||||||
# # Filter to only include the first 51 nodes
|
|
||||||
# sal_reply_body = nodes_data[:60]
|
|
||||||
# else:
|
|
||||||
# print(f"Total {total_nodes} nodes found. Processing all nodes.")
|
|
||||||
# sal_reply_body = sal_reply.get('body')
|
|
||||||
#
|
|
||||||
# except json.JSONDecodeError as e:
|
|
||||||
# print(f"Error parsing JSON: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
# filename = 'SAL_Response_10EdgeDevs.json'
|
|
||||||
# with open(filename, 'r') as file:
|
|
||||||
# sal_reply_body = json.load(file)
|
|
||||||
# print("SAL's Reply from JSON File:", sal_reply_body)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Parse the JSON string to a Python object
|
# Parse the JSON string to a Python object
|
||||||
nodes_data = json.loads(sal_body)
|
nodes_data = json.loads(sal_body)
|
||||||
total_nodes = len(nodes_data) # Get the total number of nodes
|
# Check if there is any error in SAL's reply body
|
||||||
|
if 'key' in nodes_data and any(keyword in nodes_data['key'].lower() for keyword in ['error', 'exception']):
|
||||||
|
print("Error found in message body:", nodes_data['message'])
|
||||||
|
sal_reply_body = []
|
||||||
|
else: # No error found in SAL's reply body
|
||||||
|
total_nodes = len(nodes_data) # Get the total number of nodes
|
||||||
|
print("Total Nodes in SAL's reply:", total_nodes)
|
||||||
|
|
||||||
# Check if more than 58 nodes exist
|
if total_nodes > 400: # Check if more than 400 nodes received
|
||||||
if total_nodes > 400:
|
print("More than 400 nodes returned from SAL.")
|
||||||
print("More than 58 nodes exist. Only the first 51 nodes will be processed.")
|
# Filter to only include the first 400 nodes and convert back to JSON string
|
||||||
# Filter to only include the first 51 nodes and convert back to JSON string
|
sal_reply_body = json.dumps(nodes_data[:400])
|
||||||
sal_reply_body = json.dumps(nodes_data[:400])
|
elif total_nodes > 0 and total_nodes <= 400:
|
||||||
else:
|
print(f"Total {total_nodes} nodes returned from SAL. Processing all nodes.")
|
||||||
print(f"Total {total_nodes} nodes found. Processing all nodes.")
|
# Keep sal_reply_body as is since it's already a JSON string
|
||||||
# Keep sal_reply_body as is since it's already a JSON string
|
sal_reply_body = sal_body
|
||||||
sal_reply_body = sal_body
|
else:
|
||||||
|
print(f"Total {total_nodes} nodes returned from SAL.")
|
||||||
|
sal_reply_body = []
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"Error parsing JSON: {e}")
|
print(f"Error parsing JSON reply from SAL: {e}")
|
||||||
sal_reply_body = "[]" # Default to an empty JSON array as a string in case of error
|
sal_reply_body = [] # Default to an empty JSON array as a string in case of error
|
||||||
|
|
||||||
if sal_reply_body: # Check whether SAL's reply body is empty
|
if sal_reply_body: # Check whether SAL's reply body is empty
|
||||||
# logging.info(f"Whole reply Received from SAL: {sal_reply}")
|
# logging.info(f"Reply Received from SAL: {sal_reply}")
|
||||||
# print("SAL reply Body:", sal_reply_body)
|
# print("SAL reply Body:", sal_reply_body)
|
||||||
|
|
||||||
# Search for application_id, Read JSON and create data to pass to Evaluation
|
|
||||||
if check_json_file_exists(application_id_optimizer): # Application JSON exist in DB
|
|
||||||
print(f"JSON file for application ID {application_id_optimizer} exists.")
|
|
||||||
node_ids = extract_SAL_node_candidate_data(sal_reply)[2] # 0,1,2nd Position returns the function
|
|
||||||
# node_ids = ['8a7482868df473cc018df47d8ea60003', '8a7482868df473cc018df47d8fc70005', '8a7482868df473cc018df47d90e70007', '8a7482868df473cc018df47d92090009', '8a7482868df473cc018df47d9326000b', '8a7482868df473cc018df47d9445000d', '8a7482868df473cc018df47d957f000f', '8a7482868df473cc018df47d96a50011', '8a7482868df473cc018df47d97c70013', '8a7482868df473cc018df47d98e30015']
|
|
||||||
# print("node_ids_SAL:", node_ids_SAL)
|
|
||||||
|
|
||||||
# Check if there is any difference in available nodes between saved data in DB and SAL's reply
|
|
||||||
data_table, relative_wr_data, immediate_wr_data, node_names = read_application_data(application_id_optimizer, node_ids)
|
|
||||||
if not node_names:
|
|
||||||
node_names = node_ids
|
|
||||||
print("data_table filtered from DB:", data_table)
|
|
||||||
print("node_ids filtered from DB:", node_ids)
|
|
||||||
print("node_names filtered from DB:", node_names)
|
|
||||||
|
|
||||||
# I need to use the most updated data for nodes sent from SAL,
|
|
||||||
# I can modify the function to retrieve only WR info but there is a problem if other criteria are used
|
|
||||||
# Maybe I have to use the new data only for the criteria with data coming from SAL and the saved ones for the
|
|
||||||
# rest criteria
|
|
||||||
# In case a new node sent from SAL which I have not data saved, then do not consider it if also other crieria
|
|
||||||
# exist rather than the ones
|
|
||||||
|
|
||||||
else: # Application JSON does not exist in DB
|
|
||||||
print(f"JSON file for application ID {application_id_optimizer} does not exist.")
|
|
||||||
# Read data from SAL's response by calling the function extract_node_candidate_data()
|
|
||||||
# extracted_data, number_of_nodes, node_ids, node_names = extract_node_candidate_data('SAL_Response_11EdgeDevs.json')
|
|
||||||
extracted_data, number_of_nodes, node_ids, node_names = extract_SAL_node_candidate_data(sal_reply_body)
|
|
||||||
# print("extracted_data:", extracted_data)
|
|
||||||
print("node_ids:", node_ids)
|
|
||||||
|
|
||||||
# Use the create_criteria_mapping() to get the criteria mappings
|
|
||||||
# selected_criteria = ["Operating cost", "Memory Price", "Number of CPU Cores", "Memory Size", "Storage Capacity"]
|
|
||||||
selected_criteria = ["Number of CPU Cores", "Memory Size"]
|
|
||||||
field_mapping = create_criteria_mapping(selected_criteria, extracted_data)
|
|
||||||
# Create data_table:
|
|
||||||
data_table = create_data_table(selected_criteria, extracted_data, field_mapping)
|
|
||||||
relative_wr_data = []
|
|
||||||
immediate_wr_data = []
|
|
||||||
print("created_data_table:", data_table)
|
|
||||||
|
|
||||||
# Check the number of nodes before Evaluation
|
# Check the number of nodes before Evaluation
|
||||||
print("There are " + str(len(node_ids)) + " elements in node_ids")
|
if total_nodes > 1:
|
||||||
|
# Search for application_id, Read JSON and create data to pass to Evaluation
|
||||||
|
if check_json_file_exists(application_id_optimizer): # Application JSON exist in DB
|
||||||
|
print(f"JSON file for application ID {application_id_optimizer} exists.")
|
||||||
|
# Check if there are differences in available nodes between saved data in JSON file and SAL's reply
|
||||||
|
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = read_application_data(application_id_optimizer, sal_reply_body)
|
||||||
|
# print("sal_reply_body:", sal_reply_body)
|
||||||
|
# print("data_table filtered from JSON and SAL:", data_table)
|
||||||
|
# print("node_ids filtered from JSON and SAL:", node_ids)
|
||||||
|
# print("relative_wr_data:", relative_wr_data)
|
||||||
|
# print("immediate_wr_data:", immediate_wr_data)
|
||||||
|
# print("node_names filtered from JSON and SAL:", node_names)
|
||||||
|
|
||||||
## Run evaluation
|
else: # Application does not exist in directory
|
||||||
evaluation_results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids)
|
print(f"JSON file for application ID {application_id_optimizer} does not exist.")
|
||||||
# print("Evaluation Results:", evaluation_results)
|
# Read data from SAL's response by calling the function extract_node_candidate_data()
|
||||||
|
# extracted_data_SAL, node_ids, node_names = extract_node_candidate_data('SAL_Response_11EdgeDevs.json')
|
||||||
|
extracted_data_SAL, node_ids, node_names = extract_SAL_node_candidate_data(sal_reply_body)
|
||||||
|
# print("extracted_data_SAL:", extracted_data_SAL)
|
||||||
|
# print("node_ids:", node_ids)
|
||||||
|
|
||||||
## Extract and save the results
|
# Use the create_criteria_mapping() to get the criteria mappings
|
||||||
# ScoresAndRanks = evaluation_results['results']
|
# selected_criteria = ["Operating cost", "Memory Price", "Number of CPU Cores", "Memory Size", "Storage Capacity"]
|
||||||
ScoresAndRanks = evaluation_results.get('results', [])
|
selected_criteria = ["Number of CPU Cores", "Memory Size"]
|
||||||
print("Scores and Ranks:", ScoresAndRanks)
|
field_mapping = create_criteria_mapping()
|
||||||
|
# Create data_table:
|
||||||
|
data_table = create_data_table(selected_criteria, extracted_data_SAL, field_mapping)
|
||||||
|
relative_wr_data = []
|
||||||
|
immediate_wr_data = []
|
||||||
|
# print("created_data_table:", data_table)
|
||||||
|
|
||||||
# Append the Score and Rank of each node to SAL's Response
|
# Check the number of nodes before Evaluation
|
||||||
SAL_and_Scores_Body = append_evaluation_results(sal_reply_body, ScoresAndRanks)
|
print("There are " + str(len(node_ids)) + " nodes for Evaluation")
|
||||||
# SAL_and_Scores_Body = append_evaluation_results('SAL_Response_11EdgeDevs.json', ScoresAndRanks)
|
|
||||||
# print("SAL_and_Scores_Body:", SAL_and_Scores_Body)
|
## Run evaluation
|
||||||
|
evaluation_results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids)
|
||||||
|
# print("Evaluation Results:", evaluation_results)
|
||||||
|
|
||||||
|
## Extract and save the results
|
||||||
|
# ScoresAndRanks = evaluation_results['results']
|
||||||
|
ScoresAndRanks = evaluation_results.get('results', [])
|
||||||
|
# print("Scores and Ranks:", ScoresAndRanks)
|
||||||
|
|
||||||
|
# Append the Score and Rank of each node to SAL's Response
|
||||||
|
SAL_and_Scores_Body = append_evaluation_results(sal_reply_body, ScoresAndRanks)
|
||||||
|
# print("SAL_and_Scores_Body:", SAL_and_Scores_Body)
|
||||||
|
else:
|
||||||
|
print("There is only one node!")
|
||||||
|
# Append the Score and Rank of each node to SAL's Response
|
||||||
|
SAL_and_Scores_Body = append_evaluation_results(sal_reply_body, [])
|
||||||
|
|
||||||
## Prepare message to be sent to OPTIMIZER
|
## Prepare message to be sent to OPTIMIZER
|
||||||
# CFSBResponse = read_dummy_response_data_toOpt('CFSB_Body_Response.json') # Data and Scores for 5 Nodes
|
# CFSBResponse = read_dummy_response_data_toOpt('CFSB_Body_Response.json') # Data and Scores for 5 Nodes
|
||||||
|
|
||||||
CFSBResponse = {
|
CFSBResponse = {
|
||||||
"metaData": {"user": "admin"},
|
"metaData": {"user": "admin"},
|
||||||
"body": SAL_and_Scores_Body
|
"body": SAL_and_Scores_Body
|
||||||
}
|
}
|
||||||
print("CFSBResponse:", CFSBResponse)
|
|
||||||
|
|
||||||
|
# print("CFSBResponse:", CFSBResponse)
|
||||||
|
# Writing the formatted JSON to a json file
|
||||||
formatted_json = json.dumps(CFSBResponse, indent=4)
|
formatted_json = json.dumps(CFSBResponse, indent=4)
|
||||||
# Writing the formatted JSON to a file named test.json
|
|
||||||
with open('CFSBResponse.json', 'w') as file:
|
with open('CFSBResponse.json', 'w') as file:
|
||||||
file.write(formatted_json)
|
file.write(formatted_json)
|
||||||
print("Formatted JSON has been saved to CFSBResponse.json")
|
print("Formatted JSON has been saved to CFSBResponse.json")
|
||||||
|
|
||||||
else: # Then SAL's reply body is empty send an empty body to Optimizer
|
else: # Then SAL's reply body is empty send an empty body to Optimizer
|
||||||
print("No Body in reply from SAL!")
|
print("No Body in reply from SAL!")
|
||||||
# Send [] to Optimizer
|
# Send [] to Optimizer
|
||||||
CFSBResponse = {
|
CFSBResponse = {
|
||||||
@ -248,6 +206,14 @@ class SyncedHandler(Handler):
|
|||||||
logging.error(f"Failed to parse message body from Optimizer as JSON: {e}")
|
logging.error(f"Failed to parse message body from Optimizer as JSON: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def requestSAL(self, RequestToSal):
|
||||||
|
sal_reply = Context.publishers['SAL-GET'].send_sync(RequestToSal)
|
||||||
|
# Process SAL's Reply
|
||||||
|
sal_body = sal_reply.get('body') # Get the 'body' as a JSON string
|
||||||
|
# print("sal_body requestSAL function:", sal_body)
|
||||||
|
return sal_body
|
||||||
|
|
||||||
class Bootstrap(ConnectorHandler):
|
class Bootstrap(ConnectorHandler):
|
||||||
context = None
|
context = None
|
||||||
def ready(self, context: Context):
|
def ready(self, context: Context):
|
||||||
@ -279,6 +245,13 @@ def start_exn_connector_in_background():
|
|||||||
thread.daemon = True # Daemon threads will shut down immediately when the program exits
|
thread.daemon = True # Daemon threads will shut down immediately when the program exits
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def call_publisher(body):
|
||||||
|
handler = SyncedHandler()
|
||||||
|
request = handler.requestSAL(body)
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
# Used to read dummy response and send to Optimizer using JSON
|
# Used to read dummy response and send to Optimizer using JSON
|
||||||
# I have already sent to Optimizer using this function
|
# I have already sent to Optimizer using this function
|
||||||
def read_dummy_response_data_toOpt(file_path):
|
def read_dummy_response_data_toOpt(file_path):
|
||||||
@ -291,6 +264,7 @@ def read_dummy_response_data_toOpt(file_path):
|
|||||||
}
|
}
|
||||||
return encapsulated_data
|
return encapsulated_data
|
||||||
|
|
||||||
|
|
||||||
def is_json(myjson):
|
def is_json(myjson):
|
||||||
try:
|
try:
|
||||||
json_object = json.loads(myjson)
|
json_object = json.loads(myjson)
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
from app_factory import create_app
|
from app_factory import create_app
|
||||||
from dotenv import load_dotenv
|
|
||||||
from activemq import start_exn_connector_in_background
|
from activemq import start_exn_connector_in_background
|
||||||
from activemqOLD import start_exn_connector_in_background1
|
|
||||||
from app_factory import create_app # Import your Flask app factory
|
from app_factory import create_app # Import your Flask app factory
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
# Start the EXN connector in the background
|
# Start the EXN connector in the background
|
||||||
start_exn_connector_in_background()
|
start_exn_connector_in_background()
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
version: '3.0'
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ports:
|
|
||||||
- "8001:8001"
|
|
||||||
env_file:
|
|
||||||
- .env.prod
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
db:
|
|
||||||
image: postgres:16
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=dbuser
|
|
||||||
- POSTGRES_PASSWORD=pass123
|
|
||||||
- POSTGRES_DB=fog_broker
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data/
|
|
||||||
- ./db/db_script.sql:/docker-entrypoint-initdb.d/db_script.sql
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
80
cfsb-backend/edge_data_ipat.json
Normal file
80
cfsb-backend/edge_data_ipat.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"_id": "b4ce322c-698a-43b9-a889-bf0da2a4dcb9",
|
||||||
|
"os": "LINUX",
|
||||||
|
"name": "Test VM #0001",
|
||||||
|
"owner": "admin",
|
||||||
|
"ipAddress": "10.10.0.6",
|
||||||
|
"location": {
|
||||||
|
"name": "laptop",
|
||||||
|
"latitude": 12.345,
|
||||||
|
"longitude": 56.789
|
||||||
|
},
|
||||||
|
"username": "ubuntu",
|
||||||
|
"password": [
|
||||||
|
"u",
|
||||||
|
"b",
|
||||||
|
"u",
|
||||||
|
"n",
|
||||||
|
"t",
|
||||||
|
"u"
|
||||||
|
],
|
||||||
|
"publicKey": [],
|
||||||
|
"deviceInfo": {
|
||||||
|
"CPU_SOCKETS": "1",
|
||||||
|
"CPU_CORES": "10",
|
||||||
|
"CPU_PROCESSORS": "20",
|
||||||
|
"RAM_TOTAL_KB": "16218480",
|
||||||
|
"RAM_AVAILABLE_KB": "13366788",
|
||||||
|
"RAM_FREE_KB": "10943372",
|
||||||
|
"RAM_USED_KB": "5275108",
|
||||||
|
"RAM_UTILIZATION": "32.5253",
|
||||||
|
"DISK_TOTAL_KB": "1055762868",
|
||||||
|
"DISK_FREE_KB": "976527612",
|
||||||
|
"DISK_USED_KB": "79235256",
|
||||||
|
"DISK_UTILIZATION": "7.50502",
|
||||||
|
"OS_ARCHITECTURE": "x86_64",
|
||||||
|
"OS_KERNEL": "Linux",
|
||||||
|
"OS_KERNEL_RELEASE": "5.15.133.1-microsoft-standard-WSL2"
|
||||||
|
},
|
||||||
|
"requestId": "eb6441fc-613a-482e-ba94-b16db57ecd36",
|
||||||
|
"creationDate": "2024-01-15T13:23:40.602Z",
|
||||||
|
"lastUpdateDate": "2024-01-15T14:32:43.485Z",
|
||||||
|
"status": "HEALTHY",
|
||||||
|
"nodeReference": "40ed1989-49ba-4496-a5c5-3d8ca1a18972",
|
||||||
|
"messages": [],
|
||||||
|
"statusUpdate": {
|
||||||
|
"ipAddress": "10.10.0.6",
|
||||||
|
"clientId": "VM-LINUX-TEST-VM-0001-Test VM #0001-DEFAULT-10.10.0.6-_",
|
||||||
|
"state": "REGISTERED",
|
||||||
|
"stateLastUpdate": "2024-01-15T13:23:47.463Z",
|
||||||
|
"reference": "40ed1989-49ba-4496-a5c5-3d8ca1a18972",
|
||||||
|
"errors": []
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"ipAddress": "10.10.0.6",
|
||||||
|
"clientId": "VM-LINUX-TEST-VM-0001-Test VM",
|
||||||
|
"timestamp": "2024-01-15T14:32:33.467Z",
|
||||||
|
"metrics": {
|
||||||
|
"count-total-events-failures": 0,
|
||||||
|
"count-total-events-text": 0,
|
||||||
|
"tx": 0,
|
||||||
|
"count-total-events-other": 0,
|
||||||
|
"count-event-forwards-success": 0,
|
||||||
|
"count-event-forwards-failure": 0,
|
||||||
|
"rx": 0,
|
||||||
|
"count-total-events": 0,
|
||||||
|
"cpu": 0.6,
|
||||||
|
"uptime": 10742,
|
||||||
|
"count-event-local-publish-failure": 0,
|
||||||
|
"count-total-events-object": 0,
|
||||||
|
"disk": 2.48262,
|
||||||
|
"count-event-local-publish-success": 0,
|
||||||
|
"updatetime": 1705318391,
|
||||||
|
"currdatetime": 1705329133,
|
||||||
|
"ram": 23.7719
|
||||||
|
},
|
||||||
|
"latestEvents": []
|
||||||
|
},
|
||||||
|
"retries": 0,
|
||||||
|
"_class": "eu.nebulous.resource.discovery.monitor.model.Device"
|
||||||
|
}
|
@ -7,9 +7,11 @@ from data_types import get_attr_data_type
|
|||||||
import db.db_functions as db_functions
|
import db.db_functions as db_functions
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import get_data as file
|
||||||
import activemq
|
import activemq
|
||||||
# from activemq import connector_handler
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import logging
|
||||||
|
# logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
main_routes = Blueprint('main', __name__)
|
main_routes = Blueprint('main', __name__)
|
||||||
|
|
||||||
@ -19,6 +21,74 @@ NoData_Variables = ['attr-security', 'attr-performance-capacity', 'attr-performa
|
|||||||
Cont_Variables = ['attr-performance', 'attr-financial', 'attr-performance-capacity-memory',
|
Cont_Variables = ['attr-performance', 'attr-financial', 'attr-performance-capacity-memory',
|
||||||
'attr-performance-capacity-memory-speed']
|
'attr-performance-capacity-memory-speed']
|
||||||
|
|
||||||
|
dummy_node_data = {
|
||||||
|
"id": "8a7481d98e702b64018e702cbe070000",
|
||||||
|
"nodeCandidateType": "EDGE",
|
||||||
|
"jobIdForByon": "",
|
||||||
|
"jobIdForEdge": "FCRnewLight0",
|
||||||
|
"price": 0.0,
|
||||||
|
"cloud": {
|
||||||
|
"id": "edge",
|
||||||
|
"endpoint": "",
|
||||||
|
"cloudType": "EDGE",
|
||||||
|
"api": "",
|
||||||
|
"credential": "",
|
||||||
|
"cloudConfiguration": {
|
||||||
|
"nodeGroup": "",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
"owner": "EDGE",
|
||||||
|
"state": "",
|
||||||
|
"diagnostic": ""
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"id": "edge-location-KmVf4xDJKL7acBGc",
|
||||||
|
"name": "",
|
||||||
|
"providerId": "",
|
||||||
|
"locationScope": "",
|
||||||
|
"isAssignable": "",
|
||||||
|
"geoLocation": {
|
||||||
|
"city": "Warsaw",
|
||||||
|
"country": "Poland",
|
||||||
|
"latitude": 52.237049,
|
||||||
|
"longitude": 21.017532
|
||||||
|
},
|
||||||
|
"parent": "",
|
||||||
|
"state": "",
|
||||||
|
"owner": ""
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"id": "edge-image-KmVf4xDJKL7acBGc",
|
||||||
|
"name": "edge-image-name-UBUNTU-UNKNOWN",
|
||||||
|
"providerId": "",
|
||||||
|
"operatingSystem": {
|
||||||
|
"operatingSystemFamily": "UBUNTU",
|
||||||
|
"operatingSystemArchitecture": "UNKNOWN",
|
||||||
|
"operatingSystemVersion": 1804.00
|
||||||
|
},
|
||||||
|
"location": "",
|
||||||
|
"state": "",
|
||||||
|
"owner": ""
|
||||||
|
},
|
||||||
|
"hardware": {
|
||||||
|
"id": "edge-hardware-KmVf4xDJKL7acBGc",
|
||||||
|
"name": "",
|
||||||
|
"providerId": "",
|
||||||
|
"cores": 1,
|
||||||
|
"ram": 1,
|
||||||
|
"disk": 1.0,
|
||||||
|
"fpga": 0,
|
||||||
|
"location": "",
|
||||||
|
"state": "",
|
||||||
|
"owner": ""
|
||||||
|
},
|
||||||
|
"pricePerInvocation": 0.0,
|
||||||
|
"memoryPrice": 0.0,
|
||||||
|
"nodeId": "",
|
||||||
|
"environment": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#Used in HomePage.vue to save app_id and user_id
|
#Used in HomePage.vue to save app_id and user_id
|
||||||
# @main_routes.route('/save_ids', methods=['POST'])
|
# @main_routes.route('/save_ids', methods=['POST'])
|
||||||
# def save_ids():
|
# def save_ids():
|
||||||
@ -34,7 +104,6 @@ Cont_Variables = ['attr-performance', 'attr-financial', 'attr-performance-capaci
|
|||||||
#Used in CriteriaSelection.vue
|
#Used in CriteriaSelection.vue
|
||||||
@main_routes.route('/get_hierarchical_category_list')
|
@main_routes.route('/get_hierarchical_category_list')
|
||||||
def get_hierarchical_category_list():
|
def get_hierarchical_category_list():
|
||||||
# TODO order by title in every level
|
|
||||||
items_list = file.get_level_1_items() # Assume this function returns the list correctly
|
items_list = file.get_level_1_items() # Assume this function returns the list correctly
|
||||||
if items_list is not None:
|
if items_list is not None:
|
||||||
# Return the list as a JSON response
|
# Return the list as a JSON response
|
||||||
@ -48,101 +117,196 @@ def get_hierarchical_category_list():
|
|||||||
def process_selected_criteria():
|
def process_selected_criteria():
|
||||||
try:
|
try:
|
||||||
data = request.json
|
data = request.json
|
||||||
# Selected Criteria by the User from the List
|
|
||||||
selected_criteria = data.get('selectedItems', [])
|
selected_criteria = data.get('selectedItems', [])
|
||||||
# Extract app_id, user_id
|
|
||||||
application_id = data.get('app_id') # Take it from local storage from frontend
|
|
||||||
# application_id = 'd535cf554ea66fbebfc415ac837a5828' #dummy application_id_optimizer
|
|
||||||
user_id = data.get('user_id') # Take it from local storage from frontend
|
|
||||||
print("user_id:", user_id)
|
|
||||||
print("application_id:", application_id)
|
|
||||||
|
|
||||||
## Prepare message to be send to SAL
|
# application_id = data.get('app_id')
|
||||||
message_for_SAL = [ # User side so ask SAL for every available node
|
# user_id = data.get('user_id')
|
||||||
{
|
# print("user_id:", user_id)
|
||||||
"type": "NodeTypeRequirement",
|
# print("application_id:", application_id)
|
||||||
"nodeTypes": ["IAAS", "PAAS", "FAAS", "BYON", "EDGE", "SIMULATION"]
|
|
||||||
# "jobIdForEDGE": "FCRnewLight0"
|
message_for_SAL = [{
|
||||||
}
|
"type": "NodeTypeRequirement",
|
||||||
|
"nodeTypes": ["IAAS", "PAAS", "FAAS", "BYON", "EDGE", "SIMULATION"]}
|
||||||
|
# ,{
|
||||||
|
# "type": "AttributeRequirement",
|
||||||
|
# "requirementClass": "hardware",
|
||||||
|
# "requirementAttribute": "cores",
|
||||||
|
# "requirementOperator": "GEQ",
|
||||||
|
# "value": "64"
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "type": "AttributeRequirement",
|
||||||
|
# "requirementClass": "hardware",
|
||||||
|
# "requirementAttribute": "ram",
|
||||||
|
# "requirementOperator": "GEQ",
|
||||||
|
# "value": "33000"
|
||||||
|
# }
|
||||||
]
|
]
|
||||||
# Convert the body data to a JSON string
|
body_json_string_for_SAL = json.dumps(message_for_SAL)
|
||||||
body_json_string = json.dumps(message_for_SAL)
|
|
||||||
|
|
||||||
RequestToSal = { # Dictionary
|
RequestToSal = {
|
||||||
"metaData": {"user": "admin"}, # key [String "metaData"] value [dictionary]
|
"metaData": {"user": "admin"},
|
||||||
"body": body_json_string # key [String "body"] value [JSON String]
|
"body": body_json_string_for_SAL
|
||||||
}
|
}
|
||||||
print("RequestToSal:", RequestToSal)
|
# print("RequestToSal:", RequestToSal)
|
||||||
|
|
||||||
# print("Is RequestToSal a valid dictionary:", isinstance(RequestToSal, dict))
|
sal_reply = activemq.call_publisher(RequestToSal)
|
||||||
# print("Is the 'body' string in RequestToSal a valid JSON string:", is_json(RequestToSal["body"]))
|
nodes_data = json.loads(sal_reply) if isinstance(sal_reply, str) else sal_reply
|
||||||
|
# print("nodes_data", nodes_data)
|
||||||
|
|
||||||
## Request the node candidates from SAL
|
extracted_data, node_ids, node_names = extract_SAL_node_candidate_data_Front(nodes_data)
|
||||||
# sal_reply = activemq.context.publishers['SAL-GET'].send_sync(RequestToSal)
|
# print("extracted_data:", extracted_data)
|
||||||
|
field_mapping = create_criteria_mapping()
|
||||||
|
# print("field_mapping", field_mapping)
|
||||||
|
|
||||||
## Process SAL's Reply
|
default_list_criteria_mapping = {
|
||||||
# extracted_data, number_of_nodes, node_ids, node_names = extract_SAL_node_candidate_data(sal_reply)
|
# "Cost": "price",
|
||||||
# extracted_data, number_of_nodes, node_names = extract_node_candidate_data('dummy_data_node_candidates.json')
|
"Operating cost": "price",
|
||||||
extracted_data, number_of_nodes, node_ids, node_names = extract_node_candidate_data('SAL_Response_11EdgeDevs.json')
|
"Memory Price": "memoryPrice",
|
||||||
print("extracted_data:", extracted_data)
|
"Number of CPU Cores": "cores",
|
||||||
|
"Memory Size": "ram",
|
||||||
|
"Storage Capacity": "disk"
|
||||||
|
}
|
||||||
|
|
||||||
# Use the create_criteria_mapping() to get the criteria mappings
|
grid_data = {}
|
||||||
field_mapping = create_criteria_mapping(selected_criteria, extracted_data)
|
|
||||||
grid_data = {name: [] for name in node_names}
|
|
||||||
|
|
||||||
# Prepare the data to be sent to DataGrid.vue
|
|
||||||
# Blank by default for the Selected Criteria not found in mapping
|
|
||||||
for node_data in extracted_data:
|
for node_data in extracted_data:
|
||||||
node_name = node_data.get('name') # Using name to match
|
node_id = node_data.get('id')
|
||||||
node_id = node_data.get('id') # Extract the node ID
|
# print("Before create_node_name")
|
||||||
grid_data[node_name] = {"id": node_id, "criteria": []}
|
node_name = create_node_name(node_data) if node_data else "Unknown"
|
||||||
|
# print("After create_node_name")
|
||||||
|
|
||||||
if node_name in grid_data: # Check if node_name exists in grid_data keys
|
if node_id and node_id not in grid_data:
|
||||||
for item in selected_criteria:
|
grid_data[node_id] = {"name": node_name, "criteria": []}
|
||||||
criterion_data = {}
|
|
||||||
criterion_data["data_type"] = get_attr_data_type(item)
|
|
||||||
item_data_dict = file.get_subject_data(file.SMI_prefix + item)
|
|
||||||
criterion_data["title"] = item_data_dict["title"]
|
|
||||||
field_name = field_mapping.get(criterion_data["title"], item)
|
|
||||||
|
|
||||||
# Check if the field_name is a direct key or nested inside 'hardware'
|
hardware_info = node_data.get('hardware', {}) # contains the values for criteria coming from SAL
|
||||||
if field_name in node_data:
|
|
||||||
value = node_data[field_name]
|
for criterion_key in selected_criteria:
|
||||||
elif 'hardware' in node_data and field_name in node_data['hardware']:
|
# print("criterion_key:", criterion_key)
|
||||||
value = node_data['hardware'][field_name]
|
criterion_info = file.get_subject_data(file.SMI_prefix + criterion_key) # It contains the titles of the criteria
|
||||||
|
# print("criterion_info:", criterion_info)
|
||||||
|
|
||||||
|
# Resolve title and then map title to field name
|
||||||
|
criterion_data_type = get_attr_data_type(criterion_key) # criterion_data_type: {'type': 1, 'values': ['Low', 'Medium', 'High']}
|
||||||
|
# print("criterion_data_type:", criterion_data_type)
|
||||||
|
criterion_title = criterion_info["title"]
|
||||||
|
|
||||||
|
# Fetch the values of the selected default criteria
|
||||||
|
if criterion_title in default_list_criteria_mapping:
|
||||||
|
SAL_criterion_name = field_mapping.get(criterion_title) # Map the criterion title with the criterion name in SAL's reply
|
||||||
|
value = hardware_info.get(SAL_criterion_name, "N/A") # Get the criterion values
|
||||||
|
else:
|
||||||
|
# Handle other criteria (this part may need adjustment based on your actual data structure)
|
||||||
|
# value = "N/A" # Placeholder for the logic to determine non-default criteria values
|
||||||
|
# Generate random or default values for rest criteria
|
||||||
|
type_value = criterion_data_type['type']
|
||||||
|
# print("type_value:", type_value)
|
||||||
|
|
||||||
|
if type_value == 1:
|
||||||
|
value = random.choice(["High", "Medium", "Low"])
|
||||||
|
elif type_value == 5:
|
||||||
|
value = random.choice(["True", "False"])
|
||||||
else:
|
else:
|
||||||
# Generate random or default values for unmapped criteria or missing data
|
value = round(random.uniform(1, 100), 2)
|
||||||
item_data_type_value = criterion_data["data_type"].get('type')
|
|
||||||
if item_data_type_value == 1:
|
|
||||||
value = random.choice(["High", "Medium", "Low"])
|
|
||||||
elif item_data_type_value == 5:
|
|
||||||
value = random.choice(["True", "False"])
|
|
||||||
else:
|
|
||||||
value = round(random.uniform(1, 100), 2)
|
|
||||||
|
|
||||||
criterion_data["value"] = value if value != 0 else 0.00001
|
criterion_data = {
|
||||||
# grid_data[node_id].append(criterion_data)
|
"title": criterion_title,
|
||||||
# grid_data[node_name].append(criterion_data) # Use node_name as key
|
"value": value,
|
||||||
grid_data[node_name]["criteria"].append(criterion_data)
|
"data_type": criterion_data_type # criterion_data_type: {'type': 1, 'values': ['Low', 'Medium', 'High']}
|
||||||
|
}
|
||||||
|
grid_data[node_id]["criteria"].append(criterion_data)
|
||||||
|
|
||||||
# Conversion to list format remains unchanged
|
grid_data_with_names = [{
|
||||||
# grid_data_with_names = [{'name': name, 'criteria': data} for name, data in grid_data.items()]
|
'name': data["name"],
|
||||||
grid_data_with_names = [{'name': name, 'id': data["id"], 'criteria': data["criteria"]} for name, data in grid_data.items()]
|
'id': node_id,
|
||||||
print("grid_data_with_names:", grid_data_with_names)
|
'criteria': data["criteria"]
|
||||||
|
} for node_id, data in grid_data.items()]
|
||||||
|
# print("grid_data_with_names:", grid_data_with_names)
|
||||||
|
|
||||||
# Send the comprehensive grid_data_with_names to the frontend
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'gridData': grid_data_with_names,
|
'gridData': grid_data_with_names,
|
||||||
'NodeNames': node_names
|
'NodeNames': node_names
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing selected items: {e}")
|
print(f"Error processing selected items: {e}")
|
||||||
traceback.print_exc()
|
|
||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# Used in WR.vue
|
# Works by reading a JSON file with dummy data
|
||||||
|
# def process_selected_criteria():
|
||||||
|
# try:
|
||||||
|
# data = request.json
|
||||||
|
# # Selected Criteria by the User from the List
|
||||||
|
# selected_criteria = data.get('selectedItems', [])
|
||||||
|
# # Extract app_id, user_id
|
||||||
|
# application_id = data.get('app_id') # Take it from local storage from frontend
|
||||||
|
# # application_id = 'd535cf554ea66fbebfc415ac837a5828' #dummy application_id_optimizer
|
||||||
|
# user_id = data.get('user_id') # Take it from local storage from frontend
|
||||||
|
# print("user_id:", user_id)
|
||||||
|
# print("application_id:", application_id)
|
||||||
|
#
|
||||||
|
# ## Process SAL's Reply
|
||||||
|
# # extracted_data, number_of_nodes, node_names = extract_node_candidate_data('dummy_data_node_candidates.json')
|
||||||
|
# extracted_data, node_ids, node_names = extract_node_candidate_data('SAL_Response_11EdgeDevs.json')
|
||||||
|
# print("extracted_data:", extracted_data)
|
||||||
|
#
|
||||||
|
# # Use the create_criteria_mapping() to get the criteria mappings
|
||||||
|
# field_mapping = create_criteria_mapping(selected_criteria, extracted_data)
|
||||||
|
# grid_data = {name: [] for name in node_names}
|
||||||
|
#
|
||||||
|
# # Prepare the data to be sent to DataGrid.vue
|
||||||
|
# for node_data in extracted_data:
|
||||||
|
# node_name = node_data.get('name') # Using name to match
|
||||||
|
# node_id = node_data.get('id') # Extract the node ID
|
||||||
|
# grid_data[node_name] = {"id": node_id, "criteria": []}
|
||||||
|
#
|
||||||
|
# if node_name in grid_data: # Check if node_name exists in grid_data keys
|
||||||
|
# for item in selected_criteria:
|
||||||
|
# criterion_data = {}
|
||||||
|
# criterion_data["data_type"] = get_attr_data_type(item)
|
||||||
|
# item_data_dict = file.get_subject_data(file.SMI_prefix + item)
|
||||||
|
# criterion_data["title"] = item_data_dict["title"]
|
||||||
|
# field_name = field_mapping.get(criterion_data["title"], item)
|
||||||
|
#
|
||||||
|
# # Check if the field_name is a direct key or nested inside 'hardware'
|
||||||
|
# if field_name in node_data:
|
||||||
|
# value = node_data[field_name]
|
||||||
|
# elif 'hardware' in node_data and field_name in node_data['hardware']:
|
||||||
|
# value = node_data['hardware'][field_name]
|
||||||
|
# else:
|
||||||
|
# # Generate random or default values for unmapped criteria or missing data
|
||||||
|
# item_data_type_value = criterion_data["data_type"].get('type')
|
||||||
|
# if item_data_type_value == 1:
|
||||||
|
# value = random.choice(["High", "Medium", "Low"])
|
||||||
|
# elif item_data_type_value == 5:
|
||||||
|
# value = random.choice(["True", "False"])
|
||||||
|
# else:
|
||||||
|
# value = round(random.uniform(1, 100), 2)
|
||||||
|
#
|
||||||
|
# criterion_data["value"] = value if value != 0 else 0.00001
|
||||||
|
# # grid_data[node_id].append(criterion_data)
|
||||||
|
# grid_data[node_name]["criteria"].append(criterion_data)
|
||||||
|
#
|
||||||
|
# # Conversion to list format remains unchanged
|
||||||
|
# # grid_data_with_names = [{'name': name, 'criteria': data} for name, data in grid_data.items()]
|
||||||
|
# grid_data_with_names = [{'name': name, 'id': data["id"], 'criteria': data["criteria"]} for name, data in grid_data.items()]
|
||||||
|
# print("grid_data_with_names:", grid_data_with_names)
|
||||||
|
#
|
||||||
|
# # Send the comprehensive grid_data_with_names to the frontend
|
||||||
|
# return jsonify({
|
||||||
|
# 'success': True,
|
||||||
|
# 'gridData': grid_data_with_names,
|
||||||
|
# 'NodeNames': node_names
|
||||||
|
# })
|
||||||
|
# except Exception as e:
|
||||||
|
# print(f"Error processing selected items: {e}")
|
||||||
|
# traceback.print_exc()
|
||||||
|
# return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@main_routes.route('/process-evaluation-data', methods=['POST'])
|
@main_routes.route('/process-evaluation-data', methods=['POST'])
|
||||||
def process_evaluation_data():
|
def process_evaluation_data():
|
||||||
try:
|
try:
|
||||||
@ -150,14 +314,15 @@ def process_evaluation_data():
|
|||||||
if data is None:
|
if data is None:
|
||||||
raise ValueError("Received data is not in JSON format or 'Content-Type' header is not set to 'application/json'")
|
raise ValueError("Received data is not in JSON format or 'Content-Type' header is not set to 'application/json'")
|
||||||
|
|
||||||
print("JSON data:", data)
|
# print("JSON in process_evaluation_data:", data)
|
||||||
# Transform grid data to table and get node names directly from the function
|
# Transform grid data to table and get node names directly from the function
|
||||||
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = transform_grid_data_to_table(data)
|
data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = transform_grid_data_to_table(data)
|
||||||
|
|
||||||
# print("data_table:", data_table)
|
# print("data_table FRONT:", data_table)
|
||||||
# print("relative_wr_data:", relative_wr_data)
|
# print("relative_wr_data:", relative_wr_data)
|
||||||
# print("immediate_wr_data:", immediate_wr_data)
|
# print("immediate_wr_data:", immediate_wr_data)
|
||||||
# print("node_names:", node_names)
|
# print("# node_names:", len(node_names))
|
||||||
|
# print("# node_ids:", len(node_ids))
|
||||||
|
|
||||||
# Run Optimization - Perform evaluation
|
# Run Optimization - Perform evaluation
|
||||||
results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids)
|
results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids)
|
||||||
|
140
cfsb-backend/test.py
Normal file
140
cfsb-backend/test.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# ActiveMQ communication logic via EXN library
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
sys.path.insert(0,'../exn')
|
||||||
|
import logging
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
load_dotenv()
|
||||||
|
from proton import Message
|
||||||
|
from exn import core
|
||||||
|
from exn.connector import EXN
|
||||||
|
from exn.core.consumer import Consumer
|
||||||
|
from exn.core.synced_publisher import SyncedPublisher
|
||||||
|
from exn.core.publisher import Publisher
|
||||||
|
from exn.core.context import Context
|
||||||
|
from exn.core.handler import Handler
|
||||||
|
from exn.handler.connector_handler import ConnectorHandler
|
||||||
|
from User_Functions import *
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
logging.getLogger('exn.connector').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class SyncedHandler(Handler):
|
||||||
|
def on_message(self, key, address, body, message: Message, context=None):
|
||||||
|
logging.info(f"[SyncedHandler] Received {key} => {address}: {body}")
|
||||||
|
logging.info("on_message in SyncedHandler is executed")
|
||||||
|
logging.info(f"[body] {body}")
|
||||||
|
|
||||||
|
# Triggered by OPTIMIZER, Get app id, correlation id and filters
|
||||||
|
# if address == "topic://eu.nebulouscloud.cfsb.get_node_candidates":
|
||||||
|
if key == "OPT-triggering":
|
||||||
|
logging.info("Entered in OPT-triggering Key")
|
||||||
|
|
||||||
|
# Save the correlation_id (We do not have it from the app_side)
|
||||||
|
# Optimizer_correlation_id = '88334290cad34ad9b21eb468a9f8ff11' # dummy correlation_id
|
||||||
|
Optimizer_correlation_id = message.correlation_id
|
||||||
|
logging.info(f"Optimizer_correlation_id {message.correlation_id}")
|
||||||
|
application_id = message.subject # can be taken also from message.annotations.application
|
||||||
|
|
||||||
|
try:
|
||||||
|
opt_message_data = body
|
||||||
|
print("Message from Optimizer:", opt_message_data)
|
||||||
|
|
||||||
|
# Extract 'body' from opt_message_data
|
||||||
|
# opt_body_data = opt_message_data.get('body', {})
|
||||||
|
opt_body_data =[
|
||||||
|
{
|
||||||
|
"type": "NodeTypeRequirement",
|
||||||
|
"nodeTypes": ["EDGE"],
|
||||||
|
"jobIdForEDGE": "FCRnewLight0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
logging.info(opt_body_data)
|
||||||
|
print("Extracted body from Optim Message:", opt_body_data)
|
||||||
|
|
||||||
|
## Prepare message to be send to SAL
|
||||||
|
RequestToSal = {
|
||||||
|
"metaData": {"user": "admin"},
|
||||||
|
"body": opt_body_data
|
||||||
|
}
|
||||||
|
print("RequestToSal:", RequestToSal)
|
||||||
|
|
||||||
|
# Convert the Python structure to a JSON string
|
||||||
|
# RequestToSal = json.dumps(RequestToSal)
|
||||||
|
|
||||||
|
# Request the node candidates from SAL
|
||||||
|
sal_reply = context.publishers['SAL-GET'].send_sync(RequestToSal, application_id,
|
||||||
|
properties={'correlation_id': Optimizer_correlation_id}, raw=False)
|
||||||
|
# sal_reply = context.publishers['SAL-GET'].send_sync(RequestToSal, application_id)
|
||||||
|
if sal_reply:
|
||||||
|
logging.info(f"Received reply from SAL: {sal_reply}")
|
||||||
|
print("SAL reply:", sal_reply)
|
||||||
|
else:
|
||||||
|
print("No reply from SAL!")
|
||||||
|
|
||||||
|
## Prepare message to be sent to OPTIMIZER
|
||||||
|
CFSBResponse = read_dummy_response_data_toOpt('CFSB_Body_Response.json')
|
||||||
|
|
||||||
|
# SAL_and_Scores_Body = Give me a short example
|
||||||
|
# Encapsulate the data within the "body" structure
|
||||||
|
# CFSBResponse = {
|
||||||
|
# "metaData": {"user": "admin"},
|
||||||
|
# "body": SAL_and_Scores_Body
|
||||||
|
# }
|
||||||
|
# print("CFSBResponse:", CFSBResponse)
|
||||||
|
|
||||||
|
# Send message to Optimizer
|
||||||
|
context.get_publisher('SendToOPT').send(CFSBResponse, application_id)
|
||||||
|
# context.publishers['SendToOPT'].send(CFSBResponse, application_id, properties={
|
||||||
|
# 'correlation_id': Optimizer_correlation_id}, raw=True)
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logging.error(f"Failed to parse message body from Optimizer as JSON: {e}")
|
||||||
|
|
||||||
|
class Bootstrap(ConnectorHandler):
|
||||||
|
context = None
|
||||||
|
def ready(self, context: Context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
|
||||||
|
def start_exn_connector_in_background():
|
||||||
|
def run_connector():
|
||||||
|
# eu.nebulouscloud.exn.sal.nodecandidate.*
|
||||||
|
addressSAL_GET = 'eu.nebulouscloud.exn.sal.nodecandidate.get'
|
||||||
|
|
||||||
|
addressSAL_GET_REPLY = 'eu.nebulouscloud.exn.sal.nodecandidate.get.reply'
|
||||||
|
addressOPTtriggering = 'eu.nebulouscloud.cfsb.get_node_candidates'
|
||||||
|
addressSendToOPT = 'eu.nebulouscloud.cfsb.get_node_candidates.reply'
|
||||||
|
|
||||||
|
connector = EXN('ui', url="localhost", port=5672, username="admin", password="admin",
|
||||||
|
handler=Bootstrap(),
|
||||||
|
publishers=[
|
||||||
|
SyncedPublisher('SAL-GET', addressSAL_GET, True, True),
|
||||||
|
core.publisher.Publisher('SendToOPT', addressSendToOPT, True, True)
|
||||||
|
],
|
||||||
|
consumers=[
|
||||||
|
# Consumer('SAL-GET-REPLY', addressSAL_GET, handler=SyncedHandler(), topic=True, fqdn=True),
|
||||||
|
Consumer('OPT-triggering', addressOPTtriggering, handler=SyncedHandler(), topic=True, fqdn=True)
|
||||||
|
])
|
||||||
|
connector.start()
|
||||||
|
|
||||||
|
# Start the EXN connector in a separate thread
|
||||||
|
thread = threading.Thread(target=run_connector)
|
||||||
|
thread.daemon = True # Daemon threads will shut down immediately when the program exits
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
# Used to read dummy JSON and send to Optimizer
|
||||||
|
def read_dummy_response_data_toOpt(file_path):
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
# Encapsulating the data within the "body" structure
|
||||||
|
encapsulated_data = {
|
||||||
|
"metaData": {"user": "admin"},
|
||||||
|
"body": data
|
||||||
|
}
|
||||||
|
return encapsulated_data
|
||||||
|
|
1256
cfsb-backend/updated_SALs_JSON.json
Normal file
1256
cfsb-backend/updated_SALs_JSON.json
Normal file
File diff suppressed because it is too large
Load Diff
2
cfsb-frontend/.env
Normal file
2
cfsb-frontend/.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
VUE_APP_BACKEND_URL=http://127.0.0.1:5000
|
||||||
|
VITE_BACKEND_URL=http://127.0.0.1:5000
|
2
cfsb-frontend/.env.development
Normal file
2
cfsb-frontend/.env.development
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
VUE_APP_BACKEND_URL=http://127.0.0.1:8001
|
||||||
|
VITE_BACKEND_URL=http://127.0.0.1:8001
|
2
cfsb-frontend/.env.production
Normal file
2
cfsb-frontend/.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
VUE_APP_BACKEND_URL=http://127.0.0.1:8001
|
||||||
|
VITE_BACKEND_URL=http://127.0.0.1:8001
|
22
cfsb-frontend/Dockerfile
Normal file
22
cfsb-frontend/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Step 1: Build Stage
|
||||||
|
FROM node:16 as build-stage
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Step 2: Nginx Stage
|
||||||
|
FROM docker.io/nginx:alpine
|
||||||
|
|
||||||
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
|
COPY .env.production /usr/share/nginx/html/.env
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
1297
cfsb-frontend/package-lock.json
generated
1297
cfsb-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
|
"package.json": "^2.0.1",
|
||||||
"vue-router": "^4.0.13"
|
"vue-router": "^4.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
@ -28,17 +28,55 @@
|
|||||||
<!-- Main content where routed components will be displayed -->
|
<!-- Main content where routed components will be displayed -->
|
||||||
<!-- <router-view></router-view>
|
<!-- <router-view></router-view>
|
||||||
<button v-if="showCriteriaSelectionButton" @click="goToCriteriaSelection">Go to Criteria Selection</button> -->
|
<button v-if="showCriteriaSelectionButton" @click="goToCriteriaSelection">Go to Criteria Selection</button> -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="userLoginModal" aria-hidden="false">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title">User login</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<form @submit.prevent="submitUserLoginForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="app_id" class="form-label">Insert Application ID</label>
|
||||||
|
<input type="text" class="form-control" id="app_id" v-model="app_id" placeholder="Application ID" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Your username</label>
|
||||||
|
<input type="text" class="form-control" id="username" v-model="username" placeholder="Username" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Your password</label>
|
||||||
|
<input type="password" class="form-control" id="password" v-model="password" placeholder="Password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-success">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div v-if="!login" class="alert alert-danger">Error</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" ref="modalCloseBtn">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer class="footer text-center p-2">
|
<footer class="footer text-center p-2">
|
||||||
<span class="text-white">© NebulOus</span>
|
<span class="text-white">© NebulOus - Cloud Fog Service Broker</span>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--main-color: #7030A0;
|
--main-color: #1b253b;
|
||||||
--secondary-color: #e0cffc;
|
--secondary-color: #e0cffc;
|
||||||
--color-indigo-700: #3d0a91;
|
--color-indigo-700: #172135;
|
||||||
--light-gray-color: #f8f9fa;
|
--light-gray-color: #f8f9fa;
|
||||||
--medium-gray-color: #6c757d;
|
--medium-gray-color: #6c757d;
|
||||||
}
|
}
|
||||||
@ -89,22 +127,107 @@
|
|||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
background-color: var(--main-color);
|
background-color: var(--main-color);
|
||||||
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
uuid: "",
|
||||||
|
app_id: "",
|
||||||
|
login: true
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
goToCriteriaSelection() {
|
goToCriteriaSelection() {
|
||||||
this.$router.push('/criteria-selection');
|
this.$router.push('/criteria-selection');
|
||||||
}
|
},
|
||||||
|
checkUserLogin() {
|
||||||
|
let uuid = localStorage.getItem('fog_broker_user_uuid');
|
||||||
|
if (uuid) {
|
||||||
|
console.log("user is set");
|
||||||
|
} else {
|
||||||
|
console.log("user not set");
|
||||||
|
let myModal = new bootstrap.Modal(document.getElementById('userLoginModal'));
|
||||||
|
myModal.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submitUserLoginForm() {
|
||||||
|
console.log('username = :', this.username);
|
||||||
|
let user_data = {
|
||||||
|
'username': this.username,
|
||||||
|
'password': this.password,
|
||||||
|
'app_id': this.app_id,
|
||||||
|
}
|
||||||
|
let result = await this.fetchUser(user_data)
|
||||||
|
this.username = "";
|
||||||
|
this.password = "";
|
||||||
|
},
|
||||||
|
async fetchUser(user_data) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL+'/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(user_data)
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
if (data.length===1) {
|
||||||
|
this.uuid = data[0][2];
|
||||||
|
console.log(data[0][2]);
|
||||||
|
localStorage.setItem('fog_broker_user_uuid', data[0][2]);
|
||||||
|
localStorage.setItem('fog_broker_app_id', user_data.app_id);
|
||||||
|
let elem = this.$refs.modalCloseBtn
|
||||||
|
elem.click()
|
||||||
|
this.login = true;
|
||||||
|
} else {
|
||||||
|
this.login = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getURLparams() {
|
||||||
|
let app_in_url = false
|
||||||
|
let user_in_url = false
|
||||||
|
let app_id_from_js = new URL(location.href).searchParams.get('app_id');
|
||||||
|
let user_id_from_js = new URL(location.href).searchParams.get('user_id');
|
||||||
|
|
||||||
|
if (app_id_from_js) {
|
||||||
|
console.log('app_id from URL:', app_id_from_js);
|
||||||
|
this.app_id = app_id_from_js;
|
||||||
|
app_in_url = true;
|
||||||
|
localStorage.setItem('fog_broker_app_id', this.app_id);
|
||||||
|
}
|
||||||
|
if (user_id_from_js) {
|
||||||
|
console.log('user_id from URL:', user_id_from_js);
|
||||||
|
this.uuid = user_id_from_js;
|
||||||
|
user_in_url = true
|
||||||
|
localStorage.setItem('fog_broker_user_uuid', this.uuid);
|
||||||
|
}
|
||||||
|
if (app_in_url && user_in_url){
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showCriteriaSelectionButton() {
|
showCriteriaSelectionButton() {
|
||||||
return this.$route.path === '/'; /* other conditions */
|
return this.$route.path === '/'; /* other conditions */
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getURLparams();
|
||||||
|
this.checkUserLogin();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
:root {
|
:root {
|
||||||
--vt-c-white: #ffffff;
|
--vt-c-white: #1B253BFF;
|
||||||
--vt-c-white-soft: #f8f8f8;
|
--vt-c-white-soft: #1b253b;
|
||||||
--vt-c-white-mute: #f2f2f2;
|
--vt-c-white-mute: #1b253b;
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
--vt-c-black: #181818;
|
||||||
--vt-c-black-soft: #222222;
|
--vt-c-black-soft: #222222;
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row" style="padding-bottom: 2rem">
|
<div class="row" style="padding-bottom: 2rem"></div>
|
||||||
</div>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col col-12 col-lg-8">
|
<div class="col col-12 col-lg-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<!-- Use a flex container for the header -->
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h2>Selection of Criteria</h2>
|
<h2>Selection of Criteria</h2>
|
||||||
|
<!-- Clickable icon and text for expanding/collapsing -->
|
||||||
|
<span class="expand-collapse-link" @click="toggleExpandAll">
|
||||||
|
<i v-bind:class="expandIconClass"></i>{{ expandButtonText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<HierarchicalCategoryList
|
<p class="description">
|
||||||
:items="hierarchicalCategoryList"
|
Please select at least two criteria to proceed.
|
||||||
@selected-items="updateSelectedItems"
|
</p>
|
||||||
></HierarchicalCategoryList>
|
<!-- HierarchicalCategoryList is included here with the necessary bindings -->
|
||||||
|
<HierarchicalCategoryList ref="hierarchicalList" :items="hierarchicalCategoryList" @selected-items="updateSelectedItems"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div>Selected Items Length: {{ selectedItems.length }}</div>
|
|
||||||
<button v-if="selectedItems.length > 0" @click="navigateToDataGrid">Go to DataGrid</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
import HierarchicalCategoryList from "@/components/HierarchicalCategoryList.vue";
|
import HierarchicalCategoryList from "@/components/HierarchicalCategoryList.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -33,32 +38,62 @@ export default {
|
|||||||
return {
|
return {
|
||||||
hierarchicalCategoryList: [],
|
hierarchicalCategoryList: [],
|
||||||
selectedItems: [],
|
selectedItems: [],
|
||||||
|
allCategoriesExpanded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log('CriteriaSelection.vue mounted');
|
console.log('CriteriaSelection.vue mounted');
|
||||||
this.fetchHierarchicalCategoryList();
|
this.fetchHierarchicalCategoryList();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
expandButtonText() {
|
||||||
|
return this.allCategoriesExpanded ? 'Collapse All' : 'Expand All';
|
||||||
|
},
|
||||||
|
expandIconClass() {
|
||||||
|
return this.allCategoriesExpanded ? 'bi-arrow-bar-up' : 'bi-arrow-bar-down';
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchHierarchicalCategoryList() {
|
async fetchHierarchicalCategoryList() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://127.0.0.1:5000/get_hierarchical_category_list');
|
const response = await fetch(apiURL+'/get_hierarchical_category_list');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.hierarchicalCategoryList = data;
|
this.hierarchicalCategoryList = data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching hierarchical category list:', error);
|
console.error('Error fetching hierarchical category list:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateToDataGrid() {
|
toggleExpandAll() {
|
||||||
console.log('Navigating to DataGrid');
|
this.allCategoriesExpanded = !this.allCategoriesExpanded;
|
||||||
this.$router.push({ name: 'DataGrid' });
|
if (this.$refs.hierarchicalList) {
|
||||||
|
this.$refs.hierarchicalList.setChildrenVisibility(this.hierarchicalCategoryList, this.allCategoriesExpanded);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateSelectedItems(newSelectedItems) {
|
updateSelectedItems(newSelectedItems) {
|
||||||
//console.log('Updating selected items in CriteriaSelection.vue:', newSelectedItems);
|
//console.log('Updating selected items in CriteriaSelection.vue:', newSelectedItems);
|
||||||
this.selectedItems = newSelectedItems;
|
this.selectedItems = newSelectedItems;
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-collapse-link {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-indigo-700); user-select: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Aligns the icon and text vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-collapse-link i {
|
||||||
|
margin-right: 0.5rem; /* Add some space between the icon and text */
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -1,25 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h2>Edge / Fog Nodes Data</h2>
|
<h2>Nodes Data</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <table v-if="gridData.length" class="grid-cell-class">-->
|
||||||
|
<!-- <thead>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th>Node</th>-->
|
||||||
|
<!-- <!– Assuming all entries have the same criteria, using the first one to generate headers –>-->
|
||||||
|
<!-- <th v-for="(criterion, index) in gridData[0].criteria" :key="index">-->
|
||||||
|
<!-- {{ criterion.title }}-->
|
||||||
|
<!-- </th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr v-for="(entry, entryIndex) in gridData" :key="entry.name">-->
|
||||||
|
<!-- <td>{{ entry.name }}</td>-->
|
||||||
|
<!-- <td v-for="(dataValue, dataIndex) in entry.data_values" :key="`${entry.name}-${dataIndex}`">-->
|
||||||
|
<!-- <!– Numeric data type –>-->
|
||||||
|
<!-- <template v-if="dataValue.data_type.type === 2">-->
|
||||||
|
<!-- <input type="number" v-model="dataValue.value" @blur="validateNumeric(entry.data_values, dataIndex)" step="0.5"/>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
<table v-if="Object.keys(gridData).length" class="grid-cell-class">
|
<!-- <!– Ordinal data type –>-->
|
||||||
|
<!-- <template v-else-if="dataValue.data_type.type === 1">-->
|
||||||
|
<!-- <select v-model="dataValue.value">-->
|
||||||
|
<!-- <option v-for="option in dataValue.data_type.values" :value="option" :key="option">{{ option }}</option>-->
|
||||||
|
<!-- </select>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
|
<!-- <!– Boolean data type –>-->
|
||||||
|
<!-- <template v-else-if="dataValue.data_type.type === 5">-->
|
||||||
|
<!-- <select v-model="dataValue.value">-->
|
||||||
|
<!-- <option v-for="option in ['True', 'False']" :value="option" :key="option">{{ option }}</option>-->
|
||||||
|
<!-- </select>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
|
||||||
|
<!-- <!– Fallback or other data types –>-->
|
||||||
|
<!-- <template v-else>-->
|
||||||
|
<!-- <input type="text" v-model="dataValue.value" />-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<table v-if="gridData.length" class="grid-cell-class">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Edge / Fog Nodes</th>
|
<th>Node</th>
|
||||||
<th v-for="(values, column) in gridData" :key="column">{{ values.title }}</th>
|
<th v-for="(criterion, index) in gridData[0].criteria" :key="index">{{ criterion.title }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="index in rowCount" :key="index">
|
<tr v-for="(entry, entryIndex) in gridData" :key="entry.name">
|
||||||
<!-- (-1) Because there is a different indexing in the gridData and the fog node titles that starts from 0 -->
|
<td>{{ entry.name }}</td>
|
||||||
<td>{{ fogNodesTitles[index-1] }}</td>
|
<td v-for="(criterion, criterionIndex) in entry.criteria" :key="`${entry.name}-${criterionIndex}`">
|
||||||
<td v-for="(values, column) in gridData" :key="`${column}-${index}`">
|
<input v-if="criterion.data_type.type === 2" type="number" v-model="criterion.value" />
|
||||||
<select v-if="Ordinal_Variables.includes(column)" v-model="values.data_values[index - 1]">
|
<select v-else-if="criterion.data_type.type === 1" v-model="criterion.value">
|
||||||
<option v-for="option in dropdownOptions" :value="option" :key="option">{{ option }}</option>
|
<option v-for="option in criterion.data_type.values" :value="option" :key="option">{{ option }}</option>
|
||||||
</select>
|
</select>
|
||||||
<input v-else type="text" v-model="values.data_values[index - 1]" />
|
<select v-else-if="criterion.data_type.type === 5" v-model="criterion.value">
|
||||||
|
<option v-for="option in ['True', 'False']" :value="option">{{ option }}</option>
|
||||||
|
</select>
|
||||||
|
<input v-else type="text" v-model="criterion.value" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -28,58 +71,62 @@
|
|||||||
No data to display.
|
No data to display.
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4"></div>
|
<div class="pt-4"></div>
|
||||||
<!-- <button @click="SaveDataforEvaluation" class="bg-color-primary">Save and Run Evaluation</button> -->
|
<button @click="goBackToCriteriaSelection" class="bg-color-primary">Back to Criteria Selection</button>
|
||||||
<button @click="SaveDataforWR" class="bg-color-primary">Save and Add Weight Restrictions</button>
|
<button @click="SaveDataforWR" class="bg-color-primary">Save and Add Weight Restrictions</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useRouter } from 'vue-router';
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fogNodesTitles: [],
|
NodeNames: [],
|
||||||
gridData: [], // Data for the grid
|
gridData: [], // Updated to be an array to match the structure provided by the backend
|
||||||
selectedItemsFromBack: [],
|
|
||||||
Ordinal_Variables: ['attr-reputation', 'attr-assurance', 'attr-security'],
|
|
||||||
dropdownOptions: ['High', 'Medium', 'Low'], // Options for the dropdown
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const router = useRouter();
|
|
||||||
return {
|
|
||||||
router
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const selectedItems = this.$route.params.selectedItems || [];
|
let selectedItemsWithTypes = this.getSelectedItemsFromStorage();
|
||||||
if (selectedItems.length > 0) {
|
if (!selectedItemsWithTypes.length) {
|
||||||
this.fetchGridData(selectedItems);
|
selectedItemsWithTypes = this.$route.params.selectedItems || [];
|
||||||
}
|
}
|
||||||
this.fetchFogNodesTitles();
|
if (selectedItemsWithTypes.length > 0) {
|
||||||
},
|
this.fetchGridData(selectedItemsWithTypes.map(item => item.name));
|
||||||
computed: {
|
|
||||||
rowCount() {
|
|
||||||
// Check if gridData has any keys and use the first key to find the row count
|
|
||||||
const firstKey = Object.keys(this.gridData)[0];
|
|
||||||
return firstKey ? this.gridData[firstKey].data_values.length : 0;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Receives the Grid Data 1st time
|
getSelectedItemsFromStorage() {
|
||||||
|
const storedItems = localStorage.getItem('selectedCriteria');
|
||||||
|
return storedItems ? JSON.parse(storedItems) : [];
|
||||||
|
},
|
||||||
async fetchGridData(selectedItems) {
|
async fetchGridData(selectedItems) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://127.0.0.1:5000/process_selected_items', {
|
// Retrieve app_id and user_id from local storage directly within this method
|
||||||
|
const app_id = localStorage.getItem('fog_broker_app_id');
|
||||||
|
const user_id = localStorage.getItem('fog_broker_user_uuid');
|
||||||
|
const response = await fetch(apiURL+'/process_selected_criteria', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({selectedItems}),
|
// body: JSON.stringify({selectedItems}),
|
||||||
|
body: JSON.stringify({
|
||||||
|
selectedItems,
|
||||||
|
app_id, // Include app_id from local storage
|
||||||
|
user_id // Include user_id from local storage
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const criteria_data = await response.json();
|
const { gridData, NodeNames } = await response.json();
|
||||||
this.gridData = criteria_data.gridData; // Assigning the gridData from the response
|
// Initialize data_values for each entry in gridData
|
||||||
console.log('DataGrid.vue received the criteria data from the Backend:', this.gridData); // Log the received grid data
|
this.gridData = gridData.map(entry => ({
|
||||||
|
...entry,
|
||||||
|
data_values: entry.criteria.map(criterion => ({
|
||||||
|
value: criterion.value,
|
||||||
|
data_type: criterion.data_type
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
this.NodeNames = NodeNames || [];
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to fetch grid data');
|
throw new Error('Failed to fetch grid data');
|
||||||
}
|
}
|
||||||
@ -87,81 +134,136 @@ export default {
|
|||||||
console.error('Error fetching grid data:', error);
|
console.error('Error fetching grid data:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchFogNodesTitles() { // Receives the names of fog nodes (grid's 1st column)
|
validateNumeric(entry, criterionIndex) {
|
||||||
fetch('http://127.0.0.1:5000/get-fog-nodes-titles')
|
// Directly modify and validate numeric value within the entry's criteria
|
||||||
.then(response => response.json())
|
const numericValue = parseFloat(entry.criteria[criterionIndex].value);
|
||||||
.then(data => {
|
if (isNaN(numericValue) || numericValue <= 0) {
|
||||||
// 'data' is an array like ['Fog Node 1', 'Fog Node 2', ...]
|
alert('Please enter a number greater than zero.');
|
||||||
this.fogNodesTitles = data;
|
entry.criteria[criterionIndex].value = ''; // Reset invalid value
|
||||||
})
|
return false; // Halt further processing
|
||||||
.catch(error => console.error('Error fetching fog nodes titles:', error));
|
} else {
|
||||||
|
entry.criteria[criterionIndex].value = numericValue; // Update with valid numeric value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
validateGridData() {
|
validateGridData() {
|
||||||
for (const key in this.gridData) {
|
for (const entry of this.gridData) {
|
||||||
if (this.gridData.hasOwnProperty(key)) {
|
for (const criterion of entry.criteria) {
|
||||||
const dataValues = this.gridData[key].data_values;
|
// Convert value to string to handle trimming and empty checks
|
||||||
for (const value of dataValues) {
|
const valueAsString = String(criterion.value).trim();
|
||||||
if (value === 0 || value === null || value === '') {
|
|
||||||
return false; // Invalid data found
|
switch (criterion.data_type.type) {
|
||||||
}
|
case 2: // Numeric
|
||||||
|
const numericValue = parseFloat(valueAsString);
|
||||||
|
if (isNaN(numericValue) || numericValue <= 0) {
|
||||||
|
alert('Please enter a valid number for all numeric fields.');
|
||||||
|
return false; // Prevent further processing
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Ordinal
|
||||||
|
if (!criterion.data_type.values.includes(criterion.value)) {
|
||||||
|
alert(`Please select a valid option for ${criterion.title}.`);
|
||||||
|
return false; // Prevent further processing
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: // Boolean
|
||||||
|
if (!["True", "False"].includes(valueAsString)) {
|
||||||
|
alert(`Please select a valid boolean value for ${criterion.title}.`);
|
||||||
|
return false; // Prevent further processing
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Check for empty values for any other data types
|
||||||
|
if (valueAsString === '') {
|
||||||
|
alert(`Please ensure all fields are filled for ${criterion.title}.`);
|
||||||
|
return false; // Prevent further processing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true; // All data is valid
|
return true; // All validations passed
|
||||||
},
|
},
|
||||||
async SaveDataforWR() {
|
goBackToCriteriaSelection() {
|
||||||
if (!this.validateGridData()) {
|
this.$router.push({ name: 'CriteriaSelection' });
|
||||||
alert('Invalid input: Zero or null values are not accepted.');
|
|
||||||
return; // Stop submission if validation fails
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
try {
|
|
||||||
const DataforWR = JSON.stringify({
|
|
||||||
gridData: this.gridData
|
|
||||||
});
|
|
||||||
// Navigate to WR component with data
|
|
||||||
this.router.push({ name: 'WR', params: { data: DataforWR } });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async SaveDataforEvaluation() {
|
async SaveDataforWR() {
|
||||||
|
if (!this.validateGridData()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Log the current state of gridData
|
||||||
|
console.log("Before saving, gridData is:", JSON.parse(JSON.stringify(this.gridData)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formattedGridData = this.gridData.map(node => ({
|
||||||
|
name: node.name,
|
||||||
|
id: node.id,
|
||||||
|
criteria: node.criteria.map(criterion => ({
|
||||||
|
title: criterion.title,
|
||||||
|
value: criterion.value,
|
||||||
|
data_type: criterion.data_type.type
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
const DataforWR = JSON.stringify(formattedGridData);
|
||||||
|
localStorage.setItem('gridData', DataforWR); // Save gridData to localStorage
|
||||||
|
// console.log("Save DataforWR DataGrid.VUE to localstorage:", JSON.stringify(JSON.parse(DataforWR), null, 2));
|
||||||
|
|
||||||
|
// Save the NodeNames to localStorage
|
||||||
|
const NodeNames = JSON.stringify(this.NodeNames);
|
||||||
|
localStorage.setItem('NodeNames', NodeNames);
|
||||||
|
|
||||||
|
// Navigate to WR component with prepared data and NodeNames
|
||||||
|
this.$router.push({
|
||||||
|
name: 'WR',
|
||||||
|
params: {
|
||||||
|
data: DataforWR,
|
||||||
|
NodeNames: NodeNames // Include NodeNames in the route parameters
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Basic table styling */
|
/* Basic table styling */
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header styling */
|
/* Header styling */
|
||||||
th {
|
th {
|
||||||
background-color: #813F8F; /* Primary color */
|
background-color: #232d45; /* Primary color */
|
||||||
color: #FFFFFF; /* White text */
|
color: #FFFFFF; /* White text */
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Row styling */
|
/* Row styling */
|
||||||
td {
|
td {
|
||||||
background-color: #E7E7E7; /* Light grey */
|
background-color: #E7E7E7; /* Light grey */
|
||||||
color: #374591; /* Secondary color */
|
color: #172135; /* Secondary color */
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alternate row colors for better readability */
|
/* Alternate row colors for better readability */
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background-color: #E4DCD5; /* Light tan */
|
background-color: #155e75; /* Light tan */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover effect on rows */
|
/* Hover effect on rows */
|
||||||
tr:hover {
|
tr:hover {
|
||||||
background-color: #6FBFFF; /* Light blue */
|
background-color: #6FBFFF; /* Light blue */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Additional styles for editable input fields in the table */
|
/* Additional styles for editable input fields in the table */
|
||||||
@ -200,10 +302,11 @@ button:hover {
|
|||||||
select {
|
select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #1b253b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-cell-class {
|
.grid-cell-class {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -3,35 +3,37 @@
|
|||||||
<form v-if="!isChild" @submit.prevent="submitSelection">
|
<form v-if="!isChild" @submit.prevent="submitSelection">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li v-for="item in items" :key="item.name" class="list-group-item criteria-card">
|
<li v-for="item in items" :key="item.name" class="list-group-item criteria-card">
|
||||||
<span v-if="item.children.length > 0" @click="toggleCategory(item)" class="float-end" v-bind:title="'Expand ' + item.title"><i class="bi bi-arrow-bar-down" v-bind:aria-label="'Expand ' + item.title"></i></span>
|
<span v-if="item.children.length > 0" @click="toggleCategory(item)" class="float-end" v-bind:title="'Expand ' + item.title">
|
||||||
|
<i class="bi bi-arrow-bar-down" v-bind:aria-label="'Expand ' + item.title"></i>
|
||||||
|
</span>
|
||||||
<label>
|
<label>
|
||||||
<!--
|
<!-- <input v-model="item.checked" type="checkbox" @change="handleCheckboxChange(item)" /> -->
|
||||||
<input v-model="item.checked" type="checkbox" @change="handleCheckboxChange(item)" />
|
|
||||||
<span @click="toggleCategory(item)" v-bind:title="item.description">{{ item.title }}</span> -->
|
|
||||||
<input type="checkbox" :checked="item.checked" @change="() => handleCheckboxChange(item)" />
|
<input type="checkbox" :checked="item.checked" @change="() => handleCheckboxChange(item)" />
|
||||||
<span @click="toggleCategory(item)">{{ item.title }}</span>
|
<span @click="toggleCategory(item)" v-bind:title="item.description"> {{ item.title }} ({{ getTypeName(item.type) }}) </span>
|
||||||
</label>
|
</label>
|
||||||
<ul v-show="item.showChildren" class="list-group">
|
<ul v-show="item.showChildren" class="list-group">
|
||||||
<!-- Recursive call without Submit button -->
|
<!-- Recursive call without Submit button -->
|
||||||
<HierarchicalCategoryList :isChild="true" :items="item.children" @selected-items="updateSelectedItems" />
|
<!-- <HierarchicalCategoryList :isChild="true" :items="item.children" @selected-items="updateSelectedItems" /> -->
|
||||||
|
<HierarchicalCategoryList :isChild="true" :items="item.children" />
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<button @click="goBackToHome" class="bg-color-primary">Back</button>
|
||||||
<!-- Submit button outside the recursive structure -->
|
<!-- Submit button outside the recursive structure -->
|
||||||
<button type="submit" class="bg-color-primary">Submit</button>
|
<button type="submit" class="bg-color-primary">Next</button>
|
||||||
</form>
|
</form>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<li v-for="item in items" :key="item.name" class="list-group-item criteria-card">
|
<li v-for="item in items" :key="item.name" class="list-group-item criteria-card">
|
||||||
<span v-if="item.children.length > 0" @click="toggleCategory(item)" class="float-end" v-bind:title="'Expand ' + item.title"><i class="bi bi-arrow-bar-down" v-bind:aria-label="'Expand ' + item.title"></i></span>
|
<span v-if="item.children.length > 0" @click="toggleCategory(item)" class="float-end" v-bind:title="'Expand ' + item.title"><i class="bi bi-arrow-bar-down" v-bind:aria-label="'Expand ' + item.title"></i></span>
|
||||||
<label>
|
<label>
|
||||||
<!-- <input v-model="item.checked" type="checkbox" @change="handleCheckboxChange(item)" />
|
<!-- <input v-model="item.checked" type="checkbox" @change="handleCheckboxChange(item)" />-->
|
||||||
<span @click="toggleCategory(item)" v-bind:title="item.description">{{ item.title }}</span> -->
|
|
||||||
<input type="checkbox" :checked="item.checked" @change="() => handleCheckboxChange(item)" />
|
<input type="checkbox" :checked="item.checked" @change="() => handleCheckboxChange(item)" />
|
||||||
<span @click="toggleCategory(item)">{{ item.title }}</span>
|
<span @click="toggleCategory(item)" v-bind:title="item.description"> {{ item.title }} ({{ getTypeName(item.type) }}) </span>
|
||||||
</label>
|
</label>
|
||||||
<ul v-show="item.showChildren">
|
<ul v-show="item.showChildren">
|
||||||
<!-- Recursive call without Submit button -->
|
<!-- Recursive call without Submit button
|
||||||
<HierarchicalCategoryList :isChild="true" :items="item.children" @selected-items="updateSelectedItems" />
|
<HierarchicalCategoryList :isChild="true" :items="item.children" @selected-items="updateSelectedItems" /> -->
|
||||||
|
<HierarchicalCategoryList :isChild="true" :items="item.children" />
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
@ -46,11 +48,12 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
updateItemType: Function,
|
||||||
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
localSelectedItems: [],
|
localSelectedItems: [],
|
||||||
selectedItemsFromBack: [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -95,50 +98,91 @@ export default {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
setChildrenVisibility(items, visible) {
|
||||||
|
items.forEach(item => {
|
||||||
|
item.showChildren = visible;
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
this.setChildrenVisibility(item.children, visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
updateSelectedItems() {
|
updateSelectedItems() {
|
||||||
// Update the selected items list
|
// Update the selected items list with additional type information
|
||||||
this.localSelectedItems = this.collectSelectedItems(this.items);
|
const selectedItemsWithType = this.items
|
||||||
// Emit the updated list
|
.filter(item => item.checked)
|
||||||
this.$emit('selected-items', this.localSelectedItems);
|
.map(item => ({ name: item.name, type: item.type }));
|
||||||
|
},
|
||||||
|
getTypeName(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 2: return 'Numeric';
|
||||||
|
case 1: return 'Ordinal';
|
||||||
|
case 5: return 'Boolean';
|
||||||
|
case 7: return 'Ordinal';
|
||||||
|
default: return 'Numeric';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
collectSelectedItems(items) {
|
collectSelectedItems(items) {
|
||||||
let selectedItems = [];
|
let selectedItems = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.checked) {
|
if (item.checked) {
|
||||||
selectedItems.push(item.name);
|
console.log(`Selected item: ${item.name}, Type: ${item.type}, Title: ${item.title}`);
|
||||||
|
selectedItems.push({ name: item.name, type: item.type, title: item.title });
|
||||||
|
//console.log('Selected items in collectSelectedItems:', selectedItems); // Log selected items
|
||||||
}
|
}
|
||||||
if (item.children) {
|
if (item.children && item.children.length > 0) {
|
||||||
selectedItems = selectedItems.concat(this.collectSelectedItems(item.children));
|
const childSelectedItems = this.collectSelectedItems(item.children);
|
||||||
|
selectedItems = selectedItems.concat(childSelectedItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return selectedItems;
|
return selectedItems;
|
||||||
},
|
},
|
||||||
submitSelection() {
|
async submitSelection() {
|
||||||
const selectedItems = this.collectSelectedItems(this.items);
|
const selectedItems = this.collectSelectedItems(this.items);
|
||||||
|
//console.log('Selected items in Submit:', selectedItems); // Log selected items
|
||||||
|
|
||||||
if (selectedItems.length < 2) {
|
let nonBooleanCriteriaCount = 0;
|
||||||
alert('Please select at least two items before submitting.');
|
let selectedItemsWithType = selectedItems.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
type: item.type,
|
||||||
|
title: item.title
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const item of selectedItemsWithType) {
|
||||||
|
console.log(`Item: ${item.name}, Type: ${item.type}`); // Add this line for debugging
|
||||||
|
if (item.type !== 5) { // Or item.type !== 'Boolean' depending on the actual format
|
||||||
|
nonBooleanCriteriaCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('Non-boolean criteria count:', nonBooleanCriteriaCount); // Log non-boolean criteria count
|
||||||
|
console.log('selectedItemsWithType:', selectedItemsWithType);
|
||||||
|
|
||||||
|
if (selectedItemsWithType.length < 2) {
|
||||||
|
//console.log('Blocking submission due to insufficient criteria selection.');
|
||||||
|
alert('Please select at least two criteria to proceed.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emitting the selected items - useful if there's a parent component listening to this event
|
if (nonBooleanCriteriaCount < 2) {
|
||||||
this.$emit('selected-items', selectedItems);
|
//console.log('Blocking submission due to insufficient non-boolean criteria selection.');
|
||||||
|
alert('Please select at least two non-boolean criteria.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Programmatic navigation to the DataGrid page, passing the selected items as route parameters
|
// Save the selected items with types to Local Storage
|
||||||
this.$router.push({ name: 'DataGrid', params: { selectedItems: selectedItems } });
|
localStorage.setItem('selectedCriteria', JSON.stringify(selectedItemsWithType));
|
||||||
|
|
||||||
|
// Emitting the selected items with types to the DataGrid.vue
|
||||||
|
// this.$emit('selected-items', selectedItemsWithType);
|
||||||
|
|
||||||
|
// Navigate to DataGrid.vue, passing only the item names as route parameters
|
||||||
|
const itemNames = selectedItemsWithType.map(item => item.name);
|
||||||
|
this.$router.push({ name: 'DataGrid', params: { selectedItems: itemNames } });
|
||||||
},
|
},
|
||||||
async postSelectedItems(selectedItems) {
|
goBackToHome() {
|
||||||
const requestOptions = {
|
this.$router.push({ name: 'HomePage' });
|
||||||
method: 'POST',
|
}
|
||||||
headers: {'Content-Type': 'application/json'},
|
}
|
||||||
body: JSON.stringify({selectedItems}),
|
|
||||||
};
|
|
||||||
const response = await fetch('http://127.0.0.1:5000/process_selected_items', requestOptions);
|
|
||||||
const data = await response.json();
|
|
||||||
//console.log('Send Selected items to back', data);
|
|
||||||
this.selectedItemsFromBack = data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -193,6 +237,8 @@ button {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
@ -217,4 +263,5 @@ ul {
|
|||||||
background-color: var(--light-gray-color);
|
background-color: var(--light-gray-color);
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,18 +1,45 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "HomePage"
|
name: "HomePage",
|
||||||
|
// methods: {
|
||||||
|
// async saveIds(appId, userId) {
|
||||||
|
// fetch(' http://127.0.0.1:5000/save_ids', {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// },
|
||||||
|
// body: JSON.stringify({
|
||||||
|
// app_id: appId,
|
||||||
|
// user_id: userId,
|
||||||
|
// }),
|
||||||
|
// })
|
||||||
|
// .then(response => response.json())
|
||||||
|
// .then(data => {
|
||||||
|
// console.log('Success:', data);
|
||||||
|
// // Save to local storage
|
||||||
|
// localStorage.setItem('app_id', appId);
|
||||||
|
// localStorage.setItem('user_id', userId);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// console.error('Error:', error);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// mounted() {
|
||||||
|
// // Example usage
|
||||||
|
// // this.saveIds('d535cf554ea66fbebfc415ac837a5828', 'e3ff4006-be5f-4e00-bbe1-e49a88b2541a');
|
||||||
|
// },
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row p-4 text-center">
|
<div class="row p-4 text-center">
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
<h1 class="display-2">Welcome to <span style="color: var(--main-color);">NebulOus</span></h1>
|
<h1 class="display-2">Welcome to <span style="color: var(--main-color);">Cloud Fog Service Broker</span></h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col col-12 col-md-6 col-lg-6">
|
<div class="col col-12 col-md-6 col-lg-6">
|
||||||
<p class="lead">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
|
<p class="lead">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
|
||||||
@ -30,7 +57,7 @@ export default {
|
|||||||
<p class="lead">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
|
<p class="lead">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
-->
|
||||||
<div class="spacer-sm"></div>
|
<div class="spacer-sm"></div>
|
||||||
|
|
||||||
<div class="row text-center p-4 bg-row border-radius-sm">
|
<div class="row text-center p-4 bg-row border-radius-sm">
|
||||||
@ -43,7 +70,7 @@ export default {
|
|||||||
|
|
||||||
<div class="row p-4 text-center">
|
<div class="row p-4 text-center">
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
<h2 class="display-4">How does it work</h2>
|
<h2 class="display-4">Architecture</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
<img src="/images/Broker.png" class="img-fluid border-radius-md" alt="...">
|
<img src="/images/Broker.png" class="img-fluid border-radius-md" alt="...">
|
||||||
@ -54,9 +81,16 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
button-primary:hover{
|
||||||
.bg-row {
|
border: 2px #172135;
|
||||||
background-color: var(--secondary-color);
|
}
|
||||||
|
.bg-row {
|
||||||
|
background-color: #e9ebed;
|
||||||
|
}
|
||||||
|
.row{text-align: justify;
|
||||||
|
}
|
||||||
|
.img-fluid {
|
||||||
|
max-width: 75%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -1,13 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="results-container">
|
<div class="results-container">
|
||||||
<h2>Evaluation Results</h2>
|
<h2>Evaluation Results</h2>
|
||||||
|
<p class="description">
|
||||||
|
The scores have been rounded to the nearest two decimal places.
|
||||||
|
</p>
|
||||||
<div v-if="loading" class="loading">Loading...</div>
|
<div v-if="loading" class="loading">Loading...</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<!-- Table for displaying the results -->
|
<!-- Table for displaying the results -->
|
||||||
<table v-if="results.length > 0">
|
<table v-if="results.length > 0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Fog Node</th>
|
<th>Node</th>
|
||||||
<th>Score (%)</th>
|
<th>Score (%)</th>
|
||||||
<th>Ranking</th>
|
<th>Ranking</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -20,6 +23,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<!-- Separator Line -->
|
||||||
|
<div class="separator-line"></div>
|
||||||
<!-- Chart Container -->
|
<!-- Chart Container -->
|
||||||
<div class="charts-container">
|
<div class="charts-container">
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
@ -29,179 +34,270 @@
|
|||||||
<canvas id="ranksChart"></canvas>
|
<canvas id="ranksChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Separator Line -->
|
||||||
|
<div class="separator-line"></div>
|
||||||
|
<div class="button-container">
|
||||||
|
<button @click="goBackToWR">Add/Modify Weight Restrictions</button>
|
||||||
|
<button @click="saveProjectResults">Save Project</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-container">
|
</template>
|
||||||
<button @click="goBackToWR">Add/Modify Weight Restrictions</button>
|
|
||||||
<button @click="saveProjectResults">Save Project</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Chart from 'chart.js/auto';
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
|
import Chart from 'chart.js/auto';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
results: [],
|
results: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
deaScoresChart: null,
|
deaScoresChart: null,
|
||||||
ranksChart: null,
|
ranksChart: null,
|
||||||
};
|
gridData: null,
|
||||||
},
|
relativeWRData: null,
|
||||||
mounted() {
|
immediateWRData: null
|
||||||
this.fetchResults();
|
};
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
goBackToWR() {
|
|
||||||
// Make sure 'WR' matches the name of the route in your router configuration
|
|
||||||
this.$router.push({ name: 'WR' });
|
|
||||||
},
|
},
|
||||||
saveProjectResults() {
|
mounted() {
|
||||||
// For now, this method is a placeholder
|
const resultsString = localStorage.getItem('evaluationResults');
|
||||||
console.log('Save Project Results button clicked');
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(resultsString);
|
||||||
|
if (data && data.results) {
|
||||||
|
this.results = data.results;
|
||||||
|
this.createCharts();
|
||||||
|
this.loading = false;
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching results: Data is not in the expected format.');
|
||||||
|
this.loading = false;
|
||||||
|
// Handle the error by navigating to a different page or displaying an error message
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing JSON:', error);
|
||||||
|
// Handle parsing error by navigating to a different page or displaying an error message
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fetchResults() {
|
methods: {
|
||||||
fetch('http://127.0.0.1:5000/get-evaluation-results')
|
goBackToWR() {
|
||||||
.then(response => response.json())
|
// Make sure 'WR' matches the name of the route in your router configuration
|
||||||
.then(data => {
|
this.$router.push({ name: 'WR' });
|
||||||
this.results = data;
|
},
|
||||||
this.loading = false;
|
async saveProjectResults() {
|
||||||
this.createCharts();
|
if (confirm("Save Project?")) {
|
||||||
})
|
console.log('Save Project Results button clicked');
|
||||||
.catch(error => {
|
let array_data = []
|
||||||
console.error('Error fetching results:', error);
|
// Application Id
|
||||||
this.loading = false;
|
let app_id = localStorage.getItem('fog_broker_app_id');
|
||||||
|
let appData = [
|
||||||
|
{app_id: app_id}
|
||||||
|
];
|
||||||
|
array_data.push(appData);
|
||||||
|
|
||||||
|
// Node Names
|
||||||
|
let NodeNamesFromStorage = localStorage.getItem('NodeNames');
|
||||||
|
let NodeNames = JSON.parse(NodeNamesFromStorage);
|
||||||
|
array_data.push(NodeNames);
|
||||||
|
|
||||||
|
// Selected Criteria
|
||||||
|
let selectedCriteriaFromStorage = localStorage.getItem('selectedCriteria');
|
||||||
|
let selectedCriteria = JSON.parse(selectedCriteriaFromStorage);
|
||||||
|
array_data.push(selectedCriteria);
|
||||||
|
|
||||||
|
// DataGrid Data
|
||||||
|
let gridDataFromStorage = localStorage.getItem('gridData');
|
||||||
|
let GridData = JSON.parse(gridDataFromStorage);
|
||||||
|
array_data.push(GridData);
|
||||||
|
// relativeWRData
|
||||||
|
let relativeWRDataFromStorage = localStorage.getItem('relativeWRData');
|
||||||
|
let relativeWRData = JSON.parse(relativeWRDataFromStorage);
|
||||||
|
array_data.push(relativeWRData);
|
||||||
|
//immediateWRData
|
||||||
|
let immediateWRDataFromStorage = localStorage.getItem('immediateWRData');
|
||||||
|
let immediateWRData = JSON.parse(immediateWRDataFromStorage);
|
||||||
|
array_data.push(immediateWRData);
|
||||||
|
// evaluation Results
|
||||||
|
let evaluationResultsFromStorage = localStorage.getItem('evaluationResults');
|
||||||
|
let evaluationResults = JSON.parse(evaluationResultsFromStorage);
|
||||||
|
array_data.push(evaluationResults.results); // Save only th results not the LPStatus
|
||||||
|
|
||||||
|
let result = await this.saveProjectData(array_data);
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
// Clear local storage
|
||||||
|
localStorage.removeItem('evaluationResults');
|
||||||
|
localStorage.removeItem('selectedCriteria');
|
||||||
|
localStorage.removeItem('NodeNames');
|
||||||
|
localStorage.removeItem('gridData');
|
||||||
|
localStorage.removeItem('relativeWRData');
|
||||||
|
localStorage.removeItem('immediateWRData');
|
||||||
|
// localStorage.removeItem('fog_broker_user_uuid'); May keep them so the user can evaluate again
|
||||||
|
// localStorage.removeItem('fog_broker_app_id');
|
||||||
|
|
||||||
|
// Redirect to the Home page
|
||||||
|
this.$router.push({ name: 'HomePage' });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('Project not saved.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveProjectData(data) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL+'/app/save', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
},
|
console.log("response" + response);
|
||||||
createCharts() {
|
let response_data = await response.json();
|
||||||
const titles = this.results.map(result => result.Title);
|
console.log(response_data);
|
||||||
const deaScores = this.results.map(result => result['DEA Score']);
|
} catch (error) {
|
||||||
const ranks = this.results.map(result => result.Rank);
|
console.error('Error saving project:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createCharts() {
|
||||||
|
if (!this.results || this.results.length === 0) {
|
||||||
|
console.error('No results data available to create charts.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(this.results);
|
||||||
|
|
||||||
this.$nextTick(() => {
|
const titles = this.results.map(result => result.Title);
|
||||||
this.createBarChart(titles, deaScores, 'deascoresChart', 'Fog Node Scores');
|
const deaScores = this.results.map(result => result['DEA Score']);
|
||||||
this.createHorizontalBarChart(titles, ranks, 'ranksChart', 'Fog Node Ranking');
|
const ranks = this.results.map(result => result.Rank);
|
||||||
});
|
|
||||||
},
|
this.$nextTick(() => {
|
||||||
createBarChart(labels, data, chartId, label) {
|
this.createBarChart(titles, deaScores, 'deascoresChart', 'Fog Node Scores');
|
||||||
const ctx = document.getElementById(chartId).getContext('2d');
|
this.createHorizontalBarChart(titles, ranks, 'ranksChart', 'Fog Node Ranking');
|
||||||
if (this.deaScoresChart) {
|
});
|
||||||
this.deaScoresChart.destroy();
|
},
|
||||||
}
|
createBarChart(labels, data, chartId, label) {
|
||||||
this.deaScoresChart = new Chart(ctx, {
|
const ctx = document.getElementById(chartId).getContext('2d');
|
||||||
type: 'bar',
|
if (this.deaScoresChart) {
|
||||||
data: {
|
this.deaScoresChart.destroy();
|
||||||
labels,
|
}
|
||||||
datasets: [{
|
this.deaScoresChart = new Chart(ctx, {
|
||||||
label,
|
type: 'bar',
|
||||||
data,
|
data: {
|
||||||
backgroundColor: 'rgba(181,141,243,0.56)',
|
labels,
|
||||||
borderColor: 'rgb(102,16,242)',
|
datasets: [{
|
||||||
borderWidth: 1
|
label,
|
||||||
}]
|
data,
|
||||||
},
|
backgroundColor: 'rgba(181,141,243,0.56)',
|
||||||
options: {
|
borderColor: 'rgb(102,16,242)',
|
||||||
responsive: true,
|
borderWidth: 1
|
||||||
//maintainAspectRatio: false,
|
}]
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
max: 1, // Set the maximum value of the y-axis to 1
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: {
|
options: {
|
||||||
tooltip: {
|
responsive: true,
|
||||||
callbacks: {
|
//maintainAspectRatio: false,
|
||||||
label: function(tooltipItem) {
|
scales: {
|
||||||
let score = tooltipItem.raw * 100;
|
y: {
|
||||||
return `Score: ${score.toFixed(2)}%`;
|
beginAtZero: true,
|
||||||
}
|
max: 1, // Set the maximum value of the y-axis to 1
|
||||||
},
|
}
|
||||||
bodyFontSize: 16, // Adjust font size as needed
|
},
|
||||||
titleFontSize: 16
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(tooltipItem) {
|
||||||
|
let score = tooltipItem.raw * 100;
|
||||||
|
return `Score: ${score.toFixed(2)}%`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bodyFontSize: 16, // Adjust font size as needed
|
||||||
|
titleFontSize: 16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createHorizontalBarChart(labels, data, chartId, label) {
|
||||||
|
const ctx = document.getElementById(chartId).getContext('2d');
|
||||||
|
if (this.ranksChart) {
|
||||||
|
this.ranksChart.destroy();
|
||||||
}
|
}
|
||||||
});
|
// Assuming higher scores should have longer bars, so we invert the scores
|
||||||
},
|
// as higher rank should have lower numerical value
|
||||||
createHorizontalBarChart(labels, data, chartId, label) {
|
const invertedData = data.map(score => Math.max(...data) - score + Math.min(...data));
|
||||||
const ctx = document.getElementById(chartId).getContext('2d');
|
this.ranksChart = new Chart(ctx, {
|
||||||
if (this.ranksChart) {
|
type: 'bar', // In Chart.js 3.x, you specify horizontal bars using indexAxis
|
||||||
this.ranksChart.destroy();
|
data: {
|
||||||
}
|
labels,
|
||||||
// Assuming higher scores should have longer bars, so we invert the scores
|
datasets: [{
|
||||||
// as higher rank should have lower numerical value
|
label,
|
||||||
const invertedData = data.map(score => Math.max(...data) - score + Math.min(...data));
|
data: invertedData,
|
||||||
this.ranksChart = new Chart(ctx, {
|
backgroundColor: 'rgba(110,108,229,0.55)',
|
||||||
type: 'bar', // In Chart.js 3.x, you specify horizontal bars using indexAxis
|
borderColor: 'rgb(60,54,235)',
|
||||||
data: {
|
borderWidth: 1
|
||||||
labels,
|
}]
|
||||||
datasets: [{
|
|
||||||
label,
|
|
||||||
data: invertedData,
|
|
||||||
backgroundColor: 'rgba(110,108,229,0.55)',
|
|
||||||
borderColor: 'rgb(60,54,235)',
|
|
||||||
borderWidth: 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
//maintainAspectRatio: false, // Set to false to allow full width and controlled height
|
|
||||||
indexAxis: 'y', // This will make the bar chart horizontal
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
beginAtZero: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
plugins: {
|
options: {
|
||||||
tooltip: {
|
responsive: true,
|
||||||
callbacks: {
|
//maintainAspectRatio: false, // Set to false to allow full width and controlled height
|
||||||
label: function(context) {
|
indexAxis: 'y', // This makes the bar chart horizontal
|
||||||
// Since we inverted the data, we need to correct the value displayed in the tooltip
|
scales: {
|
||||||
const rankValue = Math.max(...data) - context.parsed.x + Math.min(...data);
|
x: {
|
||||||
return `Rank: ${rankValue}`;
|
beginAtZero: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bodyFontSize: 16, // Adjust font size as needed
|
plugins: {
|
||||||
titleFontSize: 16
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
// Since we inverted the data, we need to correct the value displayed in the tooltip
|
||||||
|
const rankValue = Math.max(...data) - context.parsed.x + Math.min(...data);
|
||||||
|
return `Rank: ${rankValue}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bodyFontSize: 16, // Adjust font size as needed
|
||||||
|
titleFontSize: 16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
},
|
||||||
},
|
formatPercentage(value) {
|
||||||
formatPercentage(value) {
|
const percentage = (value * 100).toFixed(2);
|
||||||
const percentage = (value * 100).toFixed(2);
|
return percentage === '100.00' ? '100%' : `${percentage}%`;
|
||||||
return percentage === '100.00' ? '100%' : `${percentage}%`;
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.results-container {
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
.loading {
|
||||||
.results-container {
|
text-align: center;
|
||||||
padding: 20px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
.charts-container {
|
||||||
text-align: center;
|
display: flex;
|
||||||
}
|
flex-direction: row; /* Align charts horizontally */
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 0 20px; /* Add padding if needed */
|
||||||
|
}
|
||||||
|
|
||||||
.charts-container {
|
.chart-wrapper {
|
||||||
display: flex;
|
flex: 1; /* Each chart will take equal space */
|
||||||
flex-direction: row; /* Align charts horizontally */
|
/* Remove max-width or set it to a higher value if you want a specific limit */
|
||||||
justify-content: space-around;
|
margin: auto;
|
||||||
padding: 0 20px; /* Add padding if needed */
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.chart-wrapper {
|
td {
|
||||||
flex: 1; /* Each chart will take equal space */
|
text-align: center;
|
||||||
/* Remove max-width or set it to a higher value if you want a specific limit */
|
}
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
.separator-line {
|
||||||
text-align: center;
|
height: 4px; /* Thickness of the line */
|
||||||
}
|
background-color: #172135; /* Deep purple color */
|
||||||
|
margin: 10px 0; /* Spacing above and below the line */
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,97 +1,177 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wr-container">
|
<div class="wr-container">
|
||||||
<div v-for="(condition, index) in conditions" :key="index" class="condition-row">
|
<!-- Relative constraints section -->
|
||||||
<select v-model="condition.column1" @change="updateDropdowns(index)">
|
<div class="relative-constraints">
|
||||||
<option value="" disabled>Select Criterion</option>
|
<h2>Relative Constraints</h2>
|
||||||
<option v-for="col in availableColumns(index, 1)" :key="`1-${col}`" :value="col">{{ col }}</option>
|
<p class="description">
|
||||||
</select>
|
Set relative constraints between the criteria. For example, "Weight of Criterion A >= 2* Weight of Criterion B".
|
||||||
|
</p>
|
||||||
|
<div v-for="(condition, index) in relativeConditions" :key="index" class="condition-row">
|
||||||
|
<select v-model="condition.column1" @change="updateDropdowns(index)">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in availableColumns(index, 1)" :key="`1-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<select v-model="condition.operator">
|
<select v-model="condition.operator">
|
||||||
<option value="" disabled>Select Operator</option>
|
<option value="" disabled>Select Operator</option>
|
||||||
<option v-for="op in operators" :key="op" :value="op">{{ op }}</option>
|
<option v-for="op in operators" :key="op" :value="op">{{ op }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input type="number" v-model.number="condition.value" :min="0" placeholder="Value" />
|
<!-- <select v-model="condition.operator">-->
|
||||||
|
<!-- <option value="" disabled>Select Operator</option>-->
|
||||||
|
<!-- <option v-for="(value, key) in operatorMapping" :key="key" :value="value">{{ key }}</option>-->
|
||||||
|
<!-- </select>-->
|
||||||
|
|
||||||
<select v-model="condition.column2" @change="updateDropdowns(index)">
|
<input type="number" v-model.number="condition.value" :min="0" step="0.5" placeholder="Value" />
|
||||||
<option value="" disabled>Select Criterion</option>
|
|
||||||
<option v-for="col in availableColumns(index, 2)" :key="`2-${col}`" :value="col">{{ col }}</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button @click="removeCondition(index)">-</button>
|
<select v-model="condition.column2" @change="updateDropdowns(index)">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in availableColumns(index, 2)" :key="`2-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
<button @click="removeCondition(index)">-</button>
|
||||||
|
</div>
|
||||||
|
<button @click="addCondition">+ Add Relative Constraint</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Separator Line -->
|
||||||
|
<div class="separator-line"></div>
|
||||||
|
<div class="immediate-constraints">
|
||||||
|
<h2>Immediate Constraints</h2>
|
||||||
|
<p class="description">
|
||||||
|
Set immediate constraints on individual criteria. For example, "Weight of Criterion A >= 0.25".
|
||||||
|
</p>
|
||||||
|
<div v-for="(immediateCondition, index) in immediateConditions" :key="`immediate-${index}`" class="condition-row">
|
||||||
|
<select v-model="immediateCondition.criterion">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in criteria_titles" :key="`immediate-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<button @click="addCondition">+</button>
|
<select v-model="immediateCondition.operator">
|
||||||
<!-- <button @click.prevent="sendWRData">Run Evaluation</button> -->
|
<option value="" disabled>Select Operator</option>
|
||||||
<button @click="sendWRData">Run Evaluation</button>
|
<option v-for="op in operators" :key="op" :value="op">{{ op }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- <select v-model="immediateCondition.operator">-->
|
||||||
|
<!-- <option value="" disabled>Select Operator</option>-->
|
||||||
|
<!-- <option v-for="(value, key) in operatorMapping" :key="key" :value="value">{{ key }}</option>-->
|
||||||
|
<!-- </select>-->
|
||||||
|
|
||||||
|
<input type="number" v-model.number="immediateCondition.value" :min="0" step="0.1" placeholder="Value" />
|
||||||
|
|
||||||
|
<button @click="removeImmediateCondition(index)">-</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="addImmediateCondition">+ Add Immediate Constraint</button>
|
||||||
|
</div>
|
||||||
|
<!-- Separator Line -->
|
||||||
|
<div class="separator-line"></div>
|
||||||
|
<div class="pt-4"></div>
|
||||||
|
<div class="button-container">
|
||||||
|
<button @click="goBackToCriteriaSelection" class="bg-color-primary">Back to Criteria Selection</button>
|
||||||
|
<button @click="sendWRData" class="bg-color-primary">Run Evaluation</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useRouter } from 'vue-router';
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
receivedGridData: null,
|
receivedGridData: null,
|
||||||
conditions: [{ column1: '', operator: '', value: 0, column2: '' }],
|
relativeConditions: [{ column1: '', operator: '', value: 0, column2: '' }],
|
||||||
criteria_titles: [], // This is populated with the column titles
|
criteria_titles: [], // This is populated with the column titles
|
||||||
operators: ['>=', '=', '<='],
|
operators: ['>=', '=', '<='],
|
||||||
|
immediateConditions: [{ criterion: '', operator: '', value: 0 }],
|
||||||
|
operatorMapping: {
|
||||||
|
'>=': 1,
|
||||||
|
'=': 0,
|
||||||
|
'<=': -1
|
||||||
|
},
|
||||||
|
errorMessage: '', // Add this line
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
// Prioritize data from route parameters
|
||||||
if (this.$route.params.data) {
|
if (this.$route.params.data) {
|
||||||
// Parse the JSON string back into an object
|
// Parse the JSON string back into an object
|
||||||
this.receivedGridData = JSON.parse(this.$route.params.data);
|
this.receivedGridData = JSON.parse(this.$route.params.data);
|
||||||
|
} else {
|
||||||
|
// Fallback to localStorage if route params are not available
|
||||||
|
const gridDataFromStorage = localStorage.getItem('gridData');
|
||||||
|
if (gridDataFromStorage) {
|
||||||
|
this.receivedGridData = JSON.parse(gridDataFromStorage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with other localStorage checks
|
||||||
|
const wrDataFromStorage = localStorage.getItem('wrData');
|
||||||
|
const immediateWRDataFromStorage = localStorage.getItem('immediateWRData');
|
||||||
|
|
||||||
|
if (wrDataFromStorage) {
|
||||||
|
this.wrData = JSON.parse(wrDataFromStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (immediateWRDataFromStorage) {
|
||||||
|
this.immediateConditions = JSON.parse(immediateWRDataFromStorage);
|
||||||
|
} else {
|
||||||
|
this.immediateConditions = [{ criterion: '', operator: '', value: 0 }];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve selectedCriteria from local storage
|
||||||
|
const selectedCriteriaJson = localStorage.getItem('selectedCriteria');
|
||||||
|
if (selectedCriteriaJson) {
|
||||||
|
try {
|
||||||
|
const selectedCriteria = JSON.parse(selectedCriteriaJson);
|
||||||
|
// Use selectedCriteria to populate criteria_titles and filter out boolean criteria (type 5)
|
||||||
|
this.criteria_titles = selectedCriteria
|
||||||
|
.filter(info => info.type !== 5)
|
||||||
|
.map(info => info.title);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing selected criteria information:', e);
|
||||||
|
this.$router.push({ name: 'CriteriaSelection' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Error: Selected criteria information not found in local storage.');
|
||||||
|
this.$router.push({ name: 'CriteriaSelection' });
|
||||||
}
|
}
|
||||||
console.log('WR.vue Received gridData:', this.receivedGridData);
|
|
||||||
this.fetchCriteriaTitles();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchCriteriaTitles() {
|
|
||||||
fetch('http://127.0.0.1:5000/get-criteria-titles')
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
this.criteria_titles = data;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error fetching criteria titles:', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addCondition() {
|
addCondition() {
|
||||||
this.conditions.push({column1: '', column2: '', operator: '', value: 0});
|
this.relativeConditions.push({column1: '', column2: '', operator: '', value: 0});
|
||||||
},
|
},
|
||||||
removeCondition(index) {
|
removeCondition(index) {
|
||||||
this.conditions.splice(index, 1);
|
this.relativeConditions.splice(index, 1);
|
||||||
},
|
},
|
||||||
validateForm() {
|
validateForm() {
|
||||||
for (const condition of this.conditions) {
|
for (const condition of this.relativeConditions) {
|
||||||
|
if (!condition.column1 || !condition.column2) {
|
||||||
|
alert('Please select criteria for each relative constraint.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!condition.operator) {
|
if (!condition.operator) {
|
||||||
alert('Please select an operator for each condition.');
|
alert('Please select an operator for each relative constraint.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (condition.value === null || condition.value === '') {
|
if (condition.value === null || condition.value === '') {
|
||||||
alert('Please enter a numeric value for each.');
|
alert('Please enter a numeric value for each relative constraint.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (condition.value < 0) {
|
if (condition.value <= 0) {
|
||||||
alert('Values cannot be less than zero.');
|
alert('The priority in each relative constraint must be greater than zero.');
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniquePairs = new Set(
|
|
||||||
this.conditions.map(c => [c.column1, c.column2].sort().join('-'))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (uniquePairs.size !== this.conditions.length) {
|
|
||||||
alert('Each pair of criteria can only be used once in a restriction!');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uniquePairs = new Set(
|
||||||
|
this.relativeConditions.map(c => [c.column1, c.column2].sort().join('-'))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uniquePairs.size !== this.relativeConditions.length) {
|
||||||
|
alert('Each pair of criteria can only be used once in a restriction!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
updateDropdowns(index) {
|
updateDropdowns(index) {
|
||||||
@ -100,15 +180,73 @@ export default {
|
|||||||
availableColumns(index, dropdownNumber) {
|
availableColumns(index, dropdownNumber) {
|
||||||
if (dropdownNumber === 1) {
|
if (dropdownNumber === 1) {
|
||||||
// For the first dropdown, filter out the column selected in the second dropdown
|
// For the first dropdown, filter out the column selected in the second dropdown
|
||||||
return this.criteria_titles.filter(col => col !== this.conditions[index].column2);
|
return this.criteria_titles.filter(col => col !== this.relativeConditions[index].column2);
|
||||||
} else {
|
} else {
|
||||||
// For the second dropdown, filter out the column selected in the first dropdown
|
// For the second dropdown, filter out the column selected in the first dropdown
|
||||||
return this.criteria_titles.filter(col => col !== this.conditions[index].column1);
|
return this.criteria_titles.filter(col => col !== this.relativeConditions[index].column1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// Add a method to validate the Immediate Constraints
|
||||||
|
validateImmediateConstraints() {
|
||||||
|
let criterionConstraints = {};
|
||||||
|
|
||||||
|
// Iterate over immediate conditions and organize them by criterion
|
||||||
|
for (const condition of this.immediateConditions) {
|
||||||
|
if (!condition.criterion || !condition.operator) {
|
||||||
|
continue; // Skip empty conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure value is greater than 0
|
||||||
|
if (condition.value === null || condition.value === '' || condition.value <= 0) {
|
||||||
|
alert(`The importance of criterion "${condition.criterion}" should be greater than 0.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the constraints list for the criterion if not already done
|
||||||
|
if (!criterionConstraints[condition.criterion]) {
|
||||||
|
criterionConstraints[condition.criterion] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate operators
|
||||||
|
if (criterionConstraints[condition.criterion][condition.operator]) {
|
||||||
|
alert(`You cannot use the same operator more than once for the criterion "${condition.criterion}".`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the condition to the list for the criterion
|
||||||
|
criterionConstraints[condition.criterion][condition.operator] = condition.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the constraints for each criterion and apply validation rules
|
||||||
|
for (const [criterion, operators] of Object.entries(criterionConstraints)) {
|
||||||
|
// Only one constraint allowed when using '=' operator
|
||||||
|
if (operators['='] !== undefined && Object.keys(operators).length > 1) {
|
||||||
|
alert(`Only one constraint allowed for '${criterion}' when using '=' operator.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate logical consistency between '>=' and '<=' values
|
||||||
|
if (operators['>='] !== undefined && operators['<='] !== undefined) {
|
||||||
|
const greaterThanOrEqualValue = parseFloat(operators['>=']);
|
||||||
|
const lessThanOrEqualValue = parseFloat(operators['<=']);
|
||||||
|
|
||||||
|
if (isNaN(greaterThanOrEqualValue) || isNaN(lessThanOrEqualValue)) {
|
||||||
|
alert(`Invalid numeric values for the criterion "${criterion}".`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greaterThanOrEqualValue > lessThanOrEqualValue) {
|
||||||
|
alert(`For the criterion "${criterion}", the value for '>=' must be less than or equal to the value for '<='.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
validateNonInvertedConditions() {
|
validateNonInvertedConditions() {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
let conditionPairs = this.conditions.map(c => [c.column1, c.column2].sort().join('-'));
|
let conditionPairs = this.relativeConditions.map(c => [c.column1, c.column2].sort().join('-'));
|
||||||
|
|
||||||
// Create a Set for unique pairs
|
// Create a Set for unique pairs
|
||||||
const uniquePairs = new Set(conditionPairs);
|
const uniquePairs = new Set(conditionPairs);
|
||||||
@ -120,94 +258,122 @@ export default {
|
|||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
},
|
},
|
||||||
|
addImmediateCondition() {
|
||||||
|
this.immediateConditions.push({ criterion: '', operator: '', value: 0 });
|
||||||
|
},
|
||||||
|
removeImmediateCondition(index) {
|
||||||
|
this.immediateConditions.splice(index, 1);
|
||||||
|
},
|
||||||
async sendWRData() {
|
async sendWRData() {
|
||||||
// Check if any condition is set
|
// Check if any relative or immediate condition is set
|
||||||
const isAnyConditionSet = this.conditions.some(condition => condition.column1 && condition.column2 && condition.operator);
|
const isAnyRelativeConditionSet = this.relativeConditions.some(condition => condition.column1 && condition.column2 && condition.operator);
|
||||||
|
const isAnyImmediateConditionSet = this.immediateConditions.some(condition => condition.criterion && condition.operator);
|
||||||
|
|
||||||
// If no conditions are set, prompt the user
|
// Filter out incomplete or empty relative constraints
|
||||||
if (!isAnyConditionSet) {
|
const validRelativeConditions = this.relativeConditions.filter(condition => condition.column1 && condition.column2 && condition.operator);
|
||||||
|
// Filter out incomplete or empty immediate constraints
|
||||||
|
const validImmediateConditions = this.immediateConditions.filter(condition => condition.criterion && condition.operator);
|
||||||
|
|
||||||
|
// Prompt the user if no conditions are set
|
||||||
|
if (!isAnyRelativeConditionSet && !isAnyImmediateConditionSet) {
|
||||||
const proceedWithoutWR = confirm("Would you like to proceed without imposing Weight Restrictions?");
|
const proceedWithoutWR = confirm("Would you like to proceed without imposing Weight Restrictions?");
|
||||||
if (!proceedWithoutWR) {
|
if (!proceedWithoutWR) {
|
||||||
// User chose 'No', do nothing to stay on the current page
|
return; // User chose 'No', do nothing
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// User chose 'Yes', proceed with sending data
|
// Clear data if user chose 'Yes'
|
||||||
|
this.relativeConditions = [];
|
||||||
|
this.immediateConditions = [];
|
||||||
} else {
|
} else {
|
||||||
// Validate the form only if there are conditions set
|
// Validate conditions
|
||||||
if (!this.validateForm() || !this.validateNonInvertedConditions()) {
|
if ((isAnyRelativeConditionSet && (!this.validateForm() || !this.validateNonInvertedConditions())) ||
|
||||||
alert('Invalid Weight Restrictions, each pair of criteria can be used only once!');
|
(isAnyImmediateConditionSet && (!this.validateImmediateConstraints()))) {
|
||||||
return; // Stop if validation fails
|
return; // Stop if validation fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const operatorMapping = {
|
// Process Relative constraints
|
||||||
'<=': -1,
|
const relativeWRData = validRelativeConditions.map(condition => ({
|
||||||
'=': 0,
|
LHSCriterion: condition.column1,
|
||||||
'>=': 1
|
Operator: this.operatorMapping[condition.operator],
|
||||||
};
|
Intense: condition.value,
|
||||||
|
RHSCriterion: condition.column2
|
||||||
|
}));
|
||||||
|
|
||||||
const processedWRData = this.conditions.map(condition => {
|
// Process Immediate constraints
|
||||||
return {
|
const immediateWRData = validImmediateConditions.map(condition => ({
|
||||||
LHSCriterion: condition.column1,
|
Criterion: condition.criterion,
|
||||||
Operator: operatorMapping[condition.operator],
|
Operator: this.operatorMapping[condition.operator],
|
||||||
Intense: condition.value,
|
Value: condition.value
|
||||||
RHSCriterion: condition.column2
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Retrieve node names from local storage
|
||||||
|
let nodeNamesArray = [];
|
||||||
|
const NodeNamesString = localStorage.getItem('NodeNames');
|
||||||
|
if (NodeNamesString) {
|
||||||
|
nodeNamesArray = JSON.parse(NodeNamesString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare payload with filtered conditions
|
||||||
const payload = {
|
const payload = {
|
||||||
gridData: this.receivedGridData, // Data received from DataGrid.vue
|
gridData: this.receivedGridData,
|
||||||
wrData: processedWRData
|
relativeWRData: relativeWRData,
|
||||||
|
immediateWRData: immediateWRData,
|
||||||
|
nodeNames: nodeNamesArray
|
||||||
};
|
};
|
||||||
|
console.log('Payload being sent to backend from WR.vue:', payload);
|
||||||
|
|
||||||
|
// Ask the backend to perform evaluation
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://127.0.0.1:5000/process-evaluation-data', {
|
const response = await fetch(apiURL+'/process-evaluation-data', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
if (!response.ok) {
|
||||||
console.log('Response from backend:', data);
|
// If the HTTP response is not OK, throw an error
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the response was successful
|
const data = await response.json();
|
||||||
if (response.ok && data.status === 'success') {
|
console.log('Response from backend process-evaluation-data():', data);
|
||||||
// Redirect to Results.vue
|
console.log('Response data.results.LPstatus:', data.results.LPstatus);
|
||||||
this.$router.push({ name: 'Results' });
|
|
||||||
} /*else {
|
// First, check the general status of the response to confirm the request was processed successfully
|
||||||
// Handle error
|
// Check the LP problem's feasibility status
|
||||||
console.error('Error in response:', data.message);
|
if (data.status === 'success') {
|
||||||
alert('Failed to process data: ' + data.message);
|
if (data.results.LPstatus === 'feasible') {
|
||||||
} */
|
localStorage.setItem('evaluationResults', JSON.stringify(data.results));
|
||||||
|
localStorage.setItem('relativeWRData', JSON.stringify(relativeWRData));
|
||||||
|
localStorage.setItem('immediateWRData', JSON.stringify(immediateWRData));
|
||||||
|
|
||||||
|
// Navigate to Results.vue
|
||||||
|
this.$router.push({ name: 'Results', params: { evaluationResults: data.results.results } });
|
||||||
|
} else if (data.results.LPstatus === 'infeasible') {
|
||||||
|
// Set the error message for infeasible LP solution
|
||||||
|
this.errorMessage = data.results.message; // Accessing the message directly
|
||||||
|
alert(this.errorMessage); // Show the message to the user via alert
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle other unexpected 'status'
|
||||||
|
this.errorMessage = 'An unexpected error occurred.';
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending data to backend:', error);
|
console.error('Error:', error);
|
||||||
alert('Failed to send data to backend.');
|
this.errorMessage = error.message || 'Failed to send data to backend.';
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
sendDataToBackend(payload) {
|
goBackToCriteriaSelection() {
|
||||||
console.log('Sending payload to backend:', payload);
|
this.$router.push({ name: 'CriteriaSelection' });
|
||||||
fetch('http://127.0.0.1:5000/process-evaluation-data', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
console.log('Raw response:', response);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log('Response from backend:', data);
|
|
||||||
// Handle the response from the backend
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error sending data to backend:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
input{height: 40px;}
|
||||||
|
|
||||||
.wr-container {
|
.wr-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -218,6 +384,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@ -233,7 +400,7 @@ button {
|
|||||||
button:hover {
|
button:hover {
|
||||||
background-color: var(--secondary-color); /* Lighter shade of purple on hover */
|
background-color: var(--secondary-color); /* Lighter shade of purple on hover */
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
border:2px;
|
border: 2px;
|
||||||
border-color:var(--main-color);
|
border-color: var(--main-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
357
cfsb-frontend/src/components/WR_AllCriteria.vue
Normal file
357
cfsb-frontend/src/components/WR_AllCriteria.vue
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wr-container">
|
||||||
|
<!-- Relative constraints section -->
|
||||||
|
<div class="relative-constraints">
|
||||||
|
<h3>Relative Constraints</h3>
|
||||||
|
<p class="description">
|
||||||
|
Set relative constraints between the criteria. For example, "Weight of Criterion A >= 2* Weight of Criterion B".
|
||||||
|
</p>
|
||||||
|
<div v-for="(condition, index) in relativeConditions" :key="index" class="condition-row">
|
||||||
|
<select v-model="condition.column1" @change="updateDropdowns(index)">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in availableColumns(index, 1)" :key="`1-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select v-model="condition.operator">
|
||||||
|
<option value="" disabled>Select Operator</option>
|
||||||
|
<option v-for="op in operators" :key="op" :value="op">{{ op }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="number" v-model.number="condition.value" :min="0" step="0.5" placeholder="Value" />
|
||||||
|
|
||||||
|
<select v-model="condition.column2" @change="updateDropdowns(index)">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in availableColumns(index, 2)" :key="`2-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
<button @click="removeCondition(index)">-</button>
|
||||||
|
</div>
|
||||||
|
<button @click="addCondition">+ Add Relative Constraint</button>
|
||||||
|
</div>
|
||||||
|
<div class="immediate-constraints">
|
||||||
|
<h3>Immediate Constraints</h3>
|
||||||
|
<p class="description">
|
||||||
|
Set immediate constraints on individual criteria. For example, "Weight of Criterion A >= 0.25".
|
||||||
|
</p>
|
||||||
|
<div v-for="(immediateCondition, index) in immediateConditions" :key="`immediate-${index}`" class="condition-row">
|
||||||
|
<select v-model="immediateCondition.criterion">
|
||||||
|
<option value="" disabled>Select Criterion</option>
|
||||||
|
<option v-for="col in criteria_titles" :key="`immediate-${col}`" :value="col">{{ col }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select v-model="immediateCondition.operator">
|
||||||
|
<option value="" disabled>Select Operator</option>
|
||||||
|
<option v-for="op in operators" :key="op" :value="op">{{ op }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="number" v-model.number="immediateCondition.value" :min="0" step="0.1" placeholder="Value" />
|
||||||
|
|
||||||
|
<button @click="removeImmediateCondition(index)">-</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="addImmediateCondition">+ Add Immediate Constraint</button>
|
||||||
|
</div>
|
||||||
|
<button @click="sendWRData">Run Evaluation</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export const backendURL = import.meta.env.VITE_BACKEND_URL;
|
||||||
|
const apiURL = backendURL;
|
||||||
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
receivedGridData: null,
|
||||||
|
relativeConditions: [{column1: '', operator: '', value: 0, column2: ''}],
|
||||||
|
criteria_titles: [], // This is populated with the column titles
|
||||||
|
operators: ['>=', '=', '<='],
|
||||||
|
immediateConditions: [{criterion: '', operator: '', value: 0}],
|
||||||
|
operatorMapping: {
|
||||||
|
'<=': -1,
|
||||||
|
'=': 0,
|
||||||
|
'>=': 1
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$route.params.data) {
|
||||||
|
// Parse the JSON string back into an object
|
||||||
|
this.receivedGridData = JSON.parse(this.$route.params.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridDataFromStorage = localStorage.getItem('gridData');
|
||||||
|
const wrDataFromStorage = localStorage.getItem('wrData');
|
||||||
|
const immediateWRDataFromStorage = localStorage.getItem('immediateWRData');
|
||||||
|
|
||||||
|
if (gridDataFromStorage) {
|
||||||
|
this.receivedGridData = JSON.parse(gridDataFromStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrDataFromStorage) {
|
||||||
|
this.wrData = JSON.parse(wrDataFromStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (immediateWRDataFromStorage) {
|
||||||
|
this.immediateConditions = JSON.parse(immediateWRDataFromStorage);
|
||||||
|
} else {
|
||||||
|
// Reset immediateConditions if there is no stored data
|
||||||
|
this.immediateConditions = [{criterion: '', operator: '', value: 0}];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchCriteriaTitles();
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchCriteriaTitles() {
|
||||||
|
fetch(apiURL+'/get-criteria-titles')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.criteria_titles = data;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching criteria titles:', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addCondition() {
|
||||||
|
this.relativeConditions.push({column1: '', column2: '', operator: '', value: 0});
|
||||||
|
},
|
||||||
|
removeCondition(index) {
|
||||||
|
this.relativeConditions.splice(index, 1);
|
||||||
|
},
|
||||||
|
validateForm() {
|
||||||
|
for (const condition of this.relativeConditions) {
|
||||||
|
if (!condition.column1 || !condition.column2) {
|
||||||
|
alert('Please select criteria for each relative constraint.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!condition.operator) {
|
||||||
|
alert('Please select an operator for each relative constraint.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (condition.value === null || condition.value === '') {
|
||||||
|
alert('Please enter a numeric value for each relative constraint.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (condition.value <= 0) {
|
||||||
|
alert('The priority in each relative constraint must be greater than zero.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniquePairs = new Set(
|
||||||
|
this.relativeConditions.map(c => [c.column1, c.column2].sort().join('-'))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uniquePairs.size !== this.relativeConditions.length) {
|
||||||
|
alert('Each pair of criteria can only be used once in a restriction!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
updateDropdowns(index) {
|
||||||
|
// May be used to update dropdown availability
|
||||||
|
},
|
||||||
|
availableColumns(index, dropdownNumber) {
|
||||||
|
if (dropdownNumber === 1) {
|
||||||
|
// For the first dropdown, filter out the column selected in the second dropdown
|
||||||
|
return this.criteria_titles.filter(col => col !== this.relativeConditions[index].column2);
|
||||||
|
} else {
|
||||||
|
// For the second dropdown, filter out the column selected in the first dropdown
|
||||||
|
return this.criteria_titles.filter(col => col !== this.relativeConditions[index].column1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Add a method to validate the Immediate Constraints
|
||||||
|
validateImmediateConstraints() {
|
||||||
|
let criterionConstraints = {};
|
||||||
|
|
||||||
|
// Iterate over immediate conditions and organize them by criterion
|
||||||
|
for (const condition of this.immediateConditions) {
|
||||||
|
if (!condition.criterion || !condition.operator) {
|
||||||
|
continue; // Skip empty conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure value is greater than 0
|
||||||
|
if (condition.value === null || condition.value === '' || condition.value <= 0) {
|
||||||
|
alert(`The importance of criterion "${condition.criterion}" should be greater than 0.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the constraints list for the criterion if not already done
|
||||||
|
if (!criterionConstraints[condition.criterion]) {
|
||||||
|
criterionConstraints[condition.criterion] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate operators
|
||||||
|
if (criterionConstraints[condition.criterion][condition.operator]) {
|
||||||
|
alert(`You cannot use the same operator more than once for the criterion "${condition.criterion}".`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the condition to the list for the criterion
|
||||||
|
criterionConstraints[condition.criterion][condition.operator] = condition.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the constraints for each criterion and apply validation rules
|
||||||
|
for (const [criterion, operators] of Object.entries(criterionConstraints)) {
|
||||||
|
// Only one constraint allowed when using '=' operator
|
||||||
|
if (operators['='] !== undefined && Object.keys(operators).length > 1) {
|
||||||
|
alert(`Only one constraint allowed for '${criterion}' when using '=' operator.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate logical consistency between '>=' and '<=' values
|
||||||
|
if (operators['>='] !== undefined && operators['<='] !== undefined) {
|
||||||
|
const greaterThanOrEqualValue = parseFloat(operators['>=']);
|
||||||
|
const lessThanOrEqualValue = parseFloat(operators['<=']);
|
||||||
|
|
||||||
|
if (isNaN(greaterThanOrEqualValue) || isNaN(lessThanOrEqualValue)) {
|
||||||
|
alert(`Invalid numeric values for the criterion "${criterion}".`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greaterThanOrEqualValue > lessThanOrEqualValue) {
|
||||||
|
alert(`For the criterion "${criterion}", the value for '>=' must be less than or equal to the value for '<='.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
validateNonInvertedConditions() {
|
||||||
|
let isValid = true;
|
||||||
|
let conditionPairs = this.relativeConditions.map(c => [c.column1, c.column2].sort().join('-'));
|
||||||
|
|
||||||
|
// Create a Set for unique pairs
|
||||||
|
const uniquePairs = new Set(conditionPairs);
|
||||||
|
|
||||||
|
if (uniquePairs.size !== conditionPairs.length) {
|
||||||
|
// There are duplicates
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
},
|
||||||
|
addImmediateCondition() {
|
||||||
|
this.immediateConditions.push({criterion: '', operator: '', value: 0});
|
||||||
|
},
|
||||||
|
removeImmediateCondition(index) {
|
||||||
|
this.immediateConditions.splice(index, 1);
|
||||||
|
},
|
||||||
|
async sendWRData() {
|
||||||
|
const operatorMapping = {'<=': -1, '=': 0, '>=': 1};
|
||||||
|
// Check if any relative or immediate condition is set
|
||||||
|
const isAnyRelativeConditionSet = this.relativeConditions.some(condition => condition.column1 && condition.column2 && condition.operator);
|
||||||
|
const isAnyImmediateConditionSet = this.immediateConditions.some(condition => condition.criterion && condition.operator);
|
||||||
|
|
||||||
|
// Filter out incomplete or empty relative constraints
|
||||||
|
const validRelativeConditions = this.relativeConditions.filter(condition => condition.column1 && condition.column2 && condition.operator);
|
||||||
|
// Filter out incomplete or empty immediate constraints
|
||||||
|
const validImmediateConditions = this.immediateConditions.filter(condition => condition.criterion && condition.operator);
|
||||||
|
|
||||||
|
// Prompt the user if no conditions are set
|
||||||
|
if (!isAnyRelativeConditionSet && !isAnyImmediateConditionSet) {
|
||||||
|
const proceedWithoutWR = confirm("Would you like to proceed without imposing Weight Restrictions?");
|
||||||
|
if (!proceedWithoutWR) {
|
||||||
|
return; // User chose 'No', do nothing
|
||||||
|
}
|
||||||
|
// Clear data if user chose 'Yes'
|
||||||
|
this.relativeConditions = [];
|
||||||
|
this.immediateConditions = [];
|
||||||
|
} else {
|
||||||
|
// Validate conditions
|
||||||
|
if ((isAnyRelativeConditionSet && (!this.validateForm() || !this.validateNonInvertedConditions())) ||
|
||||||
|
(isAnyImmediateConditionSet && (!this.validateImmediateConstraints()))) {
|
||||||
|
return; // Stop if validation fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Relative constraints
|
||||||
|
const RelativeWRData = validRelativeConditions.map(condition => ({
|
||||||
|
LHSCriterion: condition.column1,
|
||||||
|
Operator: this.operatorMapping[condition.operator],
|
||||||
|
Intense: condition.value,
|
||||||
|
RHSCriterion: condition.column2
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Process Immediate constraints
|
||||||
|
const immediateWRData = validImmediateConditions.map(condition => ({
|
||||||
|
Criterion: condition.criterion,
|
||||||
|
Operator: this.operatorMapping[condition.operator],
|
||||||
|
Value: condition.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Prepare payload with filtered conditions
|
||||||
|
const payload = {
|
||||||
|
gridData: this.receivedGridData,
|
||||||
|
wrData: RelativeWRData,
|
||||||
|
immediateWRData: immediateWRData
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL+'/process-evaluation-data', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Response from backend:', data);
|
||||||
|
|
||||||
|
// Check if the response was successful
|
||||||
|
if (response.ok && data.status === 'success') {
|
||||||
|
localStorage.setItem('gridData', JSON.stringify(this.receivedGridData));
|
||||||
|
localStorage.setItem('wrData', JSON.stringify(RelativeWRData));
|
||||||
|
|
||||||
|
this.$router.push({name: 'Results'});
|
||||||
|
} else {
|
||||||
|
console.error('Error in response:', data.message);
|
||||||
|
alert('Failed to process data: ' + data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sending data to backend:', error);
|
||||||
|
alert('Failed to send data to backend.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wr-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: var(--main-color); /* Blue color */
|
||||||
|
color: #fff; /* White text color */
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: var(--secondary-color); /* Lighter shade of purple on hover */
|
||||||
|
color: var(--main-color);
|
||||||
|
border: 2px;
|
||||||
|
border-color: var(--main-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,7 +1,6 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import DataGrid from '@/components/DataGrid.vue';
|
import DataGrid from '@/components/DataGrid.vue';
|
||||||
import CriteriaSelection from '@/components/CriteriaSelection.vue'; // Import the new component
|
import CriteriaSelection from '@/components/CriteriaSelection.vue'; // Import the new component
|
||||||
//import SummedData from '@/components/SummedData.vue';
|
|
||||||
import Evaluation from '@/components/Evaluation.vue'; // Import the Evaluation component
|
import Evaluation from '@/components/Evaluation.vue'; // Import the Evaluation component
|
||||||
import WR from '@/components/WR.vue';
|
import WR from '@/components/WR.vue';
|
||||||
import Results from '@/components/Results.vue'; // Import the Results component
|
import Results from '@/components/Results.vue'; // Import the Results component
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// router.js
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import HierarchicalCategoryList from '@/components/HierarchicalCategoryList.vue';
|
import HierarchicalCategoryList from '@/components/HierarchicalCategoryList.vue';
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: db-init-script
|
||||||
|
data:
|
||||||
|
db_script.sql: |
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
uuid VARCHAR(255) NOT NULL,
|
||||||
|
username VARCHAR(255) NOT NULL,
|
||||||
|
password VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS apps (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_uuid VARCHAR(255) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
app_id VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO users (username, password, uuid) VALUES ('greg', '12345', 'e3ff4006-be5f-4e00-bbe1-e49a88b2541a');
|
||||||
|
INSERT INTO apps (user_uuid, title, description, app_id) VALUES ('e3ff4006-be5f-4e00-bbe1-e49a88b2541a', 'Demo App', 'Demo App description', '2f7cc63df4b1da7532756f44345758da');
|
@ -0,0 +1,61 @@
|
|||||||
|
{{ if .Values.postgresql.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "nebulous-cloud-fog-service-broker.fullname" . }}-postgresql
|
||||||
|
labels:
|
||||||
|
{{- include "nebulous-cloud-fog-service-broker.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "nebulous-cloud-fog-service-broker.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "nebulous-cloud-fog-service-broker.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: postgres-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: {{ .Values.postgresql.volumeMounts.data.claimName }}
|
||||||
|
- name: init-script
|
||||||
|
configMap:
|
||||||
|
name: {{ .Values.postgresql.volumeMounts.initScript.configMapName }}
|
||||||
|
containers:
|
||||||
|
- name: postgresql
|
||||||
|
image: "{{ .Values.postgresql.image }}"
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
containerPort: {{ .Values.postgresql.port }}
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: "{{ .Values.postgresql.user }}"
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: "{{ .Values.postgresql.password }}"
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: "{{ .Values.postgresql.dbName }}"
|
||||||
|
volumeMounts:
|
||||||
|
- name: postgres-data
|
||||||
|
mountPath: /var/lib/postgresql/data/
|
||||||
|
- name: init-script
|
||||||
|
mountPath: /docker-entrypoint-initdb.d
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "nebulous-cloud-fog-service-broker.fullname" . }}-postgresql
|
||||||
|
labels:
|
||||||
|
{{- include "nebulous-cloud-fog-service-broker.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.postgresql.port }}
|
||||||
|
targetPort: postgresql
|
||||||
|
protocol: TCP
|
||||||
|
name: postgresql
|
||||||
|
selector:
|
||||||
|
{{- include "nebulous-cloud-fog-service-broker.selectorLabels" . | nindent 4 }}
|
||||||
|
{{ end }}
|
@ -35,18 +35,29 @@ spec:
|
|||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8080
|
containerPort: 8001
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
env:
|
||||||
|
- name: NEBULOUS_BROKER_URL
|
||||||
|
value: "{{ .Values.customEnv.NEBULOUS_BROKER_URL }}"
|
||||||
|
- name: NEBULOUS_BROKER_PORT
|
||||||
|
value: "{{ .Values.customEnv.NEBULOUS_BROKER_PORT }}"
|
||||||
|
- name: NEBULOUS_BROKER_USERNAME
|
||||||
|
value: "{{ .Values.customEnv.NEBULOUS_BROKER_USERNAME }}"
|
||||||
|
- name: NEBULOUS_BROKER_PASSWORD
|
||||||
|
value: "{{ .Values.customEnv.NEBULOUS_BROKER_PASSWORD }}"
|
||||||
|
- name: POSTGRES_DB_HOST
|
||||||
|
value: "{{ .Values.customEnv.POSTGRES_DB_HOST }}"
|
||||||
|
- name: POSTGRES_DB_NAME
|
||||||
|
value: "{{ .Values.customEnv.POSTGRES_DB_NAME }}"
|
||||||
|
- name: POSTGRES_DB_PORT
|
||||||
|
value: "{{ .Values.customEnv.POSTGRES_DB_PORT }}"
|
||||||
|
- name: POSTGRES_DB_USER
|
||||||
|
value: "{{ .Values.customEnv.POSTGRES_DB_USER }}"
|
||||||
|
- name: POSTGRES_DB_PASS
|
||||||
|
value: "{{ .Values.customEnv.POSTGRES_DB_PASS }}"
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgresql-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
@ -5,7 +5,7 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
repository: "quay.io/nebulous/cloud-fog-service-broker-java-spring-boot-demo"
|
repository: "quay.io/nebulous/cloud-fog-service-broker-backend"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
# Overrides the image tag whose default is the chart appVersion.
|
# Overrides the image tag whose default is the chart appVersion.
|
||||||
tag: ""
|
tag: ""
|
||||||
@ -80,3 +80,27 @@ nodeSelector: {}
|
|||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
affinity: {}
|
affinity: {}
|
||||||
|
|
||||||
|
customEnv:
|
||||||
|
NEBULOUS_BROKER_URL: "nebulous-activemq"
|
||||||
|
NEBULOUS_BROKER_PORT: "61616"
|
||||||
|
NEBULOUS_BROKER_USERNAME: "admin"
|
||||||
|
NEBULOUS_BROKER_PASSWORD: "admin"
|
||||||
|
POSTGRES_DB_HOST: "localhost"
|
||||||
|
POSTGRES_DB_NAME: "fog_broker"
|
||||||
|
POSTGRES_DB_PORT: "5432"
|
||||||
|
POSTGRES_DB_USER: "dbuser"
|
||||||
|
POSTGRES_DB_PASS: "pass123"
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
enabled: true
|
||||||
|
image: "docker.io/postgres:16"
|
||||||
|
user: "dbuser"
|
||||||
|
password: "pass123"
|
||||||
|
dbName: "fog_broker"
|
||||||
|
port: 5432
|
||||||
|
volumeMounts:
|
||||||
|
data:
|
||||||
|
claimName: "postgresql-pvc"
|
||||||
|
initScript:
|
||||||
|
configMapName: "db-init-script"
|
||||||
|
41
docker-compose.yml
Normal file
41
docker-compose.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
version: '3.0'
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./cfsb-backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8001:8001"
|
||||||
|
env_file:
|
||||||
|
- ./cfsb-backend/.env.prod
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
networks:
|
||||||
|
cfsb-network:
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=dbuser
|
||||||
|
- POSTGRES_PASSWORD=pass123
|
||||||
|
- POSTGRES_DB=fog_broker
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data/
|
||||||
|
- ./cfsb-backend/db/db_script.sql:/docker-entrypoint-initdb.d/db_script.sql
|
||||||
|
networks:
|
||||||
|
cfsb-network:
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./cfsb-frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
networks:
|
||||||
|
cfsb-network:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cfsb-network:
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
33
java-spring-boot-demo/.gitignore
vendored
33
java-spring-boot-demo/.gitignore
vendored
@ -1,33 +0,0 @@
|
|||||||
HELP.md
|
|
||||||
target/
|
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
|
||||||
!**/src/main/**/target/
|
|
||||||
!**/src/test/**/target/
|
|
||||||
|
|
||||||
### STS ###
|
|
||||||
.apt_generated
|
|
||||||
.classpath
|
|
||||||
.factorypath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
|
||||||
.idea
|
|
||||||
*.iws
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
### NetBeans ###
|
|
||||||
/nbproject/private/
|
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
build/
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
@ -1,15 +0,0 @@
|
|||||||
#
|
|
||||||
# Build stage
|
|
||||||
#
|
|
||||||
FROM docker.io/library/maven:3.9.2-eclipse-temurin-17 AS build
|
|
||||||
COPY src /home/app/src
|
|
||||||
COPY pom.xml /home/app
|
|
||||||
RUN mvn -f /home/app/pom.xml clean package
|
|
||||||
|
|
||||||
#
|
|
||||||
# Package stage
|
|
||||||
#
|
|
||||||
FROM docker.io/library/eclipse-temurin:17-jre
|
|
||||||
COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
|
|
||||||
EXPOSE 8080
|
|
||||||
ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]
|
|
@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
|
||||||
<version>3.1.0</version>
|
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
<groupId>com.example</groupId>
|
|
||||||
<artifactId>demo</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
<name>demo</name>
|
|
||||||
<description>Demo project for Spring Boot</description>
|
|
||||||
<properties>
|
|
||||||
<java.version>17</java.version>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.example.demo;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class DemoApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(DemoApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.example.demo;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
public class DemoController {
|
|
||||||
|
|
||||||
@RequestMapping("/")
|
|
||||||
public Object root() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.example.demo;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
|
|
||||||
@SpringBootTest
|
|
||||||
class DemoApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void contextLoads() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,17 +6,24 @@
|
|||||||
soft: false
|
soft: false
|
||||||
provides:
|
provides:
|
||||||
- nebulous-cloud-fog-service-broker-container-images
|
- nebulous-cloud-fog-service-broker-container-images
|
||||||
description: Build the container images.
|
description: Build the container images for both the backend and frontend.
|
||||||
files: &image_files
|
files: &image_files
|
||||||
- ^java-spring-boot-demo/
|
- ^cfsb-backend/
|
||||||
|
- ^cfsb-frontend/
|
||||||
vars: &image_vars
|
vars: &image_vars
|
||||||
promote_container_image_job: nebulous-cloud-fog-service-broker-upload-container-images
|
promote_container_image_job: nebulous-cloud-fog-service-broker-upload-container-images
|
||||||
container_images:
|
container_images:
|
||||||
- context: java-spring-boot-demo
|
- context: cfsb-backend
|
||||||
registry: quay.io
|
registry: quay.io
|
||||||
repository: quay.io/nebulous/cloud-fog-service-broker-java-spring-boot-demo
|
repository: quay.io/nebulous/cloud-fog-service-broker-backend
|
||||||
namespace: nebulous
|
namespace: nebulous
|
||||||
repo_shortname: cloud-fog-service-broker-java-spring-boot-demo
|
repo_shortname: cloud-fog-service-broker
|
||||||
|
repo_description: ""
|
||||||
|
- context: cfsb-frontend
|
||||||
|
registry: quay.io
|
||||||
|
repository: quay.io/nebulous/cloud-fog-service-broker-frontend
|
||||||
|
namespace: nebulous
|
||||||
|
repo_shortname: cloud-fog-service-broker
|
||||||
repo_description: ""
|
repo_description: ""
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
@ -27,14 +34,14 @@
|
|||||||
soft: false
|
soft: false
|
||||||
provides:
|
provides:
|
||||||
- nebulous-cloud-fog-service-broker-container-images
|
- nebulous-cloud-fog-service-broker-container-images
|
||||||
description: Build and upload the container images.
|
description: Build and upload both the backend and frontend container images.
|
||||||
files: *image_files
|
files: *image_files
|
||||||
vars: *image_vars
|
vars: *image_vars
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: nebulous-cloud-fog-service-broker-promote-container-images
|
name: nebulous-cloud-fog-service-broker-promote-container-images
|
||||||
parent: nebulous-promote-container-images
|
parent: nebulous-promote-container-images
|
||||||
description: Promote previously uploaded container images.
|
description: Promote previously uploaded backend and frontend container images.
|
||||||
files: *image_files
|
files: *image_files
|
||||||
vars: *image_vars
|
vars: *image_vars
|
||||||
|
|
||||||
@ -44,7 +51,8 @@
|
|||||||
description: Run Hadolint on Dockerfile(s).
|
description: Run Hadolint on Dockerfile(s).
|
||||||
vars:
|
vars:
|
||||||
dockerfiles:
|
dockerfiles:
|
||||||
- java-spring-boot-demo/Dockerfile
|
- cfsb-backend/Dockerfile
|
||||||
|
- cfsb-frontend/Dockerfile
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: nebulous-cloud-fog-service-broker-helm-lint
|
name: nebulous-cloud-fog-service-broker-helm-lint
|
||||||
|
Loading…
Reference in New Issue
Block a user