diff --git a/javascript/clusterize.js b/javascript/clusterize.js index 9f378a4b7..5f85b9da0 100644 --- a/javascript/clusterize.js +++ b/javascript/clusterize.js @@ -10,9 +10,9 @@ // Many operations can be lenghty. Try to limit their frequency by debouncing. const SCROLL_DEBOUNCE_TIME_MS = 50; -const RESIZE_OBSERVER_DEBOUNCE_TIME_MS = 50; // should be <= refresh debounce time +const RESIZE_OBSERVER_DEBOUNCE_TIME_MS = 100; // should be <= refresh debounce time const ELEMENT_OBSERVER_DEBOUNCE_TIME_MS = 100; -const REFRESH_DEBOUNCE_TIME_MS = 50; +const REFRESH_DEBOUNCE_TIME_MS = 100; class Clusterize { scroll_elem = null; @@ -125,12 +125,12 @@ class Clusterize { this.setup_has_run = true; } - clear(loading) { + clear(custom_text) { if (!this.setup_has_run || !this.enabled) { return; } - this.#html(this.#generateEmptyRow(loading).join("")); + this.#html(this.#generateEmptyRow(custom_text).join("")); } destroy() { @@ -226,7 +226,11 @@ class Clusterize { if (!this.enabled) { return; } - return await this.options.callbacks.fetchData(idx_start, idx_end); + try { + return await this.options.callbacks.fetchData(idx_start, idx_end); + } catch (error) { + throw error; + } } sortDataDefaultCallback() { @@ -335,7 +339,7 @@ class Clusterize { this.#max_rows = Math.ceil(this.#max_items / this.options.cols_in_block, 10); - return prev_options === JSON.stringify(this.options); + return prev_options !== JSON.stringify(this.options); } #getClusterNum() { @@ -346,26 +350,26 @@ class Clusterize { return Math.min(current_cluster, max_cluster); } - #generateEmptyRow(loading) { - // If loading==true, then we use the loading text for our element. Defaults to false. - loading = loading === true; - if (!loading && (!this.options.tag || !this.options.show_no_data_row)) { - return []; + #generateEmptyRow(text, class_name) { + if (isNullOrUndefined(text)) { + text = this.options.no_data_text; } - const text = loading ? this.options.loading_data_text : this.options.no_data_text; + if (isNullOrUndefined(class_name)) { + class_name = this.options.no_data_class; + } const empty_row = document.createElement(this.options.tag); - const no_data_content = document.createTextNode(text); - empty_row.className = this.options.no_data_class; + const content = document.createTextNode(text); + empty_row.className = class_name; if (this.options.tag === "tr") { const td = document.createElement("td"); // fixes #53 td.colSpan = 100; - td.appendChild(no_data_content); + td.appendChild(content); empty_row.appendChild(td); } else { - empty_row.appendChild(no_data_content); + empty_row.appendChild(content); } return [empty_row.outerHTML]; } @@ -380,7 +384,11 @@ class Clusterize { const idx_start = Math.max(0, rows_start * this.options.cols_in_block); const idx_end = Math.min(this.#max_items, rows_end * this.options.cols_in_block); - const this_cluster_rows = await this.fetchData(idx_start, idx_end); + let this_cluster_rows = await this.fetchData(idx_start, idx_end); + if (!Array.isArray(this_cluster_rows) || !this_cluster_rows.length) { + console.error(`Failed to fetch data for idx range (${idx_start},${idx_end})`); + this_cluster_rows = []; + } if (this_cluster_rows.length < this.options.rows_in_block) { return { @@ -402,7 +410,14 @@ class Clusterize { async #insertToDOM() { if (!this.options.cluster_height || !this.options.cluster_width) { const rows = await this.fetchData(0, 1); - this.#exploreEnvironment(rows, this.#cache); + if (!Array.isArray(rows) || !rows.length) { + console.error(`Failed to fetch data for idx range (0, 1)`); + this.#html(this.#generateEmptyRow().join("")); + return; + } else { + this.#exploreEnvironment(rows, this.#cache); + this.#html(this.#generateEmptyRow("Loading...").join("")); + } } const data = await this.#generate(); @@ -502,7 +517,7 @@ class Clusterize { } async #onResize() { - await this.refresh(true); + await this.refresh(); } #fixElementReferences() { diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 49678da25..684a71889 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -18,7 +18,6 @@ const SEARCH_INPUT_DEBOUNCE_TIME_MS = 250; const EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS = 10; -const EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_ATTEMPT_DELAY_MS = 1000; const EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS = 1000; const EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS = 1000; const EXTRA_NETWORKS_REFRESH_INTERNAL_DEBOUNCE_TIMEOUT_MS = 200; @@ -66,6 +65,9 @@ const _debounce = (handler, timeout_ms) => { }; class ExtraNetworksTab { + tabname; + extra_networks_tabname; + tabname_full; // {tabname}_{extra_networks_tabname} tree_list; cards_list; container_elem; @@ -327,10 +329,7 @@ class ExtraNetworksTab { } } - async waitForServerPageReady( - max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS, - delay_ms = EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_ATTEMPT_DELAY_MS, - ) { + async waitForServerPageReady(max_attempts = EXTRA_NETWORKS_GET_PAGE_READY_MAX_ATTEMPTS) { /** Waits for a page on the server to be ready. * * We need to wait for the page to be ready before we can fetch any data. @@ -339,48 +338,35 @@ class ExtraNetworksTab { * the server since the data isn't ready. This function allows us to wait for * the server to tell us that it is ready for data requests. * - * Resolves when the response from the server is {ready: true}. - * Rejects if we exceed the max number of attempts. - * * Args: - * max_attempts [int]: The max number of reuqests that will be attempted + * max_attempts [int]: The max number of requests that will be attempted * before giving up. If set to 0, will attempt forever. - * delay_ms [int]: The time between requests to the server. The server - * responds right away with its state so we need to - * slow down our request times. */ - const err_prefix = `error waiting for server page (${this.extra_networks_tabname})`; + const err_prefix = `error waiting for server page (${this.tabname_full})`; return new Promise((resolve, reject) => { let attempt = 0; const loop = () => { setTimeout(async() => { - let response; try { - response = JSON.parse( - await requestGetPromise( - "./sd_extra_networks/page-is-ready", - {extra_networks_tabname: this.extra_networks_tabname}, - EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS, - ) + await requestGetPromise( + "./sd_extra_networks/page-is-ready", + {extra_networks_tabname: this.extra_networks_tabname}, + EXTRA_NETWORKS_WAIT_FOR_PAGE_READY_TIMEOUT_MS, ); - if (response.ready === true) { - return resolve(); - } + return resolve(); } catch (error) { // If we get anything other than a timeout error, reject. - // Otherwise, fall through. - if (error !== "Request for ./sd_extra_networks/page-is-ready timed out.") { - return reject(`${err_prefix}: ${error}`); + // Otherwise, fall through to retry request. + if (error.status !== 408) { + return reject(`${err_prefix}: uncaught exception: ${JSON.stringify(error)}`); } } - // If we got here, then we got a timeout error. - // Timeout errors are acceptable until the max number of - // attempts has been reached. if (max_attempts !== 0 && attempt++ >= max_attempts) { return reject(`${err_prefix}: max attempts exceeded`); } else { - setTimeout(() => loop(), delay_ms); + // small delay since our request has a timeout. + setTimeout(() => loop(), 100); } }, 0); }; @@ -392,7 +378,7 @@ class ExtraNetworksTab { try { await this.waitForServerPageReady(); } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } @@ -400,9 +386,10 @@ class ExtraNetworksTab { const payload = {tabname: this.tabname, extra_networks_tabname: this.extra_networks_tabname}; const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS; try { - return JSON.parse(await requestGetPromise(url, payload, timeout)); + const response = await requestGetPromise(url, payload, timeout); + return response.response; } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } } @@ -411,7 +398,7 @@ class ExtraNetworksTab { try { await this.waitForServerPageReady(); } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } @@ -419,9 +406,10 @@ class ExtraNetworksTab { const payload = {tabname: this.tabname, extra_networks_tabname: this.extra_networks_tabname}; const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS; try { - return JSON.parse(await requestGetPromise(url, payload, timeout)); + const response = await requestGetPromise(url, payload, timeout); + return response.response; } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } } @@ -430,7 +418,7 @@ class ExtraNetworksTab { try { await this.waitForServerPageReady(); } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } @@ -438,9 +426,10 @@ class ExtraNetworksTab { const payload = {extra_networks_tabname: this.extra_networks_tabname, div_ids: div_ids}; const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS; try { - return JSON.parse(await requestGetPromise(url, payload, timeout)); + const response = await requestGetPromise(url, payload, timeout); + return response.response; } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } } @@ -449,7 +438,7 @@ class ExtraNetworksTab { try { await this.waitForServerPageReady(); } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } @@ -457,9 +446,10 @@ class ExtraNetworksTab { const payload = {extra_networks_tabname: this.extra_networks_tabname, div_ids: div_ids}; const timeout = EXTRA_NETWORKS_REQUEST_GET_TIMEOUT_MS; try { - return JSON.parse(await requestGetPromise(url, payload, timeout)); + const response = await requestGetPromise(url, payload, timeout); + return response.response; } catch (error) { - console.error(error); + console.error(JSON.stringify(error)); return {}; } } @@ -735,7 +725,6 @@ function extraNetworksUnrelatedTabSelected(tabname) { async function extraNetworksTabSelected(tabname_full, show_prompt, show_neg_prompt) { /** called from python when user selects an extra networks tab */ - await waitForKeyInObject({obj: extra_networks_tabs, k: tabname_full}); for (const [k, v] of Object.entries(extra_networks_tabs)) { if (k === tabname_full) { diff --git a/javascript/extraNetworksClusterize.js b/javascript/extraNetworksClusterize.js index e7ea46f78..8e73e6ef6 100644 --- a/javascript/extraNetworksClusterize.js +++ b/javascript/extraNetworksClusterize.js @@ -101,7 +101,7 @@ class ExtraNetworksClusterize extends Clusterize { if (this.lru instanceof LRUCache) { this.lru.clear(); } - super.clear(true); + super.clear("Loading..."); } async load(force_init_data) { @@ -182,10 +182,12 @@ class ExtraNetworksClusterize extends Clusterize { const data = {}; // Fetch any div IDs not in the LRU Cache using our callback. if (missing_div_ids.length !== 0) { - Object.assign( - data, - await this.options.callbacks.fetchData(missing_div_ids), - ); + const fetched_data = await this.options.callbacks.fetchData(missing_div_ids); + if (Object.keys(fetched_data).length !== missing_div_ids.length) { + // expected data. got nothing. + return {}; + } + Object.assign(data, fetched_data); } // Now load any cached IDs from the LRU Cache @@ -230,7 +232,7 @@ class ExtraNetworksClusterizeTreeList extends ExtraNetworksClusterize { clear() { this.selected_div_id = null; - super.clear(true); + super.clear("Loading..."); } getBoxShadow(depth) { diff --git a/javascript/utils.js b/javascript/utils.js index 5b24a3ad4..1fbd104de 100644 --- a/javascript/utils.js +++ b/javascript/utils.js @@ -387,6 +387,15 @@ function requestGet(url, data, handler, errorHandler) { } function requestGetPromise(url, data, timeout_ms) { + /**Asynchronous `GET` request that returns a promise. + * + * The result will be of the format {status: int, response: JSON object}. + * Thus, the xhr.responseText that we receive is expected to be a JSON string. + * Acceptable status codes for successful requests are 200 <= status < 300. + */ + if (!isNumber(timeout_ms)) { + timeout_ms = 1000; + } return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); const args = Object.entries(data).map(([k, v]) => { @@ -395,18 +404,18 @@ function requestGetPromise(url, data, timeout_ms) { xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { - return resolve(xhr.responseText); + return resolve({status: xhr.status, response: JSON.parse(xhr.responseText)}); } else { - return reject({status: xhr.status, response: xhr.responseText}); + return reject({status: xhr.status, response: JSON.parse(xhr.responseText)}); } }; xhr.onerror = () => { - return reject({status: xhr.status, response: xhr.responseText}); + return reject({status: xhr.status, response: JSON.parse(xhr.responseText)}); }; xhr.ontimeout = () => { - return reject(`Request for ${url} timed out.`); + return reject({status: 408, response: {detail: `Request timeout: ${url}`}}); }; const payload = JSON.stringify(data); diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index a7d436d48..194ad4735 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -873,17 +873,16 @@ def init_tree_data(tabname: str = "", extra_networks_tabname: str = "") -> JSONR Status Codes: 200 on success - 503 when data is not ready - 500 on any other error + 404 if data isn't ready or tabname doesn't exist. """ page = get_page_by_name(extra_networks_tabname) data = page.generate_tree_view_data(tabname) if data is None: - return JSONResponse({}, status_code=503) + raise HTTPException(status_code=404, detail=f"data not ready: {extra_networks_tabname}") - return JSONResponse(data, status_code=200) + return JSONResponse(data) def fetch_tree_data( @@ -894,6 +893,10 @@ def fetch_tree_data( Args: div_ids: A string with div_ids in CSV format. + + Status Codes: + 200 on success + 404 if tabname doesn't exist """ page = get_page_by_name(extra_networks_tabname) @@ -913,6 +916,10 @@ def fetch_cards_data( Args: div_ids: A string with div_ids in CSV format. + + Status Codes: + 200 on success + 404 if tabname doesn't exist """ page = get_page_by_name(extra_networks_tabname) @@ -931,34 +938,31 @@ def init_cards_data(tabname: str = "", extra_networks_tabname: str = "") -> JSON Status Codes: 200 on success - 503 when data is not ready - 500 on any other error + 404 if data isn't ready or tabname doesn't exist. """ page = get_page_by_name(extra_networks_tabname) data = page.generate_cards_view_data(tabname) if data is None: - return JSONResponse({}, status_code=503) + raise HTTPException(status_code=404, detail=f"data not ready: {extra_networks_tabname}") - return JSONResponse(data, status_code=200) + return JSONResponse(data) def page_is_ready(extra_networks_tabname: str = "") -> JSONResponse: """Returns whether the specified page is ready for fetching data. Status Codes: - 200 ready - 503 not ready - 500 on any other error + 200 if page is ready + 404 if page isn't ready or tabname doesnt exist. """ page = get_page_by_name(extra_networks_tabname) - try: - ready = len(page.items) == len(list(page.list_items())) - return JSONResponse({"ready": ready}, status_code=200) - except Exception as exc: - return JSONResponse({"error": str(exc)}, status_code=500) + if len(page.items) == len(list(page.list_items())): + return JSONResponse({}, status_code=200) + else: + raise HTTPException(status_code=404, detail=f"page not ready: {extra_networks_tabname}") def get_metadata(extra_networks_tabname: str = "", item: str = "") -> JSONResponse: