diff --git a/__init__.py b/__init__.py index 16518bf90611f2ba33459df24451575be07997fd..43b770dc3f4bae39e7cc12b870ffdf41ab5eb135 100644 --- a/__init__.py +++ b/__init__.py @@ -2,4 +2,4 @@ from flask import Flask app = Flask(__name__, template_folder="templates") -import my_flask_app.main +from . import main diff --git a/__pycache__/__init__.cpython-311.pyc b/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10a22a88935ac987aeb97db41430e3e2d4a6dbd8 Binary files /dev/null and b/__pycache__/__init__.cpython-311.pyc differ diff --git a/__pycache__/__init__.cpython-39.pyc b/__pycache__/__init__.cpython-39.pyc index 32aa421ba46b9a29d50d12b8ea9ead324ab514c9..206099422aa956812462937fecf40b36f1cb7cbb 100644 Binary files a/__pycache__/__init__.cpython-39.pyc and b/__pycache__/__init__.cpython-39.pyc differ diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d15975a5b1a12e3a15f7be1710a1cb8b3dfbcf9 Binary files /dev/null and b/__pycache__/main.cpython-311.pyc differ diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc index 0f6447fa55eaa8f10df992e71e8b5cd526f6f6d1..d378229d7ae9fbc0df103aa88b1b69fcb108054c 100644 Binary files a/__pycache__/main.cpython-39.pyc and b/__pycache__/main.cpython-39.pyc differ diff --git a/main.py b/main.py index 36e5cf9c442d5675312204c44df7402a1f94dd8b..7b8a8497169868c60da3fddc8195cf3ca9e342c8 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,17 @@ from datetime import datetime import pandas as pd -from my_flask_app import app +from . import app from .models.models import CustomTable, CustomColumn, Theme, CompressedDataType, Observation_Spec, RegType, RegRole from flask_sqlalchemy import SQLAlchemy from flask import jsonify, make_response, redirect, render_template, request, session, url_for, json, send_file -from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect +from sqlalchemy import ARRAY, BIGINT, BOOLEAN, FLOAT, INT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect import pydot, base64, os, logging, io from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.dialects.postgresql.base import ischema_names from sqlalchemy.dialects.postgresql import JSONB, TSTZRANGE, INTERVAL, BYTEA, JSON, UUID, DOUBLE_PRECISION, BYTEA, ARRAY, REAL, TSTZRANGE, UUID, BYTEA, JSONB, JSON, ARRAY, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL from sqlalchemy.dialects.sqlite import JSON, FLOAT, INTEGER, TIMESTAMP, TEXT, BOOLEAN, VARCHAR, NUMERIC, REAL from bs4 import BeautifulSoup +from sqlalchemy.exc import SQLAlchemyError # Set up database (call db.engine) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False @@ -28,12 +29,6 @@ ischema_names['_timescaledb_internal.compressed_data'] = CompressedDataType ischema_names['regtype'] = RegType ischema_names['regrole'] = RegRole -@app.errorhandler(400) -def log_bad_request(error): - logging.error(f"Bad request: {request} {request.data} {request.args}") - # You can add more request details to log here - return "Bad request", 400 - @app.route('/', methods=['POST', 'GET']) def index(): try: @@ -42,71 +37,73 @@ def index(): print("2") database_uri = request.form.get('database_uri') print(database_uri) - if database_uri != '' and database_uri != None: - print("3") - if database_uri != session.get('db_uri', ''): - session['db_uri'] = database_uri - session['target_table_names'] = [] - session['target_table_name'] = '' - session['self_defined_labels'] = [] - session['schema_selected'] = '' - session['show_all'] = False - session['object_name'] = {} - session['data_header']={'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} - session['current_data_header'] = {'tablename': '', 'type': '', 'label': [], 'features_name': []} - print("4") - # Initialize inspector here, outside the inner if-else - print("4.5") - engine = create_engine(database_uri) - insp = inspect(engine) - session_factory = sessionmaker(bind=engine) - db.session = scoped_session(session_factory) - metadata_obj = MetaData() - print("5") - - else: - database_uri = session.get('db_uri', '') - engine = create_engine(database_uri) - insp = inspect(engine) - session_factory = sessionmaker(bind=engine) - db.session = scoped_session(session_factory) - print("6") - - database = database_name_from_uri(engine, database_uri) if database_uri != '' else '' - schemas = getSchema(insp) if session['db_uri'] != '' else [] - themes = getThemes() - tables_selected = [] - dropped_items = session.get('target_table_names', []) - self_labels = session.get('self_defined_labels', []) - schema_Selected = request.form.get('schema', None) - if schema_Selected != None: - session['schema_selected'] = schema_Selected - print("888" + schema_Selected) - show_all = request.form.get('show_all') == 'True' - if show_all != False: - session['show_all'] = show_all - + try: + if database_uri != '' and database_uri != None: + print("3") + if database_uri != session.get('db_uri', ''): + session['db_uri'] = database_uri + session['target_table_names'] = [] + session['target_table_name'] = '' + session['self_defined_labels'] = [] + session['schema_selected'] = '' + session['show_all'] = False + session['object_name'] = {} + session['data_header']={'event':[], 'measurement':[], 'segment':[], 'segmentData':[]} + session['current_data_header'] = {'tablename': '', 'type': '', 'label': [], 'features_name': []} + session['data_table'] = {'O':[], 'S':[], 'SD':[]} + print("4") + # Initialize inspector here, outside the inner if-else + print("4.5") + engine = create_engine(database_uri) + insp = inspect(engine) + session_factory = sessionmaker(bind=engine) + db.session = scoped_session(session_factory) + metadata_obj = MetaData() + print("5") - tables1 = importMetadata(engine, schema_Selected, None, show_all) - graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) - image1 = generate_erd(graph_DOT1) - - print("111") - for name in dropped_items: - print(name) - if dropped_items == []: - image2 = "" - else: - tables2 = importMetadata(engine, None, dropped_items, False) - graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) - image2 = generate_erd(graph_DOT2) - print("222") + else: + database_uri = session.get('db_uri', '') + engine = create_engine(database_uri) + insp = inspect(engine) + session_factory = sessionmaker(bind=engine) + db.session = scoped_session(session_factory) + print("6") + + database = database_name_from_uri(engine, database_uri) if database_uri != '' else '' + schemas = getSchema(insp) if session['db_uri'] != '' else [] + themes = getThemes() + tables_selected = [] + dropped_items = session.get('target_table_names', []) + self_labels = session.get('self_defined_labels', []) + schema_Selected = request.form.get('schema', None) + if schema_Selected != None: + session['schema_selected'] = schema_Selected + print("888" + schema_Selected) + show_all = request.form.get('show_all') == 'True' + if show_all != False: + session['show_all'] = show_all + + tables1 = importMetadata(engine, schema_Selected, None, show_all) + graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True) + image1 = generate_erd(graph_DOT1) - # extract_ME_table(engine, 'event_data', 'E', 'time', ['col', 'machine_id'], [' col ', ' name ', ' Z_ACTUAL_ZERO_POINT '], ['value(fine, coarse)'], datetime(2022, 11, 1, 17, 5), datetime(2022, 11, 3, 0, 0)) + print("111") + for name in dropped_items: + print(name) + if dropped_items == []: + image2 = "" + else: + tables2 = importMetadata(engine, None, dropped_items, False) + graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True) + image2 = generate_erd(graph_DOT2) + print("222") - return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items, self_labels=self_labels) - + return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items, self_labels=self_labels) + + except SQLAlchemyError as e: + # If there's a database error, send an error message to the frontend + return render_template('app.html', success=False, message="Failed to connect to the database. Please check the connection details.") else: # Display the form return render_template('app.html') @@ -118,24 +115,6 @@ def index(): @app.route('/handle-drop', methods=['POST']) def handle_drop(): data = request.json - # if data.get('reset') == True or data.get('reset') != None: - # print(data.get('reset')) - # session['target_table_names'] = [] - # dropped_items = [] - # graph_DOT2 = "digraph {};" - # image2 = generate_erd(graph_DOT2) - - # database_uri = session.get('db_uri', '') - # engine = create_engine(database_uri) - # insp = inspect(engine) - # schema_Selected = session.get('schema', None) - # print("999"+ str(schema_Selected)) - # show_all = session.get('show_all', False) - # tables_in_CustomTable = importMetadata(engine, schema_Selected, None, show_all) - # tables1 = list(tables_in_CustomTable.keys()) if tables_in_CustomTable != {} else [] - - # return jsonify(image2=image2, dropped_items=dropped_items, tables=tables1) - item_name = data.get('item') action = data.get('action') dropped_items = session.get('target_table_names', []) @@ -155,6 +134,7 @@ def handle_drop(): return jsonify(image2=image2) + @app.route('/reset-target-table', methods=['POST']) def reset_target_table(): print("reset") @@ -162,6 +142,7 @@ def reset_target_table(): engine = create_engine(database_uri) session['target_table_names'] = [] schema_Selected = session.get('schema_selected', None) + print(schema_Selected) show_all = session.get('show_all', False) # Regenerate ERD based on the updated dropped_items @@ -175,6 +156,7 @@ def reset_target_table(): return jsonify(image2=image2, tables=tables1) + @app.route('/get-table-data', methods=['POST']) def get_table_data(): data = request.json @@ -191,19 +173,30 @@ def get_table_data(): # Get the table columns and values table_instance = getTableInstance(engine, table_name) - table_columns = [column.name for column in table_instance.columns] - sorted_table_columns = sorted(table_columns) + table_columns = [] + for column in table_instance.columns: + if column.datatype == "varchar" or column.datatype == "numeric" or column.datatype == "string" or column.datatype == "text": + table_columns.append(column.name) + # table_columns = [column.name for column in table_instance.columns] + sorted_table_columns = sorted(table_columns) # de-nested the JSON columns from the feature_columns - feature_columns = sorted_table_columns + column_names = [] + + for column in table_instance.columns: + if column.datatype != "timestamp" and column.datatype != "uuid": + column_names.append(column.name) + + feature_columns = sorted(column_names) if check_json_column(engine, table_name) != []: json_column_names = check_json_column(engine, table_name) for column_name in json_column_names: - feature_columns.remove(column_name) - jsonKeys = handle_json_column(engine, table_name, column_name) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] - for key in jsonKeys: - feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) - + if column_name in feature_columns: + feature_columns.remove(column_name) + jsonKeys = handle_json_column(engine, table_name, column_name) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] + for key in jsonKeys: + feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) + return jsonify({ 'html_table': html_table, 'table_columns': sorted_table_columns, 'feature_columns': feature_columns }) @@ -243,6 +236,78 @@ def add_label(): return jsonify({'defined_labels': self_defined_labels}) +@app.route('/constraint-feature-columns', methods=['POST']) +def constraint_feature_columns(): + engine = create_engine(session.get('db_uri','')) + data = request.json + table_name = session.get('target_table_name', '') + label_column = data.get('label_column', '') + label = data.get('label', '') + if table_name == None or table_name == '': + print("table_name is None" + table_name) + return jsonify({}) + if check_json_column(engine, table_name) != [] and label_column != '' and label != '': + print("constraint_feature_columns") + # Get the table columns and values + table_instance = getTableInstance(engine, table_name) + # de-nested the JSON columns from the feature_columns + column_names = [] + for column in table_instance.columns: + if column.datatype != "timestamp" and column.datatype != "uuid": + column_names.append(column.name) + + feature_columns = sorted(column_names) + json_column_names = check_json_column(engine, table_name) + for column_name in json_column_names: + if column_name in feature_columns: + feature_columns.remove(column_name) + jsonKeys = get_JSON_keys(engine, table_name, column_name, label_column, label) #[('line',), ('coarse', 'fine'), ('name',), ('percent',), ('axisID', 'coarse', 'fine')] + for key in jsonKeys: + feature_columns.append( column_name + str(key) ) if len(key) > 1 else feature_columns.append( column_name + str(key).replace(',', '')) + print(feature_columns) + return jsonify({'feature_columns': feature_columns }) + + else: + print("abfhakfbdahf") + print(table_name) + print(label_column) + print(label) + return jsonify({}) + +def get_JSON_keys(engine, table_name, column_name, label_column, label): + insp = inspect(engine) + isSQLite = insp.dialect.name == 'sqlite' + + # Create a connection from the engine + with engine.connect() as conn: + # Prepare the SQL query to fetch a sample JSON object from the specified column + if insp.dialect.name == 'postgresql': + schema = getTableSchema(table_name) + query = f"SELECT DISTINCT {column_name} FROM {schema}.{table_name} WHERE {label_column} = '{label}'" + else: + query = f"SELECT DISTINCT {column_name} FROM {table_name} WHERE {label_column} = '{label}'" + + # Execute the query and fetch the result + result = conn.execute(text(query)).fetchall() + # df = pd.read_sql_query(query, engine) + conn.close() + + jsonKeys = [] + for row in result: # row is a tuple, row[0] is a dictionary + # Convert the keys to a sorted tuple for consistency + if type(row[0]) == dict: + name = tuple(sorted(row[0].keys())) + else: + name = tuple(sorted(json.loads(row[0]).keys())) + + # Append the key list if it's not already in jsonKeys + if name not in jsonKeys: + jsonKeys.append(name) + + print(jsonKeys) + return jsonKeys + + @app.route('/add-data-header', methods=['POST']) def add_data_header(): data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) @@ -275,11 +340,7 @@ def add_data_header(): obj = session.get('object_name', {}) obj[current_table] = object_column session['object_name'] = obj - return jsonify() - print(observation.to_dict()) - print("88888") - print(data_header) session['data_header'] = data_header data_header_table = generate_html_header_table() @@ -304,6 +365,7 @@ def init_data_header_table(): @app.route('/delete-data-header', methods=['POST']) def delete_data_header(): data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + obj = session.get('object_name', {}) if data_header == {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}: data_header_table = generate_html_header_table() return jsonify({'data_header_table': data_header_table}) @@ -322,6 +384,10 @@ def delete_data_header(): data_header['segment'].remove(res) elif type == 'SD': data_header['segmentData'].remove(res) + elif type == 'O': + print("delete object", res['tablename'], res) + obj.pop(res['tablename'], None) + session['object_name'] = obj session['data_header'] = data_header data_header_table = generate_html_header_table() @@ -350,13 +416,16 @@ def get_MD_info(): elif type == 'S': time = getTimeColumns(res['tablename']) # list object = getObjectColumns(res['tablename']) # list - # index = - return jsonify({'time': time, 'object': object}) + index = getIndexColumns(res['tablename']) # list + return jsonify({'time': time, 'object': object, 'index': index}) elif type == 'SD': - time = getTimeColumns(res['tablename']) # list - object = getObjectColumns(res['tablename']) - # index = - return jsonify({'time': time, 'object': object}) + data_header = session.get('data_header', {'event':[], 'measurement':[], 'segment':[], 'segmentData':[]}) + object = getObjectColumns(res['tablename']) # list + index = getIndexColumns(res['tablename']) # list + segment = [segment['label'][2] for segment in data_header['segment']] + print("Segment options") + print(segment) + return jsonify({'object': object, 'index': index, 'segment': segment}) @app.route('/get-ME-table', methods=['POST']) @@ -398,22 +467,259 @@ def get_ME_table(): return jsonify({'table_HTML': table_HTML}) +@app.route('/get-S-table', methods=['POST']) +def get_S_table(): + engine = create_engine(session.get('db_uri', '')) + data = request.json + current_data_header = session.get('current_data_header', {'tablename': '', 'type': '', 'label': [], 'features_name': []}) + table_name = current_data_header['tablename'] + type = current_data_header['type'] + object_column = data['object_column'] + optgroupLabel = data['optgroupLabel'] + object_list = [optgroupLabel, object_column] + index_column = data['index_column'] + starttime_column = data['starttime_column'] + endtime_column = data['endtime_column'] + label_list = current_data_header['label'] + + start_time = datetime.strptime(data['minDatetime'], '%Y-%m-%d %H:%M:%S') if 'minDatetime' in data else None + end_time = datetime.strptime(data['maxDatetime'], '%Y-%m-%d %H:%M:%S') if 'maxDatetime' in data else None + + print(table_name) + print(type) + print(starttime_column) + print(endtime_column) + print(object_list) + print(label_list) + + query_result = extract_S_table(engine, table_name, starttime_column, endtime_column, index_column, object_list, label_list, start_time, end_time) + table_HTML = get_ME_table_HTML(query_result) + + if start_time == None and end_time == None: + min_datetime, max_datetime = get_min_max_datetime2(engine, table_name, starttime_column, endtime_column) + return jsonify({'table_HTML': table_HTML, 'min_datetime': min_datetime, 'max_datetime': max_datetime}) + + return jsonify({'table_HTML': table_HTML}) + + +@app.route('/get-SD-table', methods=['POST']) +def get_SD_table(): + engine = create_engine(session.get('db_uri', '')) + # Get the current data header from the session + current_data_header = session.get('current_data_header', {'tablename': '', 'type': '', 'label': [], 'features_name': []}) + table_name = current_data_header['tablename'] + type = current_data_header['type'] + label_list = current_data_header['label'] + features = [] + for feature in current_data_header['features_name']: + if '(' in feature and ')' in feature: # "value('coarse', 'fine')" + feature = feature.replace("'", "") + features.append(feature) + else: + features.append(feature) + # Input data from the frontend + data = request.json # { 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'index_column': index_column, 'segment_column': segment_column} + object_column = data['object_column'] + optgroupLabel = data['optgroupLabel'] + object_list = [optgroupLabel, object_column] + index_column = data['index_column'] + segment_column = data['segment_column'] + + print("get_SD_table") + print(table_name) + print(type) + print(segment_column) + print(object_list) + print(label_list) + + index_from = int(data['index_from']) if 'index_from' in data else None + index_to = int(data['index_to']) if 'index_to' in data else None + + query_result = extract_SD_table(engine, table_name, object_list, label_list, segment_column, index_column, features, index_from, index_to) + table_HTML = get_ME_table_HTML(query_result) + + if index_from == None and index_to == None: + min_index, max_index = get_min_max_index(engine, table_name, index_column) + return jsonify({'table_HTML': table_HTML, 'min_index': min_index, 'max_index': max_index}) + + return jsonify({'table_HTML': table_HTML}) + + +@app.route('/add-data-table', methods=['POST']) +def add_data_table(): + data = request.json + selected_rows = data['selectedRowsData'] + current_row_type = data['currentRowType'] # 'E', 'M', 'S', 'SD' + data_tables = session.get('data_tables', {'O':[], 'S':[], 'SD':[]}) + + for i in selected_rows: + print(i) #{'column0': '', 'column1': '1560628', 'column2': ' label A ', 'column3': '215', 'column4': '2000-07-16 22:00:00.000000', 'column5': '2000-07-17 21:59:59.999000'} + + if current_row_type in ['E', 'M']: + data_tables['O'].extend(selected_rows) + data_tables['O'].sort(key=lambda x: datetime.strptime(x.get('column1', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f')) # Adjust 'columnTime' to your time column key + elif current_row_type == 'S': + data_tables[current_row_type].extend(selected_rows) + data_tables['S'].sort(key=lambda x: datetime.strptime(x.get('column4', '9999-12-31 23:59:59.00000'), '%Y-%m-%d %H:%M:%S.%f')) # Adjust 'columnTime' to your time column key + else : + data_tables['SD'].extend(selected_rows) + data_tables['SD'].sort(key=lambda x: x.get('column2', '')) # Group data by 'label' + + session['data_tables'] = data_tables + table_html = generate_html_data_table(data_tables[current_row_type], current_row_type) if current_row_type in ['S', 'SD'] else generate_html_data_table(data_tables['O'], 'O') + + return jsonify({'table_HTML': table_html}) + + +@app.route('/reset-machine-data-table', methods=['POST']) +def reset_machine_data_table(): + + data_tables = {'O':[], 'S':[], 'SD':[]} + session['data_tables'] = data_tables + + return jsonify({}) + + +@app.route('/export-Header-to-csv', methods=['POST']) +def export_Header_to_csv(): + data = request.get_json() # This is the data sent from the frontend + if not data: + raise ValueError("No data provided for export.") + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + if row[1] == 'O': + data.remove(row) + while len(row) < max_columns: + row.append(None) + + headers = ['type', 'label'] # List your headers here + if max_columns > (len(headers) + 1): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers)-1)]) + + # Skip the first column + df = pd.DataFrame(data) + df = df.iloc[:, 1:] + + # Rename the DataFrame headers + df.columns = headers + + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=header.csv" + response.headers["Content-Type"] = "text/csv" + + return response + + @app.route('/export-to-csv', methods=['POST']) def export_to_csv(): data = request.get_json() # This is the data sent from the frontend + if not data: + raise ValueError("No data provided for export.") + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data df = pd.DataFrame(data) + # Adjust the dataframe to skip the first column and rename the headers + headers = ['time', 'object', 'type', 'label'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers + + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=observations.csv" + response.headers["Content-Type"] = "text/csv" + + return response + + +@app.route('/export-S-to-csv', methods=['POST']) +def export_S_to_csv(): + + data = request.get_json() # This is the data sent from the frontend if not data: raise ValueError("No data provided for export.") + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data + df = pd.DataFrame(data) + # Adjust the dataframe to skip the first column and rename the headers - df = df.iloc[:, 1:] # Skip the first column - headers = ['time', 'object', 'type', 'label'] # List your headers here - num_features = len(data[0]) - len(headers) - 1 - headers.extend([f'f_{i+1}' for i in range(num_features)]) - df.columns = headers # Rename the DataFrame headers + headers = ['object', 'segment', 'segment_index', 'start', 'end'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers + # Convert the DataFrame to a CSV string + csv_output = io.StringIO() + df.to_csv(csv_output, index=False) + csv_output.seek(0) + + # Create a Flask response + response = make_response(csv_output.getvalue()) + response.headers["Content-Disposition"] = "attachment; filename=segments.csv" + response.headers["Content-Type"] = "text/csv" + return response + + +@app.route('/export-SD-to-csv', methods=['POST']) +def export_SD_to_csv(): + + data = request.get_json() # This is the data sent from the frontend + + # Find the maximum number of columns in any row + max_columns = max(len(row) for row in data) + + # Ensure all rows have the same number of columns (pad with None if necessary) + for row in data: + while len(row) < max_columns: + row.append(None) + + # Create a DataFrame from the data + df = pd.DataFrame(data) + + # Adjust the dataframe to skip the first column and rename the headers + headers = ['object', 'segment', 'segment_index', 'label'] # List your headers here + if max_columns > len(headers): + headers.extend([f'f_{index + 1}' for index in range(max_columns-len(headers))]) + + # Rename the DataFrame headers + df.columns = headers # Convert the DataFrame to a CSV string csv_output = io.StringIO() @@ -422,11 +728,12 @@ def export_to_csv(): # Create a Flask response response = make_response(csv_output.getvalue()) - response.headers["Content-Disposition"] = "attachment; filename=exported_data.csv" + response.headers["Content-Disposition"] = "attachment; filename=segment_data.csv" response.headers["Content-Type"] = "text/csv" return response + def readHeaderHTML(str): soup = BeautifulSoup(str, 'html.parser') tds = soup.find_all('td') @@ -434,25 +741,28 @@ def readHeaderHTML(str): res = {'tablename': '', 'type': '', 'label': [], 'features_name': []} res['tablename'] = tds[1].get_text() res['type'] = tds[2].get_text() - res['label'] = json.loads(tds[3]['data-value']) - features = [] - for td in tds[4:]: - data_value = td['data-value'] - value_list = json.loads(data_value) - if value_list[0] == '': - a = value_list[1] - features.append(a) - else: - a = value_list[0] + '(' - for i in range(len(value_list)-1): - a += "'" + value_list[i+1] + "', " - a = a[:len(a)-2] - a += ')' - if a not in features: + if res['type'] == 'O': + data_value = tds[3].get_text() + else: + res['label'] = json.loads(tds[3]['data-value']) + features = [] + for td in tds[4:]: + data_value = td['data-value'] + value_list = json.loads(data_value) + if value_list[0] == '': + a = value_list[1] features.append(a) - res['features_name'] = features # ['machine_id', "value('coarse', 'fine')"] + else: + a = value_list[0] + '(' + for i in range(len(value_list)-1): + a += "'" + value_list[i+1] + "', " + a = a[:len(a)-2] + a += ')' + if a not in features: + features.append(a) + res['features_name'] = features # ['machine_id', "value('coarse', 'fine')"] - print(features) + print(features) return res @@ -501,7 +811,7 @@ def handle_json_column(engine, table_name, column_name): if name not in jsonKeys: jsonKeys.append(name) - print(jsonKeys) + print(jsonKeys) return jsonKeys @@ -557,7 +867,7 @@ def generate_html_header_table(): for key in content.keys(): for dict_item in content[key]: table_html += f"<tr onclick='getRowData(this)'>" - table_html += "<td><button type='button' class='btn-close' aria-label='Close' data-bs-dismiss='modal' onclick='deleteRow1(event, this)'></button></td>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow1(event, this)'></button></td>" table_html += f"<td>{dict_item.get('tablename', '')}</td>" table_html += f"<td data-id>{dict_item.get('type', '')}</td>" print("723723") @@ -584,11 +894,80 @@ def generate_html_header_table(): table_html += f"<td data-value='{data_value}'>{value}</td>" index += 1 table_html += "</tr>" + + # Add object table row to header table + obj = session.get('object_name', {}) + for key in obj.keys(): + table_html += "<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow1(event, this)'></button></td>" + table_html += f"<td>{key}</td>" + table_html += f"<td>O</td>" + table_html += f"<td>{obj[key]}</td>" + table_html += "</tr>" + table_html += "</tbody></table>" print(table_html) return table_html +def generate_html_data_table(data_tables:list, table_type:str): + # Generate column headers + table_html = "<thead><tr>" + table_html += "<th></th>" + if table_type == 'O': + table_html += "<th class='uk-table-expand'>time</th>" + table_html += "<th>object</th>" + table_html += "<th>type</th>" + table_html += "<th class='uk-table-expand'>label</th>" + max_features = max(len(row) - 5 for row in data_tables) # Assuming first 4 keys are not features + for i in range(max_features): + table_html += f"<th>f_{i+1}</th>" + elif table_type == 'S': + table_html += "<th>object</th>" + table_html += "<th class='uk-table-expand'>segment</th>" + table_html += "<th>segment_index</th>" + table_html += "<th class='uk-table-expand'>start</th>" + table_html += "<th class='uk-table-expand'>end</th>" + + elif table_type == 'SD': + table_html += "<th>object</th>" + table_html += "<th class='uk-table-expand'>segment</th>" + table_html += "<th>segment_index</th>" + table_html += "<th class='uk-table-expand'>label</th>" + max_features = max(len(row) - 5 for row in data_tables) # Assuming first 4 keys are not features + for i in range(max_features): + table_html += f"<th>f_{i+1}</th>" + + table_html += "</tr></thead><tbody>" + + # Generate table rows + if table_type == 'SD': + for row in data_tables: + table_html += f"<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' onclick='deleteRow2(event, this)'></button></td>" + for i in range(len(row) - 1): + if i == 1: + table_html += f"<td>{row.get('column3', '')}</td>" + elif i == 2: + table_html += f"<td>{row.get('column4', '')}</td>" + elif i == 3: + table_html += f"<td>{row.get('column2', '')}</td>" + else: + table_html += f"<td>{row.get('column' + str(i+1), '')}</td>" + table_html += "</tr>" + else: + for row in data_tables: + table_html += f"<tr>" + table_html += "<td><button type='button' class='btn-close' aria-label='Close' data-bs-dismiss='modal' onclick='deleteRow2(event, this)'></button></td>" + for i in range(len(row) - 1): + table_html += f"<td>{row.get('column' + str(i+1), '')}</td>" + table_html += "</tr>" + + table_html += "</tbody>" + print(table_html) + return table_html + + def generate_html_table(content): if not content: return "No data found." @@ -616,6 +995,7 @@ def getTimeColumns(table_name:str) -> list: schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name columns = insp.get_columns(table_name, schema) timestamp_columns = [column['name'] for column in columns if str(column['type']) == 'TIMESTAMP'] + print("timestamp columns") print(timestamp_columns) return timestamp_columns @@ -626,12 +1006,25 @@ def getObjectColumns(table_name:str) -> list: insp = inspect(engine) schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name columns = insp.get_columns(table_name, schema) - object_columns = [column['name'] for column in columns if str(column['type']) == 'VARCHAR' or str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC'] + object_columns = [column['name'] for column in columns if str(column['type']) == 'VARCHAR' or str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC' or str(column['type']) == 'TEXT' or str(column['type']) == 'SMALLINT'] + print("object columns") print(object_columns) return object_columns +def getIndexColumns(table_name:str) -> list: + engine = create_engine(session.get('db_uri', '')) + insp = inspect(engine) + schema = getTableSchema(table_name) if insp.dialect.name == 'postgresql' else insp.default_schema_name + columns = insp.get_columns(table_name, schema) + index_columns = [column['name'] for column in columns if str(column['type']) == 'INTEGER' or str(column['type']) == 'NUMERIC' or str(column['type']) == 'BIGINT' or str(column['type']) == 'SMALLINT'] + print("index columns") + print(index_columns) + + return index_columns + + def query_database_for_table_content(engine, table_name, number=200): # Initialize content list content_list = [] @@ -727,6 +1120,46 @@ def get_min_max_datetime(engine, table_name, time_column, start_time=None, end_t return None, None +def get_min_max_datetime2(engine, table_name, starttime_column, endtime_column, start_time=None, end_time=None): + schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name + # Formulate the SQL query using the text function + query = text(f"SELECT MIN({starttime_column}) AS start_datetime, MAX({endtime_column}) AS end_datetime FROM {schema}.{table_name};") + + # Execute the query + with engine.connect() as connection: + row = connection.execute(query).mappings().fetchone() + + # Extract the min and max datetime values + if row: + min_datetime, max_datetime = row['start_datetime'], row['end_datetime'] + print("Minimum datetime:", min_datetime) + print("Maximum datetime:", max_datetime) + return min_datetime, max_datetime + else: + print("No datetimes found.") + return None, None + + +def get_min_max_index(engine, table_name, index_column): + schema = getTableSchema(table_name) if engine.dialect.name == 'postgresql' else engine.dialect.default_schema_name + # Formulate the SQL query using the text function + query = text(f"SELECT MIN({index_column}) AS start_index, MAX({index_column}) AS end_index FROM {schema}.{table_name};") + + # Execute the query + with engine.connect() as connection: + row = connection.execute(query).mappings().fetchone() + + # Extract the min and max datetime values + if row: + min_index, max_index = row['start_index'], row['end_index'] + print("Minimum index:", min_index) + print("Maximum index:", max_index) + return min_index, max_index + else: + print("No indexes found.") + return None, None + + def extract_ME_table(engine, table_name: str, type: str, time_column: str, object: list, label: list, features_name: list, start_time: datetime = None, end_time: datetime = None) -> list: conn = engine.connect() insp = inspect(engine) @@ -799,9 +1232,6 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec if end_time: params['end_time'] = end_time - - # res = conn.execute(text(sql_query), params).fetchall() - # Print the query for debugging print("SQL Query:", sql_query) print("Parameters:", params) @@ -831,6 +1261,197 @@ def extract_ME_table(engine, table_name: str, type: str, time_column: str, objec return final_res +def extract_S_table(engine, table_name: str, starttime_column: str, endtime_column: str, index_column: str, object: list, label: list, start_time: datetime = None, end_time: datetime = None) -> list: + conn = engine.connect() + insp = inspect(engine) + database_name = insp.dialect.name # 'postgresql' or 'sqlite' + table_instance = getTableInstance(engine, table_name) + label_column = label[1].strip() + label_value = label[2].strip() + object_column_value = object[1].strip() + join_clause = '' + + full_table_name = f"{table_instance.schema}.{table_instance.name}" if table_instance.schema else table_instance.name + sql_columns = [] + + # Handling object_column logic + if object[0].strip() != 'self' and object[0].strip() == 'col': + print("1") + object_column = table_instance.getColumn(object_column_value) + object_column_name = object_column.fkof.table.name if object_column.fkof else '' + if object_column and object_column.fkof and object_column_name in session.get('object_name', {}): + related_table_instance = getTableInstance(engine, object_column_name) + full_related_table_name = f"{related_table_instance.schema}.{related_table_instance.name}" if related_table_instance.schema else related_table_instance.name + join_clause = f"LEFT JOIN {full_related_table_name} ON {full_table_name}.{object_column.name} = {full_related_table_name}.{object_column.fkof.name}" + sql_columns.append(f"{full_related_table_name}.{session.get('object_name').get(object_column_name)} AS {object_column_value}") + else: + sql_columns.append(f"{full_table_name}.{object_column.name}") + print("12") + # If label[0] is not 'self', add it to SQL columns + if label[0].strip() != 'self': + sql_columns.append(f"{full_table_name}.{label_column}") + print("123") + + # Adding index, starttime, endtime columns to the select clause + sql_columns.append(f"{full_table_name}.{index_column}") + sql_columns.append(f"{full_table_name}.{starttime_column}") + sql_columns.append(f"{full_table_name}.{endtime_column}") + + # Adding JSON extractions to the select clause + sql_select = ', '.join(sql_columns) + + # Constructing SQL query + sql_joins = join_clause + if label[0].strip() == 'col': + sql_where = f"WHERE {full_table_name}.{label_column} = :label_value" + if start_time: + sql_where += f" AND {full_table_name}.{starttime_column} >= :start_time AND {full_table_name}.{endtime_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{starttime_column} <= :end_time AND {full_table_name}.{endtime_column} <= :end_time" + else: + sql_where = '' + if start_time: + sql_where += f"WHERE {full_table_name}.{starttime_column} >= :start_time AND {full_table_name}.{endtime_column} >= :start_time" + if end_time: + sql_where += f" AND {full_table_name}.{starttime_column} <= :end_time AND {full_table_name}.{endtime_column} <= :end_time" + + sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {starttime_column} ASC LIMIT 500" + print("12345") + # Executing the query + params = {'label_value': label_value} + if start_time: + params['start_time'] = start_time + if end_time: + params['end_time'] = end_time + + # Print the query for debugging + print("SQL Query:", sql_query) + print("Parameters:", params) + + # Executing the query + try: + res = conn.execute(text(sql_query), params).fetchall() + except Exception as e: + print(f"Error executing query: {e}") + return [] + + # Append object and label values if necessary + final_res = [] + for row in res: + modified_row = list(row) + if object[0].strip() == 'self': + modified_row.insert(0, object_column_value) + if label[0].strip() == 'self': + label_index = 1 if object[0].strip() != 'self' else 0 + modified_row.insert(label_index, label[2]) + + final_res.append(modified_row) + + for row in final_res: + print(row) + + return final_res + + +def extract_SD_table(engine, table_name: str, object: list, label: list, segment_column: str, index_column: str, features_name: list, index_from: int = None, index_to: int = None) -> list: + conn = engine.connect() + insp = inspect(engine) + database_name = insp.dialect.name # 'postgresql' or 'sqlite' + table_instance = getTableInstance(engine, table_name) + label_column = label[1].strip() + label_value = label[2].strip() + object_column_value = object[1].strip() + join_clause = '' + + full_table_name = f"{table_instance.schema}.{table_instance.name}" if table_instance.schema else table_instance.name + sql_columns = [] + + # Handling object_column logic + if object[0].strip() != 'self' and object[0].strip() == 'col': + print("1") + object_column = table_instance.getColumn(object_column_value) + object_column_name = object_column.fkof.table.name if object_column.fkof else '' + if object_column and object_column.fkof and object_column_name in session.get('object_name', {}): + related_table_instance = getTableInstance(engine, object_column_name) + full_related_table_name = f"{related_table_instance.schema}.{related_table_instance.name}" if related_table_instance.schema else related_table_instance.name + join_clause = f"LEFT JOIN {full_related_table_name} ON {full_table_name}.{object_column.name} = {full_related_table_name}.{object_column.fkof.name}" + sql_columns.append(f"{full_related_table_name}.{session.get('object_name').get(object_column_name)} AS {object_column_value}") + else: + sql_columns.append(f"{full_table_name}.{object_column.name}") + print("12") + # If label[0] is not 'self', add it to SQL columns + if label[0].strip() != 'self': + sql_columns.append(f"{full_table_name}.{label_column}") + print("123") + # Adding index columns to the select clause + sql_columns.append(f"{full_table_name}.{index_column}") + # Handling JSON extractions + json_extractions = [] + for feature in features_name: + if '(' in feature and ')' in feature: + column_name, keys = feature[:-1].split('(') + keys = keys.split(', ') + for key in keys: + if database_name == 'postgresql': + json_extraction = f"{full_table_name}.{column_name}->>'{key}' AS {key}" + elif database_name == 'sqlite': + json_extraction = f"json_extract({full_table_name}.{column_name}, '$.{key}') AS {key}" + json_extractions.append(json_extraction) + else: + sql_columns.append(f"{full_table_name}.{feature}") + print("1234") + # Adding JSON extractions to the select clause + sql_select = ', '.join(sql_columns + json_extractions) + + # Constructing SQL query + sql_joins = join_clause + if label[0].strip() == 'col': + sql_where = f"WHERE {full_table_name}.{label_column} = :label_value" + if index_from: + sql_where += f" AND {full_table_name}.{index_column} >= :index_from" + if index_to: + sql_where += f" AND {full_table_name}.{index_column} <= :index_to" + else: + sql_where = '' + if index_from: + sql_where += f" WHERE {full_table_name}.{index_column} >= :index_from" + if index_to: + sql_where += f" AND {full_table_name}.{index_column} <= :index_to" + + sql_query = f"SELECT {sql_select} FROM {full_table_name} {sql_joins} {sql_where} ORDER BY {index_column} ASC LIMIT 500" + print("12345") + # Executing the query + params = {'label_value': label_value, 'index_from': index_from, 'index_to': index_to} + + # Print the query for debugging + print("SQL Query:", sql_query) + print("Parameters:", params) + + # Executing the query + try: + res = conn.execute(text(sql_query), params).fetchall() + except Exception as e: + print(f"Error executing query: {e}") + return [] + + # Append object and label values if necessary + final_res = [] + for row in res: + modified_row = list(row) + if object[0].strip() == 'self': + modified_row.insert(0, object_column_value) + if label[0].strip() == 'self': + label_index = 1 if object[0].strip() != 'self' else 0 + modified_row.insert(label_index, label[2]) + modified_row.insert(2, segment_column) + final_res.append(modified_row) + + for row in final_res: + print(row) + + return final_res + + def get_ME_table_HTML(data: list) -> str: # Start the HTML <body> content with an opening <table> tag html_content = "" @@ -841,7 +1462,7 @@ def get_ME_table_HTML(data: list) -> str: for cell in row: if isinstance(cell, datetime): # cell = cell.isoformat() - cell = cell.strftime('%Y-%m-%d %H:%M:%S:%f') + cell = cell.strftime('%Y-%m-%d %H:%M:%S.%f') html_content += f"<td>{cell}</td>" html_content += "</tr>\n" diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df4fecfba13b7c10abc18bb87debc8b989d42686 Binary files /dev/null and b/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/models/__pycache__/models.cpython-311.pyc b/models/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..780e177e25900a2bae7265a22e612d24724b20c3 Binary files /dev/null and b/models/__pycache__/models.cpython-311.pyc differ diff --git a/models/__pycache__/models.cpython-39.pyc b/models/__pycache__/models.cpython-39.pyc index 2e0a8d9d8d7c2f1d91d5104ea76cf387a4a83dbe..4dcb5a8fa921e8dc45deed97019797f6eac214e2 100644 Binary files a/models/__pycache__/models.cpython-39.pyc and b/models/__pycache__/models.cpython-39.pyc differ diff --git a/models/models.py b/models/models.py index f01eeb36ba9657d8d40b20a327537ccb37de939e..9c517d5a92206c629955aaf2bfd651ce5d57d44b 100644 --- a/models/models.py +++ b/models/models.py @@ -1,4 +1,3 @@ -from sqlalchemy import ARRAY, BIGINT, BOOLEAN, DOUBLE_PRECISION, FLOAT, INTEGER, JSON, NUMERIC, SMALLINT, TIMESTAMP, UUID, VARCHAR, MetaData, String, create_engine, text, inspect from sqlalchemy.types import UserDefinedType import re @@ -7,7 +6,7 @@ class Observation_Spec: def __init__(self, tablename:str, type:str, label:list[str], features_name:list = None): self.tablename = tablename self.type = type # 'event' or 'measurement' - self.label = label # (self-define or from column, column name, column value orself-define label) + self.label = label # (self-define or from column, column name, column value or self-define label) self.features_name = features_name if features_name else [] def add_feature(self, name): diff --git a/templates/app.html b/templates/app.html index f8e43df24b8acb232936589e2520ef1094a7c523..44e755e687a21cce520d16cf6b2a50aad96e83cc 100644 --- a/templates/app.html +++ b/templates/app.html @@ -222,7 +222,7 @@ } .uk-table-hover tbody tr:hover { background-color: #5ea9e2; - color: white; + color:#add8e6; } .terminal.resizing .terminal-header { cursor: ns-resize; @@ -283,7 +283,16 @@ position: sticky; top: 0; } - + .uk-tooltip { + border-radius: 5px !important; + } + .accordion-container { + margin-top: 26px; + display: flex; + flex-direction: column; + height: 83vh; /* or adjust as necessary */ + overflow: hidden; /* This prevents the overall container from scrolling */ + } </style> </head> <body> @@ -292,14 +301,14 @@ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous"></script> <div id="sidebar1"> <!--class="uk-panel uk-panel-scrollable" --> - <legend>Database:</legend> + <legend uk-tooltip="title: Show the database name.; pos: right">Database:</legend> <div class="mb-3"> <label for="disabledTextInput" class="form-label" style="margin-top: 10px;">{{database}}.db</label> </div> <ul uk-accordion> <li> - <a class="uk-accordion-title" href="#">Database URL</a> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Provide a valid databsae connection here.; pos: right">Database URL</a> <div class="uk-accordion-content"> <form method="post" action="/"> @@ -315,7 +324,7 @@ <li> - <a class="uk-accordion-title" href="#">Filter</a> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Use these filters to refine the tables displayed below; pos: right">Filter</a> <div class="uk-accordion-content"> <div class="accordion"> @@ -333,7 +342,7 @@ <label class="form-check-label" for="showAllClick" style="margin-top: 3px;"> Primary Tables </label> - <select class="form-select" name="schema" id="schemaSelect" onchange="this.form.submit()" onclick="openTables()" style="padding: 0px 1px 2px 1px;"> + <select class="form-select" name="schema" id="schemaSelect" onchange="this.form.submit()" style="padding: 0px 1px 2px 1px;"> <option value="">Choose here</option> {% for schema in schemas %} <option value="{{ schema }}" {% if schema == schema_Selected %} selected {% endif %}>{{ schema }}</option> @@ -385,15 +394,15 @@ </li> - <li class="uk-class"> - <a class="uk-accordion-title" href="#">Tables</a> + <li class="uk-class uk-open"></li> + <a class="uk-accordion-title" href="#" style="text-decoration: none;" uk-tooltip="title: Reorder the tables by dragging the rows to the designated area below.; pos: right">Tables</a> <div class="uk-accordion-content"> <div class="border border-secondary rounded" id="table_list" style="margin-bottom: 5px; margin-top: -10px;"> <div id="table_list_source"> <div id="show_tables1" uk-sortable="group: sortable-group" class="uk-list uk-list-collapse"> {% for table in tables %} - <div id="show_tables2" class="uk-margin" style="height: 15px; margin-bottom: -4px;"> + <div id="show_tables2" class="uk-margin" style="height: 15px; margin-bottom: -4px; width: 230px;"> <div class="list-group-item-action uk-card uk-card-default uk-card-body uk-card-small " style="height: 15px; padding-top: 5px;">{{ table }}</div> </div> {% endfor %} @@ -406,27 +415,26 @@ </ul> - <legend style="margin-top: 3px;">Target Tables:</legend> + <legend style="margin-top: 3px;" uk-tooltip="title: Activate the selected table to populate the data in the scrollable terminal section and for Step 2 procedures.; pos: right">Target Tables:</legend> <div class="border border-secondary rounded" id="table_list" > <div id="dropped_items" uk-sortable="group: sortable-group" class="uk-list uk-list-collapse "> - <!-- <div > --> {% for item in dropped_items %} - <div class="uk-margin" style="height: 15px; margin-bottom: -4px;"> + <div class="uk-margin" style="height: 15px; margin-bottom: -4px; width: 230px;"> <div class="list-group-item-action uk-card uk-card-default uk-card-body uk-card-small" style="height: 15px; padding-top: 5px;">{{ item }}</div> </div> {% endfor %} - <!-- </div> --> </div> </div> - <button id="resetButton" class="btn btn-secondary headerButton">Reset</button> + <button id="resetButton" class="btn btn-secondary headerButton" uk-tooltip="title: Empty target table.; pos: bottom">Reset</button> </div> <div id="content"> <button class="uk-button uk-button-default uk-button-small custom-push-button" type="button" uk-toggle="target: #sidebar1" onclick="toggleSidebar()"></button> <ul uk-tab class="uk-flex-center" data-uk-tab="{connect:'#my-id'}" style="margin-top: 10px; z-index: 0;"> - <li><a href="#">ERD</a></li> - <li><a href="#">Create Data Header</a></li> - <li><a href="#">Create Data instance</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display a comprehensive summary of the database contents.;pos: bottom">Step1: Overview</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Construct the data header based on the selection from the prior step, to be utilized in Step 3.;pos: bottom">Step2: Create Data Header</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Generate a detailed data table corresponding to each row of the data header, with options for additional refinement.;pos: bottom">Step3: Create Data instance</a></li> + <li><a href="#" style="text-decoration: none;" uk-tooltip="title: Display the generated data tables, with options for exporting to csv files.;pos: bottom">Step4: Established Data Table</a></li> </ul> <!-- <div> --> @@ -476,10 +484,10 @@ <div style="flex-grow: 1; padding-left: 10px; margin-top: 3px; display: flex; flex-direction: column;"> <fieldset> - <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;">Data Header</legend> + <legend style="margin: .2em .2em .4em -.4em; color:#5ea9e2; border-bottom: 1px solid gray; padding-bottom: 5px; padding-right: 3px;" uk-tooltip="title: Define the structure of the header table to be used in Step 3; pos: right">Data Header</legend> - <div class="mb-3"> - <label class="form-label" style="margin-top: 14px">Type</label> + <div class="mb-3" style="padding-bottom: 5px; border-bottom: 1px dashed black; margin-bottom: 3px;"> + <label class="form-label" style="margin-top: 8px;" uk-tooltip="title: Select the type of data header; each type requires different information to be specified; pos: left">Type</label> <div class="uk-grid-small uk-child-width-auto uk-grid"> <label><input class="uk-radio type-radio" type="radio" name="radio1" value="measurement" checked> Measurement </label><br><br> <label><input class="uk-radio type-radio" type="radio" name="radio1" value="event"> Event </label><br><br> @@ -491,13 +499,13 @@ <div class="mb-3" id="self-defined-object-selection" style="display: none; border-bottom: 1px dashed black;"> - <label class="form-label">Self-defined Object</label><br> - <input type="text" id="defined-object" class="form-control headerSelect"> - <button class="btn btn-primary headerButton" onclick="selfdefinedObject()">add</button> + <label class="form-label" uk-tooltip="title: Create a custom object name that can be used in a header instead of using predefined database table data; pos: left">Self-defined Object</label><br> + <input type="text" id="defined-object" class="form-control headerSelect" placeholder="Enter object name"> + <button class="btn btn-primary headerButton" onclick="selfdefinedObject()">add</button><span id="selfdefinedObject" style="color: red; font-size: 80%; margin-left: 2px;"></span> </div> <div class="mb-3" id="object-column-selection" style="display: none;"> - <label class="form-label">Object name</label><br> + <label class="form-label" uk-tooltip="title: Designate a specific column from the selected table to represent unique object names; pos: left">Object name</label><br> <select id="select_column_object" class="form-select headerSelect" name="column" style="margin-right: 5px; display: inline;"> {% for column in columns %} <option>Select a table first...</option> @@ -506,14 +514,14 @@ </select><span style="color: red; font-size: 80%;">*</span> </div> - <div class="mb-3" id="self-defined-label-selection"> - <label class="form-label">Self-defined Label</label><br> - <input type="text" id="defined-label" class="form-control headerSelect"> - <button class="btn btn-primary headerButton" onclick="addLabel()">add</button> + <div class="mb-3" id="self-defined-label-selection" style="border-bottom: 1px dashed black;"> + <label class="form-label" uk-tooltip="title: Create a custom label that can be used in the label selection below instead of using predefined database table columns.; pos: left">Self-defined Label</label><br> + <input type="text" id="defined-label" class="form-control headerSelect" placeholder="Enter label name"> + <button class="btn btn-primary headerButton" onclick="addLabel()">add</button><span id="addLabel" style="color: red; font-size: 80%; margin-left: 2px;"></span> </div> <div class="mb-3" id="label-selection"> - <label class="form-label">Label</label> + <label class="form-label" uk-tooltip="title: Choose a label from the available columns in the selected database table.; pos: left">Label</label> <select id="select_column" class="form-select headerSelect" name="column" onchange='handleLabelColumnClick(value)'> {% for column in columns %} <option>Select a table first...</option> @@ -538,18 +546,20 @@ </div> <div class="mb-3" id="featuresSelection"> - <label class="form-label">Feature</label><br /> - <select id="select_features" name = "value_column[]" data-placeholder="Slect a table first..." multiple class="chosen-select" tabindex="4"> - {% for column in columns %} - <option>Column select...</option> - <option value="{{label}}">{{ label }}</option> - {% endfor %} + <label class="form-label" uk-tooltip="title: Select one or multiple feature columns from the chosen table to include in the data header.; pos: left">Feature</label><br /> + <select id="select_features" name = "value_column[]" data-placeholder="Slect a table first..." multiple class="chosen-select"> </select> - <script> $(".chosen-select").chosen({tabindex: 6}); $("#select_features").css('font-size','25px'); </script> + <script> + $(document).ready(function() { + $(".chosen-select").chosen({ + tabindex: 2 + }).css('font-size', '25px'); + }); + </script> <span id="span"></span><span style="color: red; font-size: 80%;">*</span><br> </div> - <button type="submit" class="btn btn-primary headerButton" onclick="submitDataHeader()" style="margin-top: -11px;">Submit</button> + <button type="submit" class="btn btn-primary headerButton" onclick="submitDataHeader()" style="margin-top: -11px;">Submit</button><span id="submitDataHeader" style="color: red; font-size: 80%; margin-bottom: 5px;"></span> </fieldset> </div> @@ -564,7 +574,7 @@ <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetDataHeaderTable()">Reset</button> </div> <div class="uk-overflow-auto" id="data-header-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;"> - <table class='uk-table uk-table-small uk-table-hover uk-table-divider'> + <table id="H-table" class='uk-table uk-table-small uk-table-hover uk-table-divider'> <thead> <tr> <th></th> @@ -578,22 +588,22 @@ <div class="mb-3"> <h4 style="display: inline; margin-left: .2em;">Machine Data Table (LIMIT 500)</h4> <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="resetMachineDataTable()">Reset</button> - <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="">Add</button> - <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="exportSelectedRowsToCSV()">Export</button> + <button type="submit" class="btn btn-primary uk-button-small headerButton" style="margin-top: -3px;" onclick="addDataTable()">Add</button> + <span id="addDataTable" style="color: red; font-size: 80%; margin-left: 2px; margin-bottom: 1px;"></span> </div> <div class="uk-overflow-auto" id="machine-data-table" style="flex-grow: 1; overflow-y: auto; max-height: max-content; margin-top: -20px;" > <table id="MD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> <thead> <tr class="uk-table-middle"> <th class="uk-table-shrink"> - <input class="uk-checkbox" onclick="click_all_MD_table(this)" type="checkbox" aria-label="Checkbox" style="margin-top: 7px;"> + <input class="uk-checkbox" id="click_all" onclick="click_all_MD_table(this)" type="checkbox" aria-label="Checkbox" style="margin-top: 7px;"> </th> <th data-id="time" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> time - <select id="table_select_time" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option>Select a data header first...</option> + <select id="table_select_time" class="observation-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a data header first...</option> </select> </span> </th> @@ -601,7 +611,7 @@ <th data-id="object" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> object - <select id="table_object" class="time-object-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <select id="table_object" class="observation-select segment-select segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> <option value="no">Select a data header first...</option> <optgroup id="defined_object_values" label="self-defined object"> @@ -615,15 +625,29 @@ </th> <th data-id="type" class="uk-width-small">type</th> - <th data-id="label" class="uk-table-expand">label</th> - <th data-id="segment" class="uk-table-expand">segment</th> - <th data-id="index" class="uk-table-expand">index</th> + <th data-id="label" class="uk-width-small">label</th> + <th data-id="segment" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + segment + <select id="segment-label-select" class="segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a column first...</option> + </select> + </span> + </th> + <th data-id="index" class="uk-table-expand"> + <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> + index + <select id="index-select" class="segment-select segment-data-select" style="width: 160px; height: 20px; margin-top: -3px; margin-left: 3px;"> + <option value="no">Select a column first...</option> + </select> + </span> + </th> <th data-id="starttime" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> start time - <select id="starttime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option val="no">Select a column first...</option> + <select id="starttime-select" class="segment-select" style="width: 160px; height: 20px; margin-top: 6px; margin-left: 3px;"> + <option value="no">Select a column first...</option> </select> </span> </th> @@ -631,8 +655,8 @@ <th data-id="endtime" class="uk-table-expand"> <span style="display: flex; flex-direction: row; margin-bottom: -4px;"> end time - <select id="endtime-select" style="width: 80px; height: 20px; margin-top: -3px; margin-left: 3px;"> - <option val="no">Select a column first...</option> + <select id="endtime-select" class="segment-select" style="width: 160px; height: 20px; margin-top: 6px; margin-left: 3px;"> + <option value="no">Select a column first...</option> </select> </span> </th> @@ -645,6 +669,36 @@ </div> </div> </li> + <li> + <div class="accordion-container"> + <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="resetMachineDataTable2()" style="position: absolute; right: 13vh; top:32px;">Reset</button> + <button type="submit" class="btn btn-primary uk-button-small headerButton" onclick="exportSelectedRowsToCSV()" style="position: absolute; right: 1vh; top:32px;">Export</button> + <ul uk-accordion> + <li class="uk-open"> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Observation Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <!-- <h4 style="display: inline; margin-left: .2em;">Observation Table</h4> --> + <table id="ME-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + <li> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <table id="S-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + <li> + <a class="uk-accordion-title" style="text-decoration: none; background-color: #ccc;" href>Segment Data Table</a> + <div class="uk-accordion-content" style="max-height: 60vh; flex: 1; overflow-y: auto;"> + <table id="SD-table" class="uk-table uk-table-hover uk-table-small uk-table-middle uk-table-divider uk-table-striped" style="cursor: pointer;"> + </table> + </div> + </li> + </ul> + </div> + </li> </ul> <!-- </div> --> @@ -666,17 +720,16 @@ <div id="sidebar2"> <ul uk-accordion> <li> - <a class="uk-accordion-title" href="#">Filter</a> + <a class="uk-accordion-title" style="text-decoration: none;" href="#">Filter</a> <div class="uk-accordion-content"> - <div class="accordion"> <div class="accordion-item"> <h2 class="accordion-header"> - <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="true" aria-controls="collapse1"> Time </button> </h2> - <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> + <div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> <div class="accordion-body"> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="//code.jquery.com/jquery-3.6.0.min.js"></script> @@ -693,6 +746,35 @@ </div> </div> </div> + <div class="accordion-item"> + <h2 class="accordion-header"> + <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="true" aria-controls="collapse2"> + Segment Index + </button> + </h2> + <div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#accordionExample"> + <div class="accordion-body"> + <p style="margin: -4px -21px 5px -17px; padding: 0px;"> + <label for="fromInput">From: </label> + <input type="number" id="fromInput" min="0" step="1" style="width: 8vh;"/><br><br> + <label for="toInput">To: </label> + <input type="number" id="toInput" min="0" step="1" style="width: 8vh; margin-left: 20px;"/> + </p> + <button type="submit" class="btn btn-primary headerButton" style="margin: 10px 0px -5px -10px;" onclick="filter_SD_data_table()">Submit</button> + <script> + var fromInput = document.getElementById('fromInput'); + var toInput = document.getElementById('toInput'); + + fromInput.addEventListener('input', function() { + toInput.min = this.value; + if (parseInt(toInput.value) < parseInt(this.value)) { + toInput.value = this.value; + } + }); + </script> + </div> + </div> + </div> </div> </div> @@ -705,6 +787,8 @@ </div> <!-----------------------------------------------------------------------------------------------------> <script> + var currentRowType = null; // Defined at a higher scope, accessible globally + var jq = jQuery.noConflict(); function initializeSlider(minDatetime, maxDatetime) { // Default values if min or max datetime is not provided @@ -770,7 +854,38 @@ } - function exportSelectedRowsToCSV() { + // Function to check if the necessary information is filled in the Machine Data table + function checkBottomTableInfo() { + // Check the values of inputs/selects in the bottom table + var objectSelected = document.getElementById('table_object').value; + var timeSelected = document.getElementById('table_select_time').value; + var segmentSelected = document.getElementById('segment-label-select').value; + var indexSelected = document.getElementById('index-select').value; + var starttimeSelected = document.getElementById('starttime-select').value; + var endtimeSelected = document.getElementById('endtime-select').value; + + if (currentRowType === 'M' || currentRowType === 'E'){ + // Check if the values are not empty or not equal to the default "no" value + if (objectSelected === 'no' || timeSelected === 'no') { + return false; // Information is not complete + } + } else if (currentRowType === 'S') { + if (objectSelected === 'no' || indexSelected === 'no' || starttimeSelected === 'no' || endtimeSelected === 'no') { + return false; // Information is not complete + } + } else if (currentRowType === 'SD') { + if (objectSelected === 'no' || segmentSelected === 'no' || indexSelected === 'no') { + return false; // Information is not complete + } + } else { + console.error('Unknown type:', currentRowType); + return false; // Information is not complete + } + return true; // Information is complete + } + + + function addDataTable() { // Collect all selected rows from the Machine Data Table var selectedRowsData = []; document.querySelectorAll('#MD-table input[type="checkbox"]:checked').forEach(function(checkbox) { @@ -781,27 +896,191 @@ }); selectedRowsData.push(rowData); }); + document.getElementById("click_all").checked = false; - // Send the selected rows data to the Flask backend - fetch('/export-to-csv', { - method: 'POST', - body: JSON.stringify(selectedRowsData), - headers: { - 'Content-Type': 'application/json' + if (currentRowType != null && selectedRowsData.length > 0) { + // Check if the necessary information is filled in the Machine Data table + if (!checkBottomTableInfo()) { + alert('Please fill in the necessary information in the Machine Data table.'); + return; } - }) - .then(response => response.blob()) // Get the file blob from the response - .then(blob => { - // Create a link element and click it to download the file - var url = window.URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = 'exported_data.csv'; - document.body.appendChild(a); - a.click(); - a.remove(); - }) - .catch(error => console.error('Error:', error)); + document.getElementById("addDataTable").textContent = "Add successfully!"; + setTimeout(function() { + document.getElementById("addDataTable").textContent = ""; + }, 2000); + + // Send the selected rows data to the Flask backend + fetch('/add-data-table', { + method: 'POST', + body: JSON.stringify({'selectedRowsData': selectedRowsData, 'currentRowType': currentRowType}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + // Update the Machine Data Table with the new data + if (currentRowType == 'M' || currentRowType == 'E') { + const table = document.getElementById('ME-table'); + table.innerHTML = data['table_HTML']; + } else if (currentRowType == 'S') { + const table = document.getElementById('S-table'); + table.innerHTML = data['table_HTML']; + } else if (currentRowType == 'SD') { + const table = document.getElementById('SD-table'); + table.innerHTML = data['table_HTML']; + } + }) + + } else if (currentRowType == null) { + alert('Please select a row from the Data Header table.'); + return; + } else { + alert('Please select a row from the Machine Data table.'); + return; + } + } + + + function exportSelectedRowsToCSV() { + //////////////// Collect all selected rows from the Machine Data Table -- Observation Table///////////////// + var selectedRowsData = []; + document.querySelectorAll('#ME-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsData.push(rowData); + }); + if (selectedRowsData.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsData), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'observations.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Segment Table///////////////// + var selectedRowsDataS = []; + document.querySelectorAll('#S-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataS.push(rowData); + }); + if (selectedRowsDataS.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-S-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataS), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'segments.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Segment Data Table///////////////// + var selectedRowsDataSD = []; + document.querySelectorAll('#SD-table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataSD.push(rowData); + }); + if (selectedRowsDataSD.length > 0) { + // Send the selected rows data to the Flask backend + fetch('/export-SD-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataSD), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'segment_data.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } + //////////////// Collect all selected rows from the Machine Data Table -- Header Table///////////////// + var selectedRowsDataHeader = []; + document.querySelectorAll('#data-header-table table tbody tr').forEach(function(row) { + var rowData = []; + row.querySelectorAll('td').forEach(function(td, index) { + // Exclude the first column with the button + if (index !== 0) { + rowData.push(td.textContent.trim()); + } + }); + selectedRowsDataHeader.push(rowData); + }); + console.log("Header csv"); + console.log(selectedRowsDataHeader); + // Send the selected rows data to the Flask backend + if (selectedRowsDataHeader.length > 0) { + fetch('/export-Header-to-csv', { + method: 'POST', + body: JSON.stringify(selectedRowsDataHeader), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.blob()) // Get the file blob from the response + .then(blob => { + // Create a link element and click it to download the file + var url = window.URL.createObjectURL(blob); + var a = document.createElement('a'); + a.href = url; + a.download = 'header.csv'; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch(error => console.error('Error:', error)); + } } @@ -816,6 +1095,26 @@ function resetMachineDataTable() { const table = document.getElementById('MD-table'); table.querySelector('tbody').innerHTML = "" + document.getElementById("click_all").checked = false; + table.querySelectorAll('select').forEach(function(select) { + select.value = "no"; + }); + } + + + function resetMachineDataTable2() { + const tableME = document.getElementById('ME-table'); + tableME.innerHTML = ""; + const tableS = document.getElementById('S-table'); + tableS.innerHTML = ""; + const tableSD = document.getElementById('SD-table'); + tableSD.innerHTML = ""; + fetch('/reset-machine-data-table', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) } @@ -826,6 +1125,7 @@ var value = r.innerHTML; r.style.backgroundColor = "#5ea9e2"; const type = r.querySelectorAll("td")[2].innerHTML; // type: M, E, S, SD + currentRowType = type; // Store the type in the higher scope variable var numColumns = r.querySelectorAll("td").length; const table = document.getElementById('MD-table'); @@ -849,7 +1149,7 @@ }); for (let i = 1; i <= numColumns - 4; i++) { let th = tr.appendChild(document.createElement('th')); - th.classList.add("uk-table-shrink"); + th.classList.add("uk-width-small"); th.innerHTML = "F_" + i; } } else if (type == "S") { @@ -862,10 +1162,14 @@ }); for (let i = 1; i <= numColumns - 4; i++) { let th = tr.appendChild(document.createElement('th')); - th.classList.add("uk-table-shrink"); + th.classList.add("uk-width-small"); th.innerHTML = "F_" + i; } } + document.getElementById("click_all").checked = false; + table.querySelectorAll('select').forEach(function(select) { + select.value = "no"; + }); fetch('/get-MD-info', { method: 'POST', @@ -876,12 +1180,15 @@ }) .then(response => response.json()) .then(data => { - // Set all the select elements to the default value - var selectTime = document.getElementById('table_select_time'); - selectTime.value = "no"; + // Set the select element to the default value var selectObject = document.getElementById('table_object'); selectObject.value = "no"; + // Find the option with the value "no" and update its text content + var selectOption = document.querySelector('#table_object option[value="no"]'); + if (selectOption) { + selectOption.textContent = "Select a object..."; + } ////////////////////////////////////////////////////////////////////////// if (type == "M" || type == "E") { // Update the time select with the new time columns @@ -891,6 +1198,7 @@ init.value = "no"; init.textContent = "Select a column..."; selectTime.appendChild(init); + selectTime.value = "no"; data['time'].forEach(label_value => { const optionElement = document.createElement('option'); optionElement.value = label_value; @@ -907,7 +1215,100 @@ selectObject.appendChild(optionElement); }); } else if (type == "S") { + // Update the time select with the new time columns + const selectStartTime = document.getElementById('starttime-select'); + const selectEndTime = document.getElementById('endtime-select'); + selectStartTime.innerHTML = ''; // Clear existing options + selectEndTime.innerHTML = ''; // Clear existing options + + // Create initial "Select a column..." option for startTime + let initStartTime = document.createElement('option'); + initStartTime.value = "no"; + initStartTime.textContent = "Select a column..."; + selectStartTime.appendChild(initStartTime); + + // Create initial "Select a column..." option for endTime + let initEndTime = document.createElement('option'); + initEndTime.value = "no"; + initEndTime.textContent = "Select a column..."; + selectEndTime.appendChild(initEndTime); + + // Ensure both selects are set to the "no" value + selectStartTime.value = "no"; + selectEndTime.value = "no"; + + data['time'].forEach(label_value => { + // Create option element for selectStartTime + let optionElementStart = document.createElement('option'); + optionElementStart.value = label_value; + optionElementStart.textContent = label_value; + selectStartTime.appendChild(optionElementStart); + + // Create option element for selectEndTime + let optionElementEnd = document.createElement('option'); + optionElementEnd.value = label_value; + optionElementEnd.textContent = label_value; + selectEndTime.appendChild(optionElementEnd); + }); + // Update the object select with the new object columns + const selectObject = document.getElementById('table_select_object'); + selectObject.innerHTML = ''; // Clear existing options + data['object'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectObject.appendChild(optionElement); + }); + // Update the index select with the new index columns + const selectIndex = document.getElementById('index-select'); + selectIndex.innerHTML = ''; // Clear existing options + let initIndex = document.createElement('option'); + initIndex.value = "no"; + initIndex.textContent = "Select a column..."; + selectIndex.appendChild(initIndex); + data['index'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectIndex.appendChild(optionElement); + }); } else if (type == "SD") { + // Update the index select with the new time columns + const selectIndex = document.getElementById('index-select'); + selectIndex.innerHTML = ''; // Clear existing options + const initIndex = document.createElement('option'); + initIndex.value = "no"; + initIndex.textContent = "Select a column..."; + selectIndex.appendChild(initIndex); + + data['index'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectIndex.appendChild(optionElement); + }); + // Update the object select with the new object columns + const selectSegment = document.getElementById('segment-label-select'); + selectSegment.innerHTML = ''; // Clear existing options + const initSegment = document.createElement('option'); + initSegment.value = "no"; + initSegment.textContent = "Select a segment..."; + selectSegment.appendChild(initSegment); + data['segment'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectSegment.appendChild(optionElement); + }); + // Update the object select with the new object columns + const selectObject = document.getElementById('table_select_object'); + selectObject.innerHTML = ''; // Clear existing options + data['object'].forEach(label_value => { + const optionElement = document.createElement('option'); + optionElement.value = label_value; + optionElement.textContent = label_value; + selectObject.appendChild(optionElement); + }); } }) .catch(error => { @@ -916,26 +1317,269 @@ }); } - + ///////////////////////////////////////OBSERVATION HEADER ROW/////////////////////////////////// // Select only the specified select elements - const time_object_selects = document.querySelectorAll('.time-object-select'); + const time_object_selects = document.querySelectorAll('.observation-select'); - function checkSelects() { - for (let select of time_object_selects) { - if (select.value === 'no') { - return false; // If any watched select is not chosen, return false + function checkSelects(selects) { + for (let select of selects) { + console.log(select.value); + if (select.value == 'no') { + return false; // If any watched select is not chosen, return false + } } - } - return true; // If all watched selects have a value, return true + return true; // If all watched selects have a value, return true } time_object_selects.forEach(select => { - select.addEventListener('change', () => { - if (checkSelects()) { + select.addEventListener('change', () => { + if (checkSelects(time_object_selects)) { + console.log("get-ME-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var time_column = document.getElementById('table_select_time').value; + var object_column = document.getElementById('table_object').value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(time_column); + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_datetime' in data && 'max_datetime' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("ME: initialize slider with min and max datetime from the server"); + initializeSlider(data['min_datetime'], data['max_datetime']); + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + //////////////////////////////////////////////SEGMENT HEADER ROW////////////////////////////////////////// + const segment_selects = document.querySelectorAll('.segment-select'); + + segment_selects.forEach(select => { + select.addEventListener('change', () => { + if (checkSelects(segment_selects)) { + console.log("get-S-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var index_column = $('index-select').value; + var starttime_column = $('starttime-select').value; + var endtime_column = $('endtime-select').value; + var object_column = $("table_object").value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(index_column); + console.log(starttime_column); + console.log(endtime_column); + + fetch('/get-S-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'starttime_column': starttime_column, 'endtime_column': endtime_column}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_datetime' in data && 'max_datetime' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("S: initialize slider with min and max datetime from the server"); + initializeSlider(data['min_datetime'], data['max_datetime']); + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + //////////////////////////////////////////////SEGMENT DATA HEADER ROW////////////////////////////////////////// + const segment_data_selects = document.querySelectorAll('.segment-data-select'); + + segment_data_selects.forEach(select => { + select.addEventListener('change', () => { + if (checkSelects(segment_data_selects)) { + console.log("get-SD-table"); + var $ = function(id) { return document.getElementById(id); }; + // All specified selects have a value, call the fetch function + var index_column = $('index-select').value; + var segment_column = $('segment-label-select').value; + var object_column = $("table_object").value; + + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + console.log(optgroupLabel); + console.log(object_column); + console.log(index_column); + console.log(segment_column); + + fetch('/get-SD-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'segment_column': segment_column}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + + if ('min_index' in data && 'max_index' in data) { + // Initialize the slider with the min and max datetime from the server + console.log("SD: initialize slider with min and max datetime from the server"); + document.getElementById('fromInput').value = min_index; + document.getElementById('toInput').value = max_index; + } + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } + }); + }); + + + function filter_ME_data_table() { + var $ = function(id) { return document.getElementById(id); }; + // Get min and max datetime from the slider + var minDatetime = $("amount1").value; + var maxDatetime = $("amount2").value; + + // All specified selects have a value, call the fetch function + console.log(currentRowType); + var isInfoComplete = checkBottomTableInfo(); + if (!isInfoComplete) { + // Information is not complete, warn the user + alert('Please complete all the required fields in the Machine Data Table before submitting.'); + } else { + var object_column = document.getElementById('table_object').value; + var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option + var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup + if (optgroupLabel == "self-defined object") { + optgroupLabel = "self"; + } else if (optgroupLabel == "object column") { + optgroupLabel = "col"; + } + + if (currentRowType == "M" || currentRowType == "E"){ + var time_column = document.getElementById('table_select_time').value; + + fetch('/get-ME-table', { + method: 'POST', + body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } else if (currentRowType == "S") { + var index_column = document.getElementById('index-select').value; + var starttime_column = document.getElementById('starttime-select').value; + var endtime_column = document.getElementById('endtime-select').value; + + fetch('/get-S-table', { + method: 'POST', + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'index_column': index_column, 'starttime_column': starttime_column, 'endtime_column': endtime_column, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log(data['table_HTML']); + const table = document.getElementById('MD-table'); + table.querySelector('tbody').innerHTML = "" + table.querySelector('tbody').innerHTML = data['table_HTML']; + }) + .catch(error => { + // Handle any error that occurred during the fetch + console.error('Error:', error); + }); + } else if (currentRowType == "SD") { + alert('This feature is not available for the Segment Data type.'); + } + } + } + + + function filter_SD_data_table() { + var index_from = document.getElementById('fromInput').value; + var index_to = document.getElementById('toInput').value; + if (currentRowType != "SD") { + alert('This feature is only available for the Segment Data type.'); + return; + } else if (index_from == "" || index_to == "") { + alert('Please fill in the index range.'); + return; + } else if (parseInt(index_to) < parseInt(index_from)) { + index_to = index_from; + } + + if (checkSelects(segment_data_selects)) { + console.log("get-SD-table"); var $ = function(id) { return document.getElementById(id); }; // All specified selects have a value, call the fetch function - var time_column = document.getElementById('table_select_time').value; - var object_column = document.getElementById('table_object').value; + var index_column = $('index-select').value; + var segment_column = $('segment-label-select').value; + var object_column = $("table_object").value; var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup @@ -944,13 +1588,10 @@ } else if (optgroupLabel == "object column") { optgroupLabel = "col"; } - console.log(optgroupLabel); - console.log(object_column); - console.log(time_column); - fetch('/get-ME-table', { + fetch('/get-SD-table', { method: 'POST', - body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel }), + body: JSON.stringify({ 'object_column': object_column, 'optgroupLabel': optgroupLabel , 'index_column': index_column, 'segment_column': segment_column, 'index_from': index_from, 'index_to': index_to}), headers: { 'Content-Type': 'application/json' } @@ -961,70 +1602,47 @@ const table = document.getElementById('MD-table'); table.querySelector('tbody').innerHTML = "" table.querySelector('tbody').innerHTML = data['table_HTML']; - - if ('min_datetime' in data && 'max_datetime' in data) { - // Initialize the slider with the min and max datetime from the server - console.log("initialize slider with min and max datetime from the server"); - initializeSlider(data['min_datetime'], data['max_datetime']); - } }) .catch(error => { // Handle any error that occurred during the fetch console.error('Error:', error); }); + } else { + alert('Please complete all the required fields in the Machine Data Table before submitting.'); } - }); - }); - - function filter_ME_data_table() { - var $ = function(id) { return document.getElementById(id); }; - // Get min and max datetime from the slider - var minDatetime = $("amount1").value; - var maxDatetime = $("amount2").value; - - // All specified selects have a value, call the fetch function - var time_column = document.getElementById('table_select_time').value; - var object_column = document.getElementById('table_object').value; - - var selectedOption = $("table_object").options[$("table_object").selectedIndex]; // Get the selected option - var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup - if (optgroupLabel == "self-defined object") { - optgroupLabel = "self"; - } else if (optgroupLabel == "object column") { - optgroupLabel = "col"; - } - console.log(optgroupLabel); - console.log(object_column); - console.log(time_column); - - fetch('/get-ME-table', { - method: 'POST', - body: JSON.stringify({ 'time_column': time_column, 'object_column': object_column, 'optgroupLabel': optgroupLabel, 'minDatetime': minDatetime, 'maxDatetime': maxDatetime}), - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => response.json()) - .then(data => { - console.log(data['table_HTML']); - const table = document.getElementById('MD-table'); - table.querySelector('tbody').innerHTML = "" - table.querySelector('tbody').innerHTML = data['table_HTML']; - }) - .catch(error => { - // Handle any error that occurred during the fetch - console.error('Error:', error); - }); } function selfdefinedObject() { var object = document.getElementById('defined-object').value; var select = document.getElementById('defined_object_values'); - var opt = document.createElement('option'); - opt.value = object; - opt.innerHTML = object; - select.appendChild(opt); + // Check if the option already exists + var optionExists = Array.from(select.getElementsByTagName('option')).some(option => option.value === object); + console.log("optionExists"); + console.log(optionExists); + if (object == "" || object == null || object == " ") { + document.getElementById("selfdefinedObject").textContent = "This field is required!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + }else if (!optionExists) { + var opt = document.createElement('option'); + opt.value = object; + opt.innerHTML = object; + select.appendChild(opt); + document.getElementById("selfdefinedObject").textContent = "Submit successfully!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + } else { + document.getElementById("selfdefinedObject").textContent = "This object already exists!"; + setTimeout(function() { + document.getElementById("selfdefinedObject").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + } + // Clear the input field + document.getElementById('defined-object').value = ""; + } @@ -1036,10 +1654,9 @@ // var divv = document.getElementById("data-header-table"); // divv.getElementsByTagName("table")[0].deleteRow(i); - var value = r.parentNode.parentNode.innerHTML - - console.log(value); - console.log("YYYYY"); + var value = r.parentNode.parentNode.innerHTML + console.log("heyheyyouyou"); + fetch('/delete-data-header', { method: 'POST', body: JSON.stringify({ 'value': value }), @@ -1047,7 +1664,12 @@ 'Content-Type': 'application/json' } }) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) .then(data => { const table = document.getElementById('data-header-table'); table.innerHTML = data['data_header_table']; @@ -1060,6 +1682,14 @@ } + function deleteRow2(event, r) { + event.stopPropagation(); // Stop the event from bubbling up to the <tr> + var i = r.parentNode.parentNode.rowIndex; + var table = r.closest("table"); // Find the closest table ancestor + table.deleteRow(i); + } + + function resetDataHeaderTable() { fetch('/reset-data-header-table', { method: 'POST', @@ -1101,14 +1731,13 @@ function submitDataHeader() { - var $ = function(id) { return document.getElementById(id); }; var isValid = true; var radioSelect = document.querySelector('input[name="radio1"]:checked'); - var label_column = $("select_column").value; - var label = $("label_values").value; - var selectedOption = $("label_values").options[$("label_values").selectedIndex]; // Get the selected option + var label_column = document.getElementById("select_column").value; + var label = document.getElementById("label_values").value; + var selectedOption = document.getElementById("label_values").options[document.getElementById("label_values").selectedIndex]; // Get the selected option var optgroupLabel = selectedOption.parentNode.label; // Get the label of the optgroup if (optgroupLabel == "label column data") { optgroupLabel = "col"; @@ -1117,84 +1746,84 @@ } var label_list = ' [" ' + optgroupLabel + ' "," ' + label_column + ' "," ' + label + ' "] '; - var object_column = $("select_column_object").value; + var object_column = document.getElementById("select_column_object").value; - var tmp = $("select_features").selectedOptions; + var tmp = document.getElementById("select_features").selectedOptions; var features_list = Array.from(tmp).map(({ value }) => value); if (radioSelect.value == 'measurement') { if (label == 'Label select...' || label == null || label == "") { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'event') { if (label == 'Label select...' || label == null || label == "") { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'segment') { if (label == 'Label select...' || label == null || label == '') { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); return; } } else if (radioSelect.value == 'segmentData') { if (label == 'Label select...' || label == null || label == '') { - $("label_values").nextElementSibling.textContent = "This field is required."; + document.getElementById("label_values").nextElementSibling.textContent = "This field is required."; // alert("Please select a label"); console.log("Label is required"); if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; - // alert("Please select a feature"); - console.log("Feature is required"); - } + document.getElementById("span").nextElementSibling.textContent = "This field is required."; + // alert("Please select a feature"); + console.log("Feature is required"); + } return; } if (features_list.length == 0 || features_list == null || features_list == []) { - $("span").nextElementSibling.textContent = "This field is required."; + document.getElementById("span").nextElementSibling.textContent = "This field is required."; // alert("Please select a feature"); console.log("Feature is required"); return; } } else if (radioSelect.value == 'object') { if (object_column == 'Select a table first...' || object_column == null || object_column == '') { - $("select_column_object").nextElementSibling.textContent = "This field is required."; + document.getElementById("select_column_object").nextElementSibling.textContent = "This field is required."; // alert("Please select an object column"); console.log("Object column is required"); return; } } - $("label_values").nextElementSibling.textContent = "*"; - $("span").nextElementSibling.textContent = "*"; - $("select_column_object").nextElementSibling.textContent = "*"; + document.getElementById("label_values").nextElementSibling.textContent = "*"; + document.getElementById("span").nextElementSibling.textContent = "*"; + document.getElementById("select_column_object").nextElementSibling.textContent = "*"; @@ -1208,10 +1837,20 @@ }) .then(response => response.json()) .then(data => { - if (radioSelect.value != 'object') { - const table = document.getElementById('data-header-table'); - table.innerHTML = data['data_header_table']; - } + const table = document.getElementById('data-header-table'); + table.innerHTML = data['data_header_table']; + + // Set the select elements to their default options + document.getElementById("label_values").selectedIndex = 0; + // Reset the Chosen multi-select + $("#select_features").val([]).trigger("chosen:updated"); + document.getElementById("select_column_object").selectedIndex = 0; + + // Show the success message + document.getElementById("submitDataHeader").textContent = "Submit successfully!"; + setTimeout(function() { + document.getElementById("submitDataHeader").textContent = ""; + }, 2500); // 5000 milliseconds = 5 seconds }) .catch(error => { // Handle any error that occurred during the fetch @@ -1224,6 +1863,14 @@ function addLabel() { const labelValue = document.getElementById('defined-label').value; // Get the value of the input + if (labelValue == "" || labelValue == null || labelValue == " ") { + document.getElementById("addLabel").textContent = "This field is required!"; + setTimeout(function() { + document.getElementById("addLabel").textContent = ""; + }, 2000); // 5000 milliseconds = 5 seconds + return; + } + // Send the value to the server with fetch API fetch('/add-self-defined-label', { method: 'POST', @@ -1242,8 +1889,13 @@ optionElement.textContent = label_value; selectElement1.appendChild(optionElement); // Append to selectElement1 }); - // Handle response data from the server - console.log(data); + + // Show the success message + document.getElementById("addLabel").textContent = "Add successfully!"; + setTimeout(function() { + document.getElementById("addLabel").textContent = ""; + }, 2500); // 5000 milliseconds = 5 seconds + }) .catch(error => { // Handle any error that occurred during the fetch @@ -1362,6 +2014,16 @@ // Function to handle the click on a dropped item function handleDroppedItemClick(itemName) { + // Change the color of the selected item + document.querySelectorAll('.list-group-item-action').forEach(item => { + if (item.textContent === itemName) { + item.style.backgroundColor = '#5ea9e2'; + } else { + item.style.backgroundColor = 'white'; + } + }); + document.getElementById('select_label_values').innerHTML = ''; + // Send a POST request to Flask backend with the item name fetch('/get-table-data', { method: 'POST', @@ -1380,7 +2042,7 @@ selectElement0.innerHTML = ''; const optionElement0 = document.createElement('option'); optionElement0.value = ''; - optionElement0.textContent = 'Column select...'; + optionElement0.textContent = 'Select column...'; selectElement0.appendChild(optionElement0); // Add the table columns as options data.table_columns.forEach(column => { @@ -1391,18 +2053,18 @@ }); // Assign data.table_columns to the select element - const selectElement = document.getElementById('select_column'); - selectElement.innerHTML = ''; + const selectColumn = document.getElementById('select_column'); + selectColumn.innerHTML = ''; const optionElement = document.createElement('option'); optionElement.value = ''; - optionElement.textContent = 'Column select...'; - selectElement.appendChild(optionElement); + optionElement.textContent = 'Select column...'; + selectColumn.appendChild(optionElement); // Add the table columns as options data.table_columns.forEach(column => { const optionElement = document.createElement('option'); optionElement.value = column; optionElement.textContent = column; - selectElement.appendChild(optionElement); + selectColumn.appendChild(optionElement); }); // Assign data.table_columns to the multi-select element @@ -1419,8 +2081,6 @@ // Trigger Chosen to update its view $(".chosen-select").trigger("chosen:updated"); - - // toggleTerminal(); // Show the terminal }) .catch(error => console.error('Error:', error)); } @@ -1495,6 +2155,7 @@ } } + document.getElementById('resetButton').addEventListener('click', function() { // event.preventDefault(); // Prevent the default form submission console.log('Reset button clicked'); @@ -1512,7 +2173,7 @@ for (let i = 0; i < data.tables.length; i++) { var item1 = document.createElement('div'); item1.className = "list-group-item-action uk-card uk-card-default uk-card-body uk-card-small"; - item1.style = "height: 15px; padding-top: 5px;"; + item1.style = "height: 15px; padding-top: 5px; width: 230px;"; item1.textContent = data.tables[i]; var item2 = document.createElement('div'); item2.className = "uk-margin"; @@ -1569,6 +2230,9 @@ document.addEventListener('DOMContentLoaded', function() { + if ('{{message}}') { + UIkit.notification('{{message}}', {status: 'success'}); + } // Show the ERD canvas showErdCanvas("erdCanvas1", "zoomInBtn1", "zoomOutBtn1", '{{ image1 }}'); showErdCanvas("erdCanvas2", "zoomInBtn2", "zoomOutBtn2", '{{ image2 }}'); @@ -1692,20 +2356,47 @@ }); + document.addEventListener('DOMContentLoaded', function() { + // Get the select elements + var labelColumn = document.getElementById('select_column'); + var labelValuesSelect = document.getElementById('label_values'); + var featureSelect = document.getElementById('select_features'); // Assume you have a select element with this ID for features + + // Listen for changes on the label select + labelValuesSelect.addEventListener('change', function() { + // Check if the selected option is from 'label column data' + var selectedOption = this.options[this.selectedIndex]; + var isLabelColumnData = selectedOption.parentNode.id === 'select_label_values'; + + // Apply constraints to the feature select based on the above condition + if(isLabelColumnData) { + const droppedItemsDiv = document.querySelector('#dropped_items'); // Get the outer div + + fetch('/constraint-feature-columns', { + method: 'POST', + body: JSON.stringify({ 'label_column': labelColumn.value, 'label': selectedOption.value}), + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data['feature_columns']) { + // Update the feature select element + featureSelect.innerHTML = ''; // Clear existing options + data.feature_columns.forEach(column => { + const optionElement = document.createElement('option'); + optionElement.value = column; + optionElement.textContent = column; + featureSelect.appendChild(optionElement); + }); + // Trigger Chosen to update its view + $(".chosen-select").trigger("chosen:updated"); + } + }) + } + }); + }); </script> </body> -</html> - - -<!-- For the pop up windows, I used the following code: --> - <!-- <div id="dropped_items"> - <span onclick="UIkit.modal('#my-modal').show()">Click me</span> - </div> - - <div id="my-modal" uk-modal> - <div class="uk-modal-dialog uk-modal-body"> - <h2 class="uk-modal-title">Terminal</h2> - <p>Here is the content of your "terminal".</p> - <button class="uk-modal-close" type="button">Close</button> - </div> - </div> --> \ No newline at end of file +</html> \ No newline at end of file