From 44b18e131c704fa7eac4dda6d9d93e96fbd483f7 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 15 Nov 2024 20:13:03 +1100 Subject: [PATCH] GraphQl 0.124 - Running server check --- front/js/common.js | 62 ++++++++++++++----- server/__main__.py | 8 ++- server/graphql_server/graphql_server_start.py | 20 ++++-- server/helper.py | 17 +++-- server/initialise.py | 7 ++- 5 files changed, 83 insertions(+), 31 deletions(-) diff --git a/front/js/common.js b/front/js/common.js index 3c787cc5..c967ab9a 100755 --- a/front/js/common.js +++ b/front/js/common.js @@ -1260,39 +1260,70 @@ async function handleFirstLoad(callback) { } // ----------------------------------------------------------------------------- -// Execute callback once app initialized -function callAfterAppInitialized(callback) { - if (!isAppInitialized()) { +// Execute callback once the app is initialized and GraphQL server is running +async function callAfterAppInitialized(callback) { + if (!isAppInitialized() || !(await isGraphQLServerRunning())) { setTimeout(() => { - callAfterAppInitialized(callback) + callAfterAppInitialized(callback); }, 500); - } else - { + } else { callback(); } } +// ----------------------------------------------------------------------------- +// Polling function to repeatedly check if the server is running +async function waitForGraphQLServer() { + const pollInterval = 2000; // 2 seconds between each check + let serverRunning = false; + + while (!serverRunning) { + serverRunning = await isGraphQLServerRunning(); + if (!serverRunning) { + console.log("GraphQL server not running, retrying in 2 seconds..."); + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + } + + console.log("GraphQL server is now running."); +} + +// ----------------------------------------------------------------------------- +// Returns 1 if running, 0 otherwise +async function isGraphQLServerRunning() { + try { + const response = await $.get('api/app_state.json?nocache=' + Date.now()); + console.log("graphQLServerStarted: " + response["graphQLServerStarted"]); + setCache("graphQLServerStarted", response["graphQLServerStarted"]); + return response["graphQLServerStarted"]; + } catch (error) { + console.error("Failed to check GraphQL server status:", error); + return false; + } +} + // ----------------------------------------------------------------------------- // Check if the code has been executed before by checking sessionStorage function isAppInitialized() { - // return arraysContainSameValues(getCache("completedCalls").split(',').filter(Boolean), completedCalls_final); - - // loading settings + 1 (or 2 language files if not english) + device cache. - completedCallsCount_final = getLangCode() == 'en_us' ? 3 : 4 ; - - return (parseInt(getCache("completedCallsCount")) >= completedCallsCount_final); + const completedCallsCount_final = getLangCode() == 'en_us' ? 3 : 4; + return ( + parseInt(getCache("completedCallsCount")) >= completedCallsCount_final + ); } -// Define a function that will execute the code only once +// ----------------------------------------------------------------------------- +// Main execution logic async function executeOnce() { showSpinner(); if (!isAppInitialized()) { try { + await waitForGraphQLServer(); // Wait for the server to start + await cacheDevices(); await cacheSettings(); - await cacheStrings(); - + await cacheStrings(); + console.log("✅ All AJAX callbacks have completed"); onAllCallsComplete(); } catch (error) { @@ -1301,6 +1332,7 @@ async function executeOnce() { } } + // ----------------------------------------------------------------------------- // Function to handle successful completion of an AJAX call const handleSuccess = (callName) => { diff --git a/server/__main__.py b/server/__main__.py index ec145397..5a37c8e0 100755 --- a/server/__main__.py +++ b/server/__main__.py @@ -66,6 +66,9 @@ def main (): # check file permissions and fix if required filePermissions() + # Header + init app state + updateState("Initializing", None, None, None, 0) + # Open DB once and keep open # Opening / closing DB frequently actually casues more issues db = DB() # instance of class DB @@ -81,8 +84,7 @@ def main (): mylog('debug', '[MAIN] Starting loop') - # Header + init app state - updateState("Initializing") + all_plugins = None @@ -106,7 +108,7 @@ def main (): # Update API endpoints update_api(db, all_plugins) - + # proceed if 1 minute passed if conf.last_scan_run + datetime.timedelta(minutes=1) < conf.loop_start_time : diff --git a/server/graphql_server/graphql_server_start.py b/server/graphql_server/graphql_server_start.py index 4174a0d5..7efc59fa 100755 --- a/server/graphql_server/graphql_server_start.py +++ b/server/graphql_server/graphql_server_start.py @@ -12,7 +12,7 @@ INSTALL_PATH="/app" sys.path.extend([f"{INSTALL_PATH}/server"]) from logger import mylog -from helper import get_setting_value, timeNowTZ +from helper import get_setting_value, timeNowTZ, updateState from notification import write_notification app = Flask(__name__) @@ -26,7 +26,7 @@ def graphql_endpoint(): token = request.headers.get("Authorization") if token != f"Bearer {API_TOKEN}": mylog('verbose', [f'[graphql_server] Unauthorized access attempt']) - + return jsonify({"error": "Unauthorized"}), 401 data = request.get_json() @@ -42,8 +42,16 @@ def graphql_endpoint(): def start_server(): """Function to start the GraphQL server in a background thread.""" mylog('verbose', [f'[graphql_server] Starting on port: {GRAPHQL_PORT}']) - - # Start the Flask app in a separate thread - thread = threading.Thread(target=lambda: app.run(host="0.0.0.0", port=GRAPHQL_PORT, debug=True, use_reloader=False)) - thread.start() + + state = updateState("GraphQL: Starting", None, None, None, None) + + if state.graphQLServerStarted == 0: + + # Start the Flask app in a separate thread + thread = threading.Thread(target=lambda: app.run(host="0.0.0.0", port=GRAPHQL_PORT, debug=True, use_reloader=False)) + thread.start() + + # updateState(newState, settingsSaved = None (timestamp), settingsImported = None (timestamp), showSpinner = False (1/0), graphQLServerStarted = False (1/0)) + # update GraphQL = started + state = updateState("Process: Wait", None, None, None, 1) diff --git a/server/helper.py b/server/helper.py index 8a681558..086587ef 100755 --- a/server/helper.py +++ b/server/helper.py @@ -57,8 +57,9 @@ def get_timezone_offset(): # App state #------------------------------------------------------------------------------- # A class to manage the application state and to provide a frontend accessible API point +# To keep an existing value pass None class app_state_class: - def __init__(self, currentState, settingsSaved=None, settingsImported=None, showSpinner=False): + def __init__(self, currentState, settingsSaved=None, settingsImported=None, showSpinner=False, graphQLServerStarted=0): # json file containing the state to communicate with the frontend stateFile = apiPath + '/app_state.json' previousState = "" @@ -78,19 +79,21 @@ class app_state_class: mylog('none', [f'[app_state_class] Failed to handle app_state.json: {e}']) - # Check if the file exists and init values + # Check if the file exists and recover previous values if previousState != "": self.settingsSaved = previousState.get("settingsSaved", 0) self.settingsImported = previousState.get("settingsImported", 0) self.showSpinner = previousState.get("showSpinner", False) self.isNewVersion = previousState.get("isNewVersion", False) self.isNewVersionChecked = previousState.get("isNewVersionChecked", 0) - else: + self.graphQLServerStarted = previousState.get("graphQLServerStarted", 0) + else: # init first time values self.settingsSaved = 0 self.settingsImported = 0 self.showSpinner = False self.isNewVersion = checkNewVersion() self.isNewVersionChecked = int(timeNow().timestamp()) + self.graphQLServerStarted = 0 # Overwrite with provided parameters if supplied if settingsSaved is not None: @@ -99,6 +102,8 @@ class app_state_class: self.settingsImported = settingsImported if showSpinner is not None: self.showSpinner = showSpinner + if graphQLServerStarted is not None: + self.graphQLServerStarted = graphQLServerStarted # check for new version every hour and if currently not running new version if self.isNewVersion is False and self.isNewVersionChecked + 3600 < int(timeNow().timestamp()): @@ -115,7 +120,7 @@ class app_state_class: with open(stateFile, 'w') as json_file: json_file.write(json_data) except (TypeError, ValueError) as e: - mylog('none', [f'[app_state_class] Failed to serialize object to JSON: {e}']) + mylog('none', [f'[app_state_class] Failed to serialize object to JSON: {e}']) @@ -131,9 +136,9 @@ class app_state_class: #------------------------------------------------------------------------------- # method to update the state -def updateState(newState, settingsSaved = None, settingsImported = None, showSpinner = False): +def updateState(newState, settingsSaved = None, settingsImported = None, showSpinner = False, graphQLServerStarted = None): - state = app_state_class(newState, settingsSaved, settingsImported, showSpinner) + return app_state_class(newState, settingsSaved, settingsImported, showSpinner, graphQLServerStarted) #------------------------------------------------------------------------------- diff --git a/server/initialise.py b/server/initialise.py index 53e01143..83367537 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -374,7 +374,12 @@ def importConfigs (db, all_plugins): # Used to determine the next import conf.lastImportedConfFile = os.path.getmtime(config_file) - updateState("Config imported", conf.lastImportedConfFile, conf.lastImportedConfFile, False) + # updateState(newState (text), + # settingsSaved = None (timestamp), + # settingsImported = None (timestamp), + # showSpinner = False (1/0), + # graphQLServerStarted = 1 (1/0)) + updateState("Config imported", conf.lastImportedConfFile, conf.lastImportedConfFile, False, 1) msg = '[Config] Imported new settings config' mylog('minimal', msg)