diff --git a/cfsb-backend/.env.prod b/cfsb-backend/.env.prod index e9b7e4b..f3c9994 100644 --- a/cfsb-backend/.env.prod +++ b/cfsb-backend/.env.prod @@ -2,7 +2,7 @@ NEBULOUS_BROKER_URL=158.37.63.86 NEBULOUS_BROKER_PORT=31609 NEBULOUS_BROKER_USERNAME=admin NEBULOUS_BROKER_PASSWORD=admin -POSTGRES_DB_HOST=localhost +POSTGRES_DB_HOST=db POSTGRES_DB_NAME=fog_broker POSTGRES_DB_PORT=5432 POSTGRES_DB_USER=dbuser diff --git a/cfsb-backend/API_Functions.py b/cfsb-backend/API_Functions.py deleted file mode 100644 index e69de29..0000000 diff --git a/cfsb-backend/Evaluation.py b/cfsb-backend/Evaluation.py index 88424a3..8600faa 100644 --- a/cfsb-backend/Evaluation.py +++ b/cfsb-backend/Evaluation.py @@ -6,7 +6,7 @@ from scipy.stats import rankdata def perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids): print("Evaluation begun with perform_evaluation():") # 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 'boolean' in criterion.lower()] # 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 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 - for i in range(len(node_names)): + # Iterate over the list of nodes to count the '1' (True) values and assign categories + for i in range(len(node_ids)): 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 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_low in fog_node_categories[sorted_categories[higher_cat + 1]]: # 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] - low_scores = [-data_table[criterion][node_names.index(fog_node_low)] 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_ids.index(fog_node_low)] for criterion in data_table] constraint = [h - l for h, l in zip(high_scores, low_scores)] A_boolean.append(constraint) 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()))) Cols_No = len(criteria_list) 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 for dmu_index in range(num_of_dmus): # 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], "Rank": int(DEA_Scores_Ranked[i]) } - for i in range(len(node_names)) + for i in range(len(node_ids)) ] # 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'}] # 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) @@ -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}]} # # # 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) -# Evaluation_JSON = perform_evaluation(data_table, [], [], node_names) +# Evaluation_JSON = perform_evaluation(data_table, [], [], node_ids) # pretty_json = json.dumps(Evaluation_JSON) # print(pretty_json) # print("Evaluation_JSON:", Evaluation_JSON) diff --git a/cfsb-backend/User_Functions.py b/cfsb-backend/User_Functions.py index b6f0e8b..946f9ca 100644 --- a/cfsb-backend/User_Functions.py +++ b/cfsb-backend/User_Functions.py @@ -1,12 +1,8 @@ import os -# import read_file import get_data as file import random import json 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 # 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", "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 -# 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) +# Used to extract_SAL_node_candidate_data from Use Side for DataGrid +def extract_SAL_node_candidate_data_Front(json_data): + default_criteria_list = ["cores", "ram", "disk", "memoryPrice", "price"] + + if isinstance(json_data, dict): # Single node dictionary + json_data = [json_data] # Wrap it in a list extracted_data = [] node_ids = [] node_names = [] 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 = { - "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") - } + "nodeId": item.get("nodeId", ''), + "id": item.get('id', ''), + "nodeCandidateType": item.get("nodeCandidateType", ''), + **default_criteria_values, # Unpack default criteria values into node_data + "hardware": hardware_info, + "location": item.get("location", {}), + "image": item.get("image", {}), + "providerName": provider_name } extracted_data.append(node_data) - node_ids.append(item['id']) - node_names.append(item.get('name', '')) + node_ids.append(node_data["id"]) - 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): + # print("Entered in extract_SAL_node_candidate_data") try: json_data = json.loads(json_string) # Ensure json_data is a list of dictionaries except json.JSONDecodeError as e: @@ -73,9 +114,9 @@ def extract_SAL_node_candidate_data(json_string): "nodeId": item.get("nodeId", ''), "id": item.get('id', ''), "nodeCandidateType": item.get("nodeCandidateType", ''), - "price": item.get("price", 0.0), - "pricePerInvocation": item.get("pricePerInvocation", 0.0), - "memoryPrice": item.get("memoryPrice", 0.0), + "price": item.get("price", ''), + "pricePerInvocation": item.get("pricePerInvocation", ''), + "memoryPrice": item.get("memoryPrice", ''), "hardware": item.get("hardware", {}) } extracted_data.append(node_data) @@ -85,48 +126,11 @@ def extract_SAL_node_candidate_data(json_string): number_of_nodes = len(extracted_data) node_ids = [node['id'] for node in extracted_data] node_names = [node['id'] for node in extracted_data] - - 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 + return extracted_data, node_ids, node_names # 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 = { # "Cost": "price", "Operating cost": "price", @@ -137,7 +141,8 @@ def create_criteria_mapping(selected_items, extracted_data): } 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): grid_data = json_data.get('gridData', []) 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} for node in grid_data: - node_name = node.get('name') + # node_name = node.get('name') node_ids.append(node.get('id')) + node_id = node.get('id') criteria_data = {} 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 pass - temp_data_table[node_name] = criteria_data + temp_data_table[node_id] = criteria_data # Collect all criteria titles 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} # 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(): data_table[title].append(value) @@ -254,64 +260,6 @@ def check_json_file_exists(app_id): 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): # Initialize the data table with lists for each criterion 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 - - -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 - - +# Used to Append "Score" and "Rank" for each node in SAL's response JSON 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) + # 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 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 for node in sal_reply_body: @@ -404,37 +316,387 @@ def append_evaluation_results(sal_reply_body, scores_and_ranks): 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 - - - - -# Example usage -# 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) +# 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) +# file_path = os.path.join(app_dir, f"{app_id}_data.json") # -# print("Node Names:", node_names) -# print("data_table:", data_table) -# print("Relative WR Data:", relative_wr_data) -# print("Immediate WR Data:", immediate_wr_data) +# # Initialize variables to return in case of no data or an error +# data_table, relative_wr_data, immediate_wr_data, node_names, node_ids = [], [], [], [], [] +# # Read data from SAL's reply +# 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) -# print("evaluation_results:", evaluation_results) +# # 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) # -# # Extracting the results and saving them into a variable -# ScoresAndRanks = evaluation_results['results'] -# print("ScoresAndRanks:", ScoresAndRanks) +# # Filter gridData based on Nodes returned by SAL +# filtered_grid_data = [node for node in data.get('gridData', []) if node.get('id') in node_ids_SAL] +# +# 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 \ No newline at end of file diff --git a/cfsb-backend/activemq.py b/cfsb-backend/activemq.py index dafaa01..be45ff9 100644 --- a/cfsb-backend/activemq.py +++ b/cfsb-backend/activemq.py @@ -1,8 +1,6 @@ # ActiveMQ communication logic import sys import threading -import json -import time sys.path.insert(0,'../exn') import logging from dotenv import load_dotenv @@ -18,9 +16,10 @@ from exn.core.handler import Handler from exn.handler.connector_handler import ConnectorHandler from User_Functions import * import uuid +from Evaluation import perform_evaluation -# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') -# logging.getLogger('exn.connector').setLevel(logging.CRITICAL) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logging.getLogger('exn.connector').setLevel(logging.CRITICAL) class SyncedHandler(Handler): def on_message(self, key, address, body, message: Message, context=None): @@ -34,77 +33,58 @@ class SyncedHandler(Handler): # logging.info("Entered in OPT-triggering'") # 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 if not correlation_id_optimizer: correlation_id_optimizer = '88334290cad34ad9b21eb468a9f8ff11' # dummy 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.subject # application_id_optimizer = 'd535cf554ea66fbebfc415ac837a5828' #dummy application_id_optimizer - print("Application Id: ", application_id_optimizer) + # print("Application Id: ", application_id_optimizer) try: # Read the Message Sent from Optimizer 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 body_sent_from_optimizer = opt_message_data.get('body', {}) - # 100 Nodes + ## Example body # body_sent_from_optimizer = [ # { # "type": "NodeTypeRequirement", - # "nodeTypes": ["IAAS"], - # "jobIdForByon": "dummy-app-id", - # "jobIdForEDGE": "dummy-app-id" - # } - # ] - - - # 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" + # # "nodeTypes": ["EDGES"] + # "nodeTypes": ["IAAS", "PAAS", "FAAS", "BYON", "EDGE", "SIMULATION"] + # # ,"jobIdForEDGE": "FCRnewLight0" # } + # # ,{ + # # "type": "AttributeRequirement", + # # "requirementClass": "hardware", + # # "requirementAttribute": "ram", + # # "requirementOperator": "EQ", + # # "value": "2" + # # } # ] # 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 # 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 "metaData": {"user": "admin"}, # key [String "metaData"] value [dictionary] "body": body_json_string # key [String "body"] value [JSON String] } # logging.info("RequestToSal: %s", RequestToSal) - print("RequestToSal:", RequestToSal) - + # print("RequestToSal:", RequestToSal) # print("Is RequestToSal a valid dictionary:", isinstance(RequestToSal, dict)) # 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) ## Process SAL's Reply - # sal_reply_body = sal_reply.get('body') 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: # 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 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: - print("More than 58 nodes exist. Only the first 51 nodes will be processed.") - # Filter to only include the first 51 nodes and convert back to JSON string - sal_reply_body = json.dumps(nodes_data[:400]) - else: - print(f"Total {total_nodes} nodes found. Processing all nodes.") - # Keep sal_reply_body as is since it's already a JSON string - sal_reply_body = sal_body + if total_nodes > 400: # Check if more than 400 nodes received + print("More than 400 nodes returned from SAL.") + # Filter to only include the first 400 nodes and convert back to JSON string + sal_reply_body = json.dumps(nodes_data[:400]) + elif total_nodes > 0 and total_nodes <= 400: + print(f"Total {total_nodes} nodes returned from SAL. Processing all nodes.") + # Keep sal_reply_body as is since it's already a JSON string + sal_reply_body = sal_body + else: + print(f"Total {total_nodes} nodes returned from SAL.") + sal_reply_body = [] except json.JSONDecodeError as e: - print(f"Error parsing JSON: {e}") - sal_reply_body = "[]" # Default to an empty JSON array as a string in case of error + 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 - if sal_reply_body: # Check whether SAL's reply body is empty - # logging.info(f"Whole reply Received from SAL: {sal_reply}") + if sal_reply_body: # Check whether SAL's reply body is empty + # logging.info(f"Reply Received from SAL: {sal_reply}") # 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 - 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 - evaluation_results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids) - # print("Evaluation Results:", evaluation_results) + else: # Application does not exist in directory + 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_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 - # ScoresAndRanks = evaluation_results['results'] - ScoresAndRanks = evaluation_results.get('results', []) - print("Scores and Ranks:", ScoresAndRanks) + # 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() + # 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 - SAL_and_Scores_Body = append_evaluation_results(sal_reply_body, ScoresAndRanks) - # SAL_and_Scores_Body = append_evaluation_results('SAL_Response_11EdgeDevs.json', ScoresAndRanks) - # print("SAL_and_Scores_Body:", SAL_and_Scores_Body) + # Check the number of nodes before Evaluation + print("There are " + str(len(node_ids)) + " nodes for Evaluation") + + ## 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 # CFSBResponse = read_dummy_response_data_toOpt('CFSB_Body_Response.json') # Data and Scores for 5 Nodes - CFSBResponse = { - "metaData": {"user": "admin"}, - "body": SAL_and_Scores_Body + "metaData": {"user": "admin"}, + "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) - # Writing the formatted JSON to a file named test.json with open('CFSBResponse.json', 'w') as file: file.write(formatted_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!") # Send [] to Optimizer CFSBResponse = { @@ -248,6 +206,14 @@ class SyncedHandler(Handler): 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): context = None 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.start() + +def call_publisher(body): + handler = SyncedHandler() + request = handler.requestSAL(body) + return request + + # Used to read dummy response and send to Optimizer using JSON # I have already sent to Optimizer using this function def read_dummy_response_data_toOpt(file_path): @@ -291,6 +264,7 @@ def read_dummy_response_data_toOpt(file_path): } return encapsulated_data + def is_json(myjson): try: json_object = json.loads(myjson) diff --git a/cfsb-backend/app.py b/cfsb-backend/app.py index 3458925..0bb3afe 100644 --- a/cfsb-backend/app.py +++ b/cfsb-backend/app.py @@ -1,11 +1,7 @@ from app_factory import create_app -from dotenv import load_dotenv 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 -load_dotenv() - app = create_app() # Start the EXN connector in the background start_exn_connector_in_background() diff --git a/cfsb-backend/docker-compose.yml b/cfsb-backend/docker-compose.yml deleted file mode 100644 index 31712b6..0000000 --- a/cfsb-backend/docker-compose.yml +++ /dev/null @@ -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: diff --git a/cfsb-backend/edge_data_ipat.json b/cfsb-backend/edge_data_ipat.json new file mode 100644 index 0000000..d2feae8 --- /dev/null +++ b/cfsb-backend/edge_data_ipat.json @@ -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" +} diff --git a/cfsb-backend/routes.py b/cfsb-backend/routes.py index d55d3ba..ea24e58 100644 --- a/cfsb-backend/routes.py +++ b/cfsb-backend/routes.py @@ -7,9 +7,11 @@ from data_types import get_attr_data_type import db.db_functions as db_functions import os import time +import get_data as file import activemq -# from activemq import connector_handler import traceback +import logging +# logging.disable(logging.CRITICAL) 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', '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 # @main_routes.route('/save_ids', methods=['POST']) # def save_ids(): @@ -34,7 +104,6 @@ Cont_Variables = ['attr-performance', 'attr-financial', 'attr-performance-capaci #Used in CriteriaSelection.vue @main_routes.route('/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 if items_list is not None: # Return the list as a JSON response @@ -48,101 +117,196 @@ def get_hierarchical_category_list(): 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) - ## Prepare message to be send to SAL - message_for_SAL = [ # User side so ask SAL for every available node - { - "type": "NodeTypeRequirement", - "nodeTypes": ["IAAS", "PAAS", "FAAS", "BYON", "EDGE", "SIMULATION"] - # "jobIdForEDGE": "FCRnewLight0" - } + # application_id = data.get('app_id') + # user_id = data.get('user_id') + # print("user_id:", user_id) + # print("application_id:", application_id) + + 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 = json.dumps(message_for_SAL) + body_json_string_for_SAL = json.dumps(message_for_SAL) - RequestToSal = { # Dictionary - "metaData": {"user": "admin"}, # key [String "metaData"] value [dictionary] - "body": body_json_string # key [String "body"] value [JSON String] + RequestToSal = { + "metaData": {"user": "admin"}, + "body": body_json_string_for_SAL } - print("RequestToSal:", RequestToSal) + # print("RequestToSal:", RequestToSal) - # print("Is RequestToSal a valid dictionary:", isinstance(RequestToSal, dict)) - # print("Is the 'body' string in RequestToSal a valid JSON string:", is_json(RequestToSal["body"])) + sal_reply = activemq.call_publisher(RequestToSal) + 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 - # sal_reply = activemq.context.publishers['SAL-GET'].send_sync(RequestToSal) + extracted_data, node_ids, node_names = extract_SAL_node_candidate_data_Front(nodes_data) + # print("extracted_data:", extracted_data) + field_mapping = create_criteria_mapping() + # print("field_mapping", field_mapping) - ## Process SAL's Reply - # extracted_data, number_of_nodes, node_ids, node_names = extract_SAL_node_candidate_data(sal_reply) - # extracted_data, number_of_nodes, node_names = extract_node_candidate_data('dummy_data_node_candidates.json') - extracted_data, number_of_nodes, node_ids, node_names = extract_node_candidate_data('SAL_Response_11EdgeDevs.json') - print("extracted_data:", extracted_data) + default_list_criteria_mapping = { + # "Cost": "price", + "Operating cost": "price", + "Memory Price": "memoryPrice", + "Number of CPU Cores": "cores", + "Memory Size": "ram", + "Storage Capacity": "disk" + } - # 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} + grid_data = {} - # 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: - 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": []} + node_id = node_data.get('id') + # print("Before create_node_name") + 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 - 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) + if node_id and node_id not in grid_data: + grid_data[node_id] = {"name": node_name, "criteria": []} - # 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] + hardware_info = node_data.get('hardware', {}) # contains the values for criteria coming from SAL + + for criterion_key in selected_criteria: + # print("criterion_key:", criterion_key) + 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: - # 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) + 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].append(criterion_data) # Use node_name as key - grid_data[node_name]["criteria"].append(criterion_data) + criterion_data = { + "title": criterion_title, + "value": value, + "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 = [{'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) + grid_data_with_names = [{ + 'name': data["name"], + 'id': node_id, + '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({ '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 -# 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']) def process_evaluation_data(): try: @@ -150,14 +314,15 @@ def process_evaluation_data(): if data is None: 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 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("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 results = perform_evaluation(data_table, relative_wr_data, immediate_wr_data, node_names, node_ids) diff --git a/cfsb-backend/test.py b/cfsb-backend/test.py new file mode 100644 index 0000000..e2f8d98 --- /dev/null +++ b/cfsb-backend/test.py @@ -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 + diff --git a/cfsb-backend/updated_SALs_JSON.json b/cfsb-backend/updated_SALs_JSON.json new file mode 100644 index 0000000..666abd6 --- /dev/null +++ b/cfsb-backend/updated_SALs_JSON.json @@ -0,0 +1,1256 @@ +[ + { + "name": "test0", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.0" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "8", + "numberOfCores": 10, + "memory": 4, + "disk": 28.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Warsaw", + "country": "Poland", + "latitude": 68.6427, + "longitude": 81.24341 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d8d770000", + "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-bzzM75gJBIF9ciHt", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Warsaw", + "country": "Poland", + "latitude": 68.6427, + "longitude": 81.24341 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-bzzM75gJBIF9ciHt", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-bzzM75gJBIF9ciHt", + "name": null, + "providerId": null, + "cores": 10, + "ram": 4, + "disk": 28.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d8d7d0001", + "Score": 0.99999, + "Rank": 1 + }, + { + "name": "AWS-Raspberry-Madrid", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.1" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "15", + "numberOfCores": 6, + "memory": 7, + "disk": 11.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -31.018383, + "longitude": -26.717098 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d8ea20002", + "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-Ehjjsq21EsRtesOu", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -31.018383, + "longitude": -26.717098 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-Ehjjsq21EsRtesOu", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-Ehjjsq21EsRtesOu", + "name": null, + "providerId": null, + "cores": 6, + "ram": 7, + "disk": 11.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d8ea60003", + "Score": 0.99999, + "Rank": 1 + }, + { + "name": "test2", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.2" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "10", + "numberOfCores": 1, + "memory": 6, + "disk": 28.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -72.342804, + "longitude": -17.023972 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d8fc40004", + "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-sRI0g25q5kbxZYzK", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -72.342804, + "longitude": -17.023972 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-sRI0g25q5kbxZYzK", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-sRI0g25q5kbxZYzK", + "name": null, + "providerId": null, + "cores": 1, + "ram": 6, + "disk": 28.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d8fc70005", + "Score": 0.6666566666666667, + "Rank": 9 + }, + { + "name": "test3", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.3" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "13", + "numberOfCores": 13, + "memory": 6, + "disk": 16.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -18.325798, + "longitude": -2.8190002 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d90e40006", + "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-WeVLDpisE2wPtRxB", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -18.325798, + "longitude": -2.8190002 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-WeVLDpisE2wPtRxB", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-WeVLDpisE2wPtRxB", + "name": null, + "providerId": null, + "cores": 13, + "ram": 6, + "disk": 16.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d90e70007", + "Score": 1.0, + "Rank": 1 + }, + { + "name": "test4", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.4" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "15", + "numberOfCores": 1, + "memory": 8, + "disk": 51.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -45.349804, + "longitude": 15.921776 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d92040008", + "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-40xmq8oI0kvATuAY", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -45.349804, + "longitude": 15.921776 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-40xmq8oI0kvATuAY", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-40xmq8oI0kvATuAY", + "name": null, + "providerId": null, + "cores": 1, + "ram": 8, + "disk": 51.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d92090009", + "Score": 0.99999, + "Rank": 1 + }, + { + "name": "test5", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.5" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "12", + "numberOfCores": 2, + "memory": 11, + "disk": 79.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Warsaw", + "country": "Poland", + "latitude": 75.87384, + "longitude": -29.064651 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d9320000a", + "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-SX9MUIDWB80wCnHi", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Warsaw", + "country": "Poland", + "latitude": 75.87384, + "longitude": -29.064651 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-SX9MUIDWB80wCnHi", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-SX9MUIDWB80wCnHi", + "name": null, + "providerId": null, + "cores": 2, + "ram": 11, + "disk": 79.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d9326000b", + "Score": 0.99999, + "Rank": 1 + }, + { + "name": "test6", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.6" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "6", + "numberOfCores": 6, + "memory": 1, + "disk": 32.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -89.065475, + "longitude": 53.526062 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d9441000c", + "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-2NId1nrSANd43V6J", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -89.065475, + "longitude": 53.526062 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-2NId1nrSANd43V6J", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-2NId1nrSANd43V6J", + "name": null, + "providerId": null, + "cores": 6, + "ram": 1, + "disk": 32.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d9445000d", + "Score": 0.33333333333333337, + "Rank": 10 + }, + { + "name": "test7", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.7" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "11", + "numberOfCores": 3, + "memory": 4, + "disk": 93.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": 78.6129, + "longitude": -52.693478 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d957c000e", + "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-69VJ8eBysHcIB2XU", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": 78.6129, + "longitude": -52.693478 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-69VJ8eBysHcIB2XU", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-69VJ8eBysHcIB2XU", + "name": null, + "providerId": null, + "cores": 3, + "ram": 4, + "disk": 93.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d957f000f", + "Score": 0.99999, + "Rank": 1 + }, + { + "name": "test8", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.8" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "9", + "numberOfCores": 11, + "memory": 14, + "disk": 98.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -44.904438, + "longitude": 50.77269 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d96a20010", + "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-bf3bxPhgBqe20gOB", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": -44.904438, + "longitude": 50.77269 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-bf3bxPhgBqe20gOB", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-bf3bxPhgBqe20gOB", + "name": null, + "providerId": null, + "cores": 11, + "ram": 14, + "disk": 98.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d96a50011", + "Score": 0.33333333333333337, + "Rank": 10 + }, + { + "name": "test9", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.9" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "16", + "numberOfCores": 4, + "memory": 13, + "disk": 8.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -24.562317, + "longitude": 26.578308 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d97c20012", + "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-3Hi1Qjr3nwDhHVKL", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Athens", + "country": "Greece", + "latitude": -24.562317, + "longitude": 26.578308 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-3Hi1Qjr3nwDhHVKL", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-3Hi1Qjr3nwDhHVKL", + "name": null, + "providerId": null, + "cores": 4, + "ram": 13, + "disk": 8.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d97c70013", + "Score": 0.99998, + "Rank": 1 + }, + { + "name": "test10", + "loginCredential": { + "username": "ubuntu", + "password": "ProActive123", + "privateKey": "" + }, + "ipAddresses": [ + { + "IpAddressType": "PUBLIC_IP", + "IpVersion": "V4", + "value": "10.10.10.10" + }, + { + "IpAddressType": "PRIVATE_IP", + "IpVersion": "V4", + "value": "18.203.158.193" + } + ], + "nodeProperties": { + "providerId": "20", + "numberOfCores": 8, + "memory": 1, + "disk": 13.0, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": 26.856018, + "longitude": 0.13977814 + } + }, + "reason": null, + "diagnostic": null, + "userId": null, + "allocated": null, + "jobId": "FCRnewLight0", + "systemArch": "ARMv8", + "scriptURL": "https://www.google.com", + "jarURL": "https://www.activeeon.com/public_content/7cde3381417ff3784639dc41fa7e7cd0544a5234-morphemic-7bulls/node_13.1.0-SNAPSHOT_armv8.jar", + "nodeCandidate": { + "id": "8a7482868df473cc018df47d98e00014", + "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-rY42bNpWUlA7RHm7", + "name": null, + "providerId": null, + "locationScope": null, + "isAssignable": null, + "geoLocation": { + "city": "Nice", + "country": "France", + "latitude": 26.856018, + "longitude": 0.13977814 + }, + "parent": null, + "state": null, + "owner": null + }, + "image": { + "id": "edge-image-rY42bNpWUlA7RHm7", + "name": "edge-image-name-UBUNTU-UNKNOWN", + "providerId": null, + "operatingSystem": { + "operatingSystemFamily": "UBUNTU", + "operatingSystemArchitecture": "UNKNOWN", + "operatingSystemVersion": 1804.0 + }, + "location": null, + "state": null, + "owner": null + }, + "hardware": { + "id": "edge-hardware-rY42bNpWUlA7RHm7", + "name": null, + "providerId": null, + "cores": 8, + "ram": 1, + "disk": 13.0, + "fpga": 0, + "location": null, + "state": null, + "owner": null + }, + "pricePerInvocation": 0.0, + "memoryPrice": 0.0, + "nodeId": null, + "environment": null + }, + "id": "8a7482868df473cc018df47d98e30015", + "Score": 1.0, + "Rank": 1 + } +] \ No newline at end of file diff --git a/cfsb-frontend/.env b/cfsb-frontend/.env new file mode 100644 index 0000000..9b3d341 --- /dev/null +++ b/cfsb-frontend/.env @@ -0,0 +1,2 @@ +VUE_APP_BACKEND_URL=http://127.0.0.1:5000 +VITE_BACKEND_URL=http://127.0.0.1:5000 \ No newline at end of file diff --git a/cfsb-frontend/.env.development b/cfsb-frontend/.env.development new file mode 100644 index 0000000..e4e39ec --- /dev/null +++ b/cfsb-frontend/.env.development @@ -0,0 +1,2 @@ +VUE_APP_BACKEND_URL=http://127.0.0.1:8001 +VITE_BACKEND_URL=http://127.0.0.1:8001 \ No newline at end of file diff --git a/cfsb-frontend/.env.production b/cfsb-frontend/.env.production new file mode 100644 index 0000000..e4e39ec --- /dev/null +++ b/cfsb-frontend/.env.production @@ -0,0 +1,2 @@ +VUE_APP_BACKEND_URL=http://127.0.0.1:8001 +VITE_BACKEND_URL=http://127.0.0.1:8001 \ No newline at end of file diff --git a/cfsb-frontend/Dockerfile b/cfsb-frontend/Dockerfile new file mode 100644 index 0000000..a5b5e96 --- /dev/null +++ b/cfsb-frontend/Dockerfile @@ -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;"] diff --git a/cfsb-frontend/package-lock.json b/cfsb-frontend/package-lock.json index 2947fa4..26d93cd 100644 --- a/cfsb-frontend/package-lock.json +++ b/cfsb-frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "chart.js": "^4.4.1", + "package.json": "^2.0.1", "vue-router": "^4.0.13" }, "devDependencies": { @@ -679,6 +680,25 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.11.tgz", "integrity": "sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw==" }, + "node_modules/abs": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/abs/-/abs-1.3.14.tgz", + "integrity": "sha512-PrS26IzwKLWwuURpiKl8wRmJ2KdR/azaVrLEBWG/TALwT20Y7qjtYp1qcMLHA4206hBHY5phv3w4pjf9NPv4Vw==", + "dependencies": { + "ul": "^5.0.0" + } + }, + "node_modules/capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/chart.js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", @@ -690,11 +710,67 @@ "pnpm": ">=7" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "dependencies": { + "capture-stack-trace": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deffy": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.4.tgz", + "integrity": "sha512-pLc9lsbsWjr6RxmJ2OLyvm+9l4j1yK69h+TML/gUit/t3vTijpkNGh8LioaJYTGO7F25m6HZndADcUOo2PsiUg==", + "dependencies": { + "typpy": "^2.0.0" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/err": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/err/-/err-1.1.1.tgz", + "integrity": "sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA==", + "dependencies": { + "typpy": "^2.2.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.19.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", @@ -737,6 +813,15 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "node_modules/exec-limiter": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/exec-limiter/-/exec-limiter-3.2.13.tgz", + "integrity": "sha512-86Ri699bwiHZVBzTzNj8gspqAhCPchg70zPVWIh3qzUOA1pUMcb272Em3LPk8AE0mS95B9yMJhtqF8vFJAn0dA==", + "dependencies": { + "limit-it": "^3.0.0", + "typpy": "^2.1.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -751,6 +836,208 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.name": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.13.tgz", + "integrity": "sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==", + "dependencies": { + "noop6": "^1.0.1" + } + }, + "node_modules/git-package-json": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/git-package-json/-/git-package-json-1.4.10.tgz", + "integrity": "sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA==", + "dependencies": { + "deffy": "^2.2.1", + "err": "^1.1.1", + "gry": "^5.0.0", + "normalize-package-data": "^2.3.5", + "oargv": "^3.4.1", + "one-by-one": "^3.1.0", + "r-json": "^1.2.1", + "r-package-json": "^1.0.0", + "tmp": "0.0.28" + } + }, + "node_modules/git-source": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/git-source/-/git-source-1.1.10.tgz", + "integrity": "sha512-XZZ7ZgnLL35oLgM/xjnLYgtlKlxJG0FohC1kWDvGkU7s1VKGXK0pFF/g1itQEwQ3D+uTQzBnzPi8XbqOv7Wc1Q==", + "dependencies": { + "git-url-parse": "^5.0.1" + } + }, + "node_modules/git-up": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", + "integrity": "sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw==", + "dependencies": { + "is-ssh": "^1.0.0", + "parse-url": "^1.0.0" + } + }, + "node_modules/git-url-parse": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", + "integrity": "sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ==", + "dependencies": { + "git-up": "^1.0.0" + } + }, + "node_modules/got": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-5.6.0.tgz", + "integrity": "sha512-MnypzkaW8dldA8AbJFjMs7y14+ykd2V8JCLKSvX1Gmzx1alH3Y+3LArywHDoAF2wS3pnZp4gacoYtvqBeF6drQ==", + "dependencies": { + "create-error-class": "^3.0.1", + "duplexer2": "^0.1.4", + "is-plain-obj": "^1.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "node-status-codes": "^1.0.0", + "object-assign": "^4.0.1", + "parse-json": "^2.1.0", + "pinkie-promise": "^2.0.0", + "read-all-stream": "^3.0.0", + "readable-stream": "^2.0.5", + "timed-out": "^2.0.0", + "unzip-response": "^1.0.0", + "url-parse-lax": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gry": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/gry/-/gry-5.0.8.tgz", + "integrity": "sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==", + "dependencies": { + "abs": "^1.2.1", + "exec-limiter": "^3.0.0", + "one-by-one": "^3.0.0", + "ul": "^5.0.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/iterate-object": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.4.tgz", + "integrity": "sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==" + }, + "node_modules/limit-it": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.10.tgz", + "integrity": "sha512-T0NK99pHnkimldr1WUqvbGV1oWDku/xC9J/OqzJFsV1jeOS6Bwl8W7vkeQIBqwiON9dTALws+rX/XPMQqWerDQ==", + "dependencies": { + "typpy": "^2.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -762,6 +1049,14 @@ "node": ">=12" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -779,11 +1074,159 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-status-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "integrity": "sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/noop6": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.9.tgz", + "integrity": "sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA==" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/oargv": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/oargv/-/oargv-3.4.10.tgz", + "integrity": "sha512-SXaMANv9sr7S/dP0vj0+Ybipa47UE1ntTWQ2rpPRhC6Bsvfl+Jg03Xif7jfL0sWKOYWK8oPjcZ5eJ82t8AP/8g==", + "dependencies": { + "iterate-object": "^1.1.0", + "ul": "^5.0.0" + } + }, + "node_modules/obj-def": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/obj-def/-/obj-def-1.0.9.tgz", + "integrity": "sha512-bQ4ya3VYD6FAA1+s6mEhaURRHSmw4+sKaXE6UyXZ1XDYc5D+c7look25dFdydmLd18epUegh398gdDkMUZI9xg==", + "dependencies": { + "deffy": "^2.2.2" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/one-by-one": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/one-by-one/-/one-by-one-3.2.8.tgz", + "integrity": "sha512-HR/pSzZdm46Xqj58K+Bu64kMbSTw8/u77AwWvV+rprO/OsuR++pPlkUJn+SmwqBGRgHKwSKQ974V3uls7crIeQ==", + "dependencies": { + "obj-def": "^1.0.0", + "sliced": "^1.0.1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==", + "dependencies": { + "got": "^5.0.0", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/package-json-path/-/package-json-path-1.0.9.tgz", + "integrity": "sha512-uNu7f6Ef7tQHZRnkyVnCtzdSYVN9uBtge/sG7wzcUaawFWkPYUq67iXxRGrQSg/q0tzxIB8jSyIYUKjG2Jn//A==", + "dependencies": { + "abs": "^1.2.1" + } + }, + "node_modules/package.json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", + "integrity": "sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw==", + "deprecated": "Use pkg.json instead.", + "dependencies": { + "git-package-json": "^1.4.0", + "git-source": "^1.1.0", + "package-json": "^2.3.1" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg==", + "dependencies": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "node_modules/parse-url/node_modules/protocols": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -811,6 +1254,117 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "node_modules/r-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.3.0.tgz", + "integrity": "sha512-xesd+RHCpymPCYd9DvDvUr1w1IieSChkqYF1EpuAYrvCfLXji9NP36DvyYZJZZB5soVDvZ0WUtBoZaU1g5Yt9A==", + "dependencies": { + "w-json": "1.3.10" + } + }, + "node_modules/r-package-json": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/r-package-json/-/r-package-json-1.0.9.tgz", + "integrity": "sha512-G4Vpf1KImWmmPFGdtWQTU0L9zk0SjqEC4qs/jE7AQ+Ylmr5kizMzGeC4wnHp5+ijPqNN+2ZPpvyjVNdN1CDVcg==", + "dependencies": { + "package-json-path": "^1.0.0", + "r-json": "^1.2.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", + "dependencies": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/rollup": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", @@ -840,6 +1394,24 @@ "fsevents": "~2.3.2" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -848,6 +1420,130 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tmp": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", + "integrity": "sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==", + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/typpy": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/typpy/-/typpy-2.3.13.tgz", + "integrity": "sha512-vOxIcQz9sxHi+rT09SJ5aDgVgrPppQjwnnayTrMye1ODaU8gIZTDM19t9TxmEElbMihx2Nq/0/b/MtyKfayRqA==", + "dependencies": { + "function.name": "^1.0.3" + } + }, + "node_modules/ul": { + "version": "5.2.15", + "resolved": "https://registry.npmjs.org/ul/-/ul-5.2.15.tgz", + "integrity": "sha512-svLEUy8xSCip5IWnsRa0UOg+2zP0Wsj4qlbjTmX6GJSmvKMHADBuHOm1dpNkWqWPIGuVSqzUkV3Cris5JrlTRQ==", + "dependencies": { + "deffy": "^2.2.2", + "typpy": "^2.3.4" + } + }, + "node_modules/unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vite": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", @@ -936,6 +1632,11 @@ "peerDependencies": { "vue": "^3.2.0" } + }, + "node_modules/w-json": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/w-json/-/w-json-1.3.10.tgz", + "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==" } }, "dependencies": { @@ -1310,6 +2011,19 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.11.tgz", "integrity": "sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw==" }, + "abs": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/abs/-/abs-1.3.14.tgz", + "integrity": "sha512-PrS26IzwKLWwuURpiKl8wRmJ2KdR/azaVrLEBWG/TALwT20Y7qjtYp1qcMLHA4206hBHY5phv3w4pjf9NPv4Vw==", + "requires": { + "ul": "^5.0.0" + } + }, + "capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==" + }, "chart.js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", @@ -1318,11 +2032,61 @@ "@kurkle/color": "^0.3.0" } }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deffy": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.4.tgz", + "integrity": "sha512-pLc9lsbsWjr6RxmJ2OLyvm+9l4j1yK69h+TML/gUit/t3vTijpkNGh8LioaJYTGO7F25m6HZndADcUOo2PsiUg==", + "requires": { + "typpy": "^2.0.0" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "err": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/err/-/err-1.1.1.tgz", + "integrity": "sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA==", + "requires": { + "typpy": "^2.2.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, "esbuild": { "version": "0.19.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", @@ -1358,6 +2122,15 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "exec-limiter": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/exec-limiter/-/exec-limiter-3.2.13.tgz", + "integrity": "sha512-86Ri699bwiHZVBzTzNj8gspqAhCPchg70zPVWIh3qzUOA1pUMcb272Em3LPk8AE0mS95B9yMJhtqF8vFJAn0dA==", + "requires": { + "limit-it": "^3.0.0", + "typpy": "^2.1.0" + } + }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1365,6 +2138,181 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.name": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.13.tgz", + "integrity": "sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==", + "requires": { + "noop6": "^1.0.1" + } + }, + "git-package-json": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/git-package-json/-/git-package-json-1.4.10.tgz", + "integrity": "sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA==", + "requires": { + "deffy": "^2.2.1", + "err": "^1.1.1", + "gry": "^5.0.0", + "normalize-package-data": "^2.3.5", + "oargv": "^3.4.1", + "one-by-one": "^3.1.0", + "r-json": "^1.2.1", + "r-package-json": "^1.0.0", + "tmp": "0.0.28" + } + }, + "git-source": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/git-source/-/git-source-1.1.10.tgz", + "integrity": "sha512-XZZ7ZgnLL35oLgM/xjnLYgtlKlxJG0FohC1kWDvGkU7s1VKGXK0pFF/g1itQEwQ3D+uTQzBnzPi8XbqOv7Wc1Q==", + "requires": { + "git-url-parse": "^5.0.1" + } + }, + "git-up": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", + "integrity": "sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw==", + "requires": { + "is-ssh": "^1.0.0", + "parse-url": "^1.0.0" + } + }, + "git-url-parse": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", + "integrity": "sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ==", + "requires": { + "git-up": "^1.0.0" + } + }, + "got": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-5.6.0.tgz", + "integrity": "sha512-MnypzkaW8dldA8AbJFjMs7y14+ykd2V8JCLKSvX1Gmzx1alH3Y+3LArywHDoAF2wS3pnZp4gacoYtvqBeF6drQ==", + "requires": { + "create-error-class": "^3.0.1", + "duplexer2": "^0.1.4", + "is-plain-obj": "^1.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "node-status-codes": "^1.0.0", + "object-assign": "^4.0.1", + "parse-json": "^2.1.0", + "pinkie-promise": "^2.0.0", + "read-all-stream": "^3.0.0", + "readable-stream": "^2.0.5", + "timed-out": "^2.0.0", + "unzip-response": "^1.0.0", + "url-parse-lax": "^1.0.0" + } + }, + "gry": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/gry/-/gry-5.0.8.tgz", + "integrity": "sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==", + "requires": { + "abs": "^1.2.1", + "exec-limiter": "^3.0.0", + "one-by-one": "^3.0.0", + "ul": "^5.0.0" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "requires": { + "protocols": "^2.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "iterate-object": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.4.tgz", + "integrity": "sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==" + }, + "limit-it": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.10.tgz", + "integrity": "sha512-T0NK99pHnkimldr1WUqvbGV1oWDku/xC9J/OqzJFsV1jeOS6Bwl8W7vkeQIBqwiON9dTALws+rX/XPMQqWerDQ==", + "requires": { + "typpy": "^2.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -1373,16 +2321,149 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, + "node-status-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "integrity": "sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ==" + }, + "noop6": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.9.tgz", + "integrity": "sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA==" + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "oargv": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/oargv/-/oargv-3.4.10.tgz", + "integrity": "sha512-SXaMANv9sr7S/dP0vj0+Ybipa47UE1ntTWQ2rpPRhC6Bsvfl+Jg03Xif7jfL0sWKOYWK8oPjcZ5eJ82t8AP/8g==", + "requires": { + "iterate-object": "^1.1.0", + "ul": "^5.0.0" + } + }, + "obj-def": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/obj-def/-/obj-def-1.0.9.tgz", + "integrity": "sha512-bQ4ya3VYD6FAA1+s6mEhaURRHSmw4+sKaXE6UyXZ1XDYc5D+c7look25dFdydmLd18epUegh398gdDkMUZI9xg==", + "requires": { + "deffy": "^2.2.2" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "one-by-one": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/one-by-one/-/one-by-one-3.2.8.tgz", + "integrity": "sha512-HR/pSzZdm46Xqj58K+Bu64kMbSTw8/u77AwWvV+rprO/OsuR++pPlkUJn+SmwqBGRgHKwSKQ974V3uls7crIeQ==", + "requires": { + "obj-def": "^1.0.0", + "sliced": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + }, + "package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==", + "requires": { + "got": "^5.0.0", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "package-json-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/package-json-path/-/package-json-path-1.0.9.tgz", + "integrity": "sha512-uNu7f6Ef7tQHZRnkyVnCtzdSYVN9uBtge/sG7wzcUaawFWkPYUq67iXxRGrQSg/q0tzxIB8jSyIYUKjG2Jn//A==", + "requires": { + "abs": "^1.2.1" + } + }, + "package.json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/package.json/-/package.json-2.0.1.tgz", + "integrity": "sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw==", + "requires": { + "git-package-json": "^1.4.0", + "git-source": "^1.1.0", + "package-json": "^2.3.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg==", + "requires": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + }, + "dependencies": { + "protocols": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==" + } + } + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "requires": { + "pinkie": "^2.0.0" + } + }, "postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -1393,6 +2474,99 @@ "source-map-js": "^1.0.2" } }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "r-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.3.0.tgz", + "integrity": "sha512-xesd+RHCpymPCYd9DvDvUr1w1IieSChkqYF1EpuAYrvCfLXji9NP36DvyYZJZZB5soVDvZ0WUtBoZaU1g5Yt9A==", + "requires": { + "w-json": "1.3.10" + } + }, + "r-package-json": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/r-package-json/-/r-package-json-1.0.9.tgz", + "integrity": "sha512-G4Vpf1KImWmmPFGdtWQTU0L9zk0SjqEC4qs/jE7AQ+Ylmr5kizMzGeC4wnHp5+ijPqNN+2ZPpvyjVNdN1CDVcg==", + "requires": { + "package-json-path": "^1.0.0", + "r-json": "^1.2.1" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", + "requires": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "requires": { + "rc": "^1.0.1" + } + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "rollup": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.0.tgz", @@ -1415,11 +2589,129 @@ "fsevents": "~2.3.2" } }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==" + }, + "tmp": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", + "integrity": "sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==", + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "typpy": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/typpy/-/typpy-2.3.13.tgz", + "integrity": "sha512-vOxIcQz9sxHi+rT09SJ5aDgVgrPppQjwnnayTrMye1ODaU8gIZTDM19t9TxmEElbMihx2Nq/0/b/MtyKfayRqA==", + "requires": { + "function.name": "^1.0.3" + } + }, + "ul": { + "version": "5.2.15", + "resolved": "https://registry.npmjs.org/ul/-/ul-5.2.15.tgz", + "integrity": "sha512-svLEUy8xSCip5IWnsRa0UOg+2zP0Wsj4qlbjTmX6GJSmvKMHADBuHOm1dpNkWqWPIGuVSqzUkV3Cris5JrlTRQ==", + "requires": { + "deffy": "^2.2.2", + "typpy": "^2.3.4" + } + }, + "unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q==" + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "vite": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", @@ -1451,6 +2743,11 @@ "requires": { "@vue/devtools-api": "^6.0.0" } + }, + "w-json": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/w-json/-/w-json-1.3.10.tgz", + "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==" } } } diff --git a/cfsb-frontend/package.json b/cfsb-frontend/package.json index 81ce9e5..ac271c5 100644 --- a/cfsb-frontend/package.json +++ b/cfsb-frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "chart.js": "^4.4.1", + "package.json": "^2.0.1", "vue-router": "^4.0.13" }, "devDependencies": { diff --git a/cfsb-frontend/public/favicon.ico b/cfsb-frontend/public/favicon.ico index df36fcf..ea39f3c 100644 Binary files a/cfsb-frontend/public/favicon.ico and b/cfsb-frontend/public/favicon.ico differ diff --git a/cfsb-frontend/src/App.vue b/cfsb-frontend/src/App.vue index 09131f0..6a3b431 100644 --- a/cfsb-frontend/src/App.vue +++ b/cfsb-frontend/src/App.vue @@ -28,17 +28,55 @@ + +
+ Please select at least two criteria to proceed. +
+ +Edge / Fog Nodes | -{{ values.title }} | +Node | +{{ criterion.title }} |
---|---|---|---|
{{ fogNodesTitles[index-1] }} | -- |
Fog Node | +Node | Score (%) | Ranking |
---|
+ Set relative constraints between the criteria. For example, "Weight of Criterion A >= 2* Weight of Criterion B". +
++ Set immediate constraints on individual criteria. For example, "Weight of Criterion A >= 0.25". +
++ Set relative constraints between the criteria. For example, "Weight of Criterion A >= 2* Weight of Criterion B". +
++ Set immediate constraints on individual criteria. For example, "Weight of Criterion A >= 0.25". +
+