From b53251289749fde01ca13c28fb3b23ba62c8a4dd Mon Sep 17 00:00:00 2001 From: Phil Renaud Date: Mon, 24 Jun 2024 16:59:10 -0400 Subject: [PATCH] UI handles 502s and 504s gracefully --- ui/app/controllers/jobs/index.js | 73 +++ ui/app/routes/jobs/index.js | 2 + ui/app/styles/components/jobs-list.scss | 4 + ui/app/templates/jobs/index.hbs | 641 ++++++++++++------------ 4 files changed, 405 insertions(+), 315 deletions(-) diff --git a/ui/app/controllers/jobs/index.js b/ui/app/controllers/jobs/index.js index 733eb89ce16..1dfe511d70f 100644 --- a/ui/app/controllers/jobs/index.js +++ b/ui/app/controllers/jobs/index.js @@ -22,6 +22,7 @@ export default class JobsIndexController extends Controller { @service store; @service userSettings; @service watchList; + @service notifications; @tracked pageSize; @@ -156,12 +157,72 @@ export default class JobsIndexController extends Controller { ); } + /** + * In case the user wants to specifically stop polling for new jobs + */ + @action pauseJobFetching() { + // this.showingCachedJobs = false; + let notification = this.notifications.queue.find( + (n) => n.title === 'Error fetching jobs' + ); + if (notification) { + notification.destroyMessage(); + } + this.watchList.jobsIndexIDsController.abort(); + this.watchList.jobsIndexDetailsController.abort(); + this.watchJobIDs.cancelAll(); + this.watchJobs.cancelAll(); + } + + @action restartJobList() { + this.showingCachedJobs = false; + let notification = this.notifications.queue.find( + (n) => n.title === 'Error fetching jobs' + ); + if (notification) { + notification.destroyMessage(); + } + this.watchList.jobsIndexIDsController.abort(); + this.watchList.jobsIndexDetailsController.abort(); + this.watchJobIDs.cancelAll(); + this.watchJobs.cancelAll(); + this.watchJobIDs.perform({}, JOB_LIST_THROTTLE); + this.watchJobs.perform(this.jobIDs, JOB_DETAILS_THROTTLE); + } + @localStorageProperty('nomadLiveUpdateJobsIndex', true) liveUpdatesEnabled; // #endregion pagination //#region querying + /** + * + * Let the user know that there was difficulty fetching jobs, but don't overload their screen with notifications. + * Set showingCachedJobs to tell the template to prompt them to extend timeouts + * @param {Error} e + */ + notifyFetchError(e) { + const firstError = e.errors[0]; + console.log('firstError', firstError); + this.notifications.add({ + title: 'Error fetching jobs', + message: `The backend returned an error with status ${firstError.status} while fetching jobs`, + color: 'critical', + sticky: true, + preventDuplicates: true, + }); + // Specific check for a proxy timeout error + if ( + !this.showingCachedJobs && + (firstError.status === '502' || firstError.status === '504') + ) { + this.showingCachedJobs = true; + } + } + + @tracked showingCachedJobs = false; + jobQuery(params) { this.watchList.jobsIndexIDsController.abort(); this.watchList.jobsIndexIDsController = new AbortController(); @@ -172,9 +233,17 @@ export default class JobsIndexController extends Controller { abortController: this.watchList.jobsIndexIDsController, }, }) + .then((jobs) => { + this.showingCachedJobs = false; + return jobs; + }) .catch((e) => { if (e.name !== 'AbortError') { console.log('error fetching job ids', e); + this.notifyFetchError(e); + } + if (this.jobs.length) { + return this.jobs; } return; }); @@ -194,6 +263,10 @@ export default class JobsIndexController extends Controller { .catch((e) => { if (e.name !== 'AbortError') { console.log('error fetching job allocs', e); + this.notifyFetchError(e); + } + if (this.jobs.length) { + return this.jobs; } return; }); diff --git a/ui/app/routes/jobs/index.js b/ui/app/routes/jobs/index.js index 44640df6d37..f4f21d5ea23 100644 --- a/ui/app/routes/jobs/index.js +++ b/ui/app/routes/jobs/index.js @@ -81,8 +81,10 @@ export default class IndexRoute extends Route.extend( }); } catch (error) { try { + console.log('route jobs index catch try 1'); notifyForbidden(this)(error); } catch (secondaryError) { + console.log('route jobs index catch try 2'); return this.handleErrors(error); } } diff --git a/ui/app/styles/components/jobs-list.scss b/ui/app/styles/components/jobs-list.scss index 337aa686670..03f252cc2be 100644 --- a/ui/app/styles/components/jobs-list.scss +++ b/ui/app/styles/components/jobs-list.scss @@ -43,6 +43,10 @@ } } +#jobs-list-cache-warning { + margin-bottom: 1rem; +} + .status-cell { display: flex; gap: 0.5rem; diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 2f9b21f7ff3..2fd46d6190f 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -5,337 +5,348 @@ {{page-title "Jobs"}}
- - + {{#if this.showingCachedJobs}} + + Error fetching jobs — shown jobs are cached + Jobs shown are cached and may be out of date. Often, this is due to a short timeout in proxy configurations. Consider increasing the timeout. + {{#if this.watchJobIDs.isRunning}} + + {{/if}} + + + {{/if}} - + + - - - {{#each this.filterFacets as |group|}} - - - {{#each group.options as |option|}} - - {{option.key}} - - {{else}} - - No {{group.label}} filters - - {{/each}} - - {{/each}} + - {{#if this.system.shouldShowNamespaces}} - - - - - - {{#each this.shownNamespaces as |option|}} - - {{option.label}} - - {{/each}} - - {{/if}} + - {{#if this.filter}} - + - {{/if}} + {{#each group.options as |option|}} + + {{option.key}} + + {{else}} + + No {{group.label}} filters + + {{/each}} + + {{/each}} - + {{#if this.system.shouldShowNamespaces}} + + + + + + {{#each this.shownNamespaces as |option|}} + + {{option.label}} + + {{/each}} + + {{/if}} - {{#if this.pendingJobIDDiff}} - {{/if}} -
- -
+
-
-
+ {{#if this.pendingJobIDDiff}} + + {{/if}} - {{#if this.isForbidden}} - - {{else if this.jobs.length}} - - <:body as |B|> - {{!-- TODO: use --}} - - {{!-- {{#each this.tableColumns as |column|}} - {{get B.data (lowercase column.label)}} - {{/each}} --}} - - {{#if B.data.assumeGC}} - {{B.data.name}} - {{else}} - - {{B.data.name}} - {{!-- TODO: going to lose .meta with statuses endpoint! --}} - {{#if B.data.meta.structured.pack}} - - {{x-icon "box" class= "test"}} - Pack - - {{/if}} - + + + + + + + {{#if this.isForbidden}} + + {{else if this.jobs.length}} + + <:body as |B|> + {{!-- TODO: use --}} + + {{!-- {{#each this.tableColumns as |column|}} + {{get B.data (lowercase column.label)}} + {{/each}} --}} + + {{#if B.data.assumeGC}} + {{B.data.name}} + {{else}} + + {{B.data.name}} + {{!-- TODO: going to lose .meta with statuses endpoint! --}} + {{#if B.data.meta.structured.pack}} + + {{x-icon "box" class= "test"}} + Pack + {{/if}} - - {{#if this.system.shouldShowNamespaces}} - {{B.data.namespace.id}} + {{/if}} - -
+ + {{#if this.system.shouldShowNamespaces}} + {{B.data.namespace.id}} + {{/if}} + +
+ {{#if (not (eq B.data.childStatuses null))}} + {{#if B.data.childStatusBreakdown.running}} + + {{else if B.data.childStatusBreakdown.pending}} + + {{else if B.data.childStatusBreakdown.dead}} + + {{else if (not B.data.childStatuses.length)}} + + {{/if}} + {{else}} + + {{/if}} + {{#if B.data.hasPausedTask}} + + + + {{/if}} +
+
+ + {{B.data.type}} + + {{#if this.system.shouldShowNodepools}} + {{B.data.nodePool}} + {{/if}} + +
+ {{#unless B.data.assumeGC}} {{#if (not (eq B.data.childStatuses null))}} - {{#if B.data.childStatusBreakdown.running}} - - {{else if B.data.childStatusBreakdown.pending}} - - {{else if B.data.childStatusBreakdown.dead}} - - {{else if (not B.data.childStatuses.length)}} - + {{#if B.data.childStatuses.length}} + + {{else}} + -- {{/if}} {{else}} - - {{/if}} - {{#if B.data.hasPausedTask}} - - - + {{/if}} -
-
- - {{B.data.type}} - - {{#if this.system.shouldShowNodepools}} - {{B.data.nodePool}} - {{/if}} - -
- {{#unless B.data.assumeGC}} - {{#if (not (eq B.data.childStatuses null))}} - {{#if B.data.childStatuses.length}} - - {{else}} - -- - {{/if}} - {{else}} - - {{/if}} - {{/unless}} -
-
- - - + {{/unless}} +
+
+
+ +
-
- -
- -
-
- {{else}} - - {{#if this.filter}} - - - {{this.humanizedFilterError}} -

- {{#if this.model.error.correction}} - Did you mean - ? - {{else if this.model.error.suggestion}} -
    - {{#each this.model.error.suggestion as |suggestion|}} -
  • - {{/each}} -
- {{else}} - {{!-- This is the "Nothing was found for your otherwise valid filter" option. Give them suggestions --}} - Did you know: you can try using filter expressions to search through your jobs. - Try ? + {{else if this.model.error.suggestion}} +
    + {{#each this.model.error.suggestion as |suggestion|}} +
  • - {{/if}} - + {{on "click" (action this.suggestFilter suggestion)}} + />
  • + {{/each}} +
+ {{else}} + {{!-- This is the "Nothing was found for your otherwise valid filter" option. Give them suggestions --}} + Did you know: you can try using filter expressions to search through your jobs. + Try + {{/if}} +
- {{else}} - - - - - - {{/if}} -
- {{/if}} + {{else}} + + + + + + {{/if}} + + {{/if}}