good_job 2.12.2 → 2.13.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/engine/app/assets/good_job/modules/application.js +12 -0
  4. data/engine/app/assets/good_job/modules/charts.js +29 -0
  5. data/engine/app/assets/good_job/modules/document_ready.js +7 -0
  6. data/engine/app/assets/good_job/modules/poller.js +93 -0
  7. data/engine/app/assets/good_job/modules/toasts.js +8 -0
  8. data/engine/app/assets/good_job/scripts.js +3 -0
  9. data/engine/app/assets/{style.css → good_job/style.css} +9 -3
  10. data/engine/app/assets/{vendor → good_job/vendor}/bootstrap/bootstrap.bundle.min.js +0 -0
  11. data/engine/app/assets/{vendor → good_job/vendor}/bootstrap/bootstrap.min.css +0 -0
  12. data/engine/app/assets/{vendor → good_job/vendor}/chartjs/chart.min.js +0 -0
  13. data/engine/app/assets/good_job/vendor/es_module_shims.js +1 -0
  14. data/engine/app/assets/{vendor → good_job/vendor}/rails_ujs.js +0 -0
  15. data/engine/app/controllers/good_job/assets_controller.rb +23 -6
  16. data/engine/app/controllers/good_job/executions_controller.rb +1 -5
  17. data/engine/app/controllers/good_job/jobs_controller.rb +1 -1
  18. data/engine/app/views/good_job/cron_entries/show.html.erb +4 -2
  19. data/engine/app/views/good_job/executions/_table.erb +2 -2
  20. data/engine/app/views/good_job/jobs/_table.erb +2 -2
  21. data/engine/app/views/good_job/jobs/index.html.erb +2 -10
  22. data/engine/app/views/good_job/shared/_alert.erb +20 -13
  23. data/engine/app/views/good_job/shared/_chart.erb +4 -2
  24. data/engine/app/views/good_job/shared/_filter.erb +58 -50
  25. data/engine/app/views/good_job/shared/_footer.erb +2 -4
  26. data/engine/app/views/good_job/shared/_navbar.erb +5 -8
  27. data/engine/app/views/layouts/good_job/application.html.erb +17 -12
  28. data/engine/config/locales/en.yml +2 -3
  29. data/engine/config/locales/es.yml +1 -2
  30. data/engine/config/locales/nl.yml +2 -3
  31. data/engine/config/locales/ru.yml +37 -14
  32. data/engine/config/routes.rb +5 -3
  33. data/lib/good_job/version.rb +1 -1
  34. metadata +14 -10
  35. data/engine/app/assets/scripts.js +0 -133
  36. data/engine/app/filters/good_job/executions_filter.rb +0 -41
  37. data/engine/app/views/good_job/executions/index.html.erb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d56e35eff62e8575f9096bcbc763978c0803d675232bf7aa939a0603434f6c6
4
- data.tar.gz: aec46224383849afe2b9ba93d245f70674f4af85765bfd804bb23f528229fd6d
3
+ metadata.gz: 2fe80504394d81f996c4d912ec4dc0b7c6750b5ecfd949af863a2d6087af68e8
4
+ data.tar.gz: 4c19aab29e610f5a73fe4682ac9824a6d8894e9713bc2c12e2373153093558b1
5
5
  SHA512:
6
- metadata.gz: 1f6d42db1f03b01f19eef705ff88acff1a93d5f1802df2c6240109c2178fe39f68733b3648799ebe186375166c39ec1fe04388c4fe61d06708d52c520023e685
7
- data.tar.gz: 2b64a3237f6f77d3e1a890e040aa20daf48efc92c461960e0a005f27d734a3639b0a0a10cdc0b4793ec962bac0c68a988ddc89779a83e10210e977c901a30cd1
6
+ metadata.gz: da255714957639ca3aafae09d0e8d4bba4e7e36ad1cd370b696b67170d88e16525f4e41751e306b5d671f74fffe36873283ad3f667a3183e532c86e255648353
7
+ data.tar.gz: 0fdf220cc5e6856eac99aef51aa5cd906596f6359c688bcb831d594f7a2355c37346bf63637715fd3669351a952f188f89ce24a01a47c561bcdabb28abdc5f86
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.13.2](https://github.com/bensheldon/good_job/tree/v2.13.2) (2022-04-25)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.13.1...v2.13.2)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Namespaces assets per Rails docs [\#580](https://github.com/bensheldon/good_job/pull/580) ([kylekthompson](https://github.com/kylekthompson))
10
+
11
+ ## [v2.13.1](https://github.com/bensheldon/good_job/tree/v2.13.1) (2022-04-22)
12
+
13
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.13.0...v2.13.1)
14
+
15
+ **Fixed bugs:**
16
+
17
+ - `ActionMailer::MailDeliveryJob` executing twice [\#329](https://github.com/bensheldon/good_job/issues/329)
18
+ - Email job breaks dashboard [\#313](https://github.com/bensheldon/good_job/issues/313)
19
+
20
+ **Closed issues:**
21
+
22
+ - Possible encryption feature? [\#561](https://github.com/bensheldon/good_job/issues/561)
23
+ - Inconsistencies in configuration settings [\#380](https://github.com/bensheldon/good_job/issues/380)
24
+ - Lockable should accept an explicit keys on class methods too [\#341](https://github.com/bensheldon/good_job/issues/341)
25
+ - Run Scheduler\#cache\_warm on global thread pool instead of Scheduler's thread pool [\#286](https://github.com/bensheldon/good_job/issues/286)
26
+
27
+ **Merged pull requests:**
28
+
29
+ - Dashboard: Use toasts to show notices and alerts [\#577](https://github.com/bensheldon/good_job/pull/577) ([bkeepers](https://github.com/bkeepers))
30
+ - Remove executions from the dashboard [\#576](https://github.com/bensheldon/good_job/pull/576) ([bkeepers](https://github.com/bkeepers))
31
+ - Use javascript importmaps for Dashboard [\#574](https://github.com/bensheldon/good_job/pull/574) ([bensheldon](https://github.com/bensheldon))
32
+
33
+ ## [v2.13.0](https://github.com/bensheldon/good_job/tree/v2.13.0) (2022-04-19)
34
+
35
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.12.2...v2.13.0)
36
+
37
+ **Implemented enhancements:**
38
+
39
+ - Dashboard UI updates: sticky navbar, statuses as tabs [\#572](https://github.com/bensheldon/good_job/pull/572) ([bkeepers](https://github.com/bkeepers))
40
+
41
+ **Closed issues:**
42
+
43
+ - Internationalize/I18n the Dashboard Engine [\#408](https://github.com/bensheldon/good_job/issues/408)
44
+
45
+ **Merged pull requests:**
46
+
47
+ - Fix Russian translation linting [\#573](https://github.com/bensheldon/good_job/pull/573) ([bensheldon](https://github.com/bensheldon))
48
+
3
49
  ## [v2.12.2](https://github.com/bensheldon/good_job/tree/v2.12.2) (2022-04-18)
4
50
 
5
51
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.12.1...v2.12.2)
@@ -0,0 +1,12 @@
1
+ /*jshint esversion: 6, strict: false */
2
+
3
+ import documentReady from "document_ready";
4
+ import showToasts from "toasts";
5
+ import renderCharts from "charts";
6
+ import Poller from "poller";
7
+
8
+ documentReady(function() {
9
+ renderCharts();
10
+ showToasts();
11
+ Poller.start();
12
+ });
@@ -0,0 +1,29 @@
1
+ function renderCharts(animate) {
2
+ const charts = document.querySelectorAll('.chart');
3
+
4
+ for (let i = 0; i < charts.length; i++) {
5
+ const chartEl = charts[i];
6
+ const chartData = JSON.parse(chartEl.dataset.json);
7
+
8
+ const ctx = chartEl.getContext('2d');
9
+ const chart = new Chart(ctx, {
10
+ type: 'line',
11
+ data: {
12
+ labels: chartData.labels,
13
+ datasets: chartData.datasets
14
+ },
15
+ options: {
16
+ animation: animate,
17
+ responsive: true,
18
+ maintainAspectRatio: false,
19
+ scales: {
20
+ y: {
21
+ beginAtZero: true
22
+ }
23
+ }
24
+ }
25
+ });
26
+ }
27
+ }
28
+
29
+ export { renderCharts as default };
@@ -0,0 +1,7 @@
1
+ export default function documentReady(callback) {
2
+ if (document.readyState !== "loading") {
3
+ callback();
4
+ } else {
5
+ document.addEventListener("DOMContentLoaded", callback);
6
+ }
7
+ }
@@ -0,0 +1,93 @@
1
+ /*jshint esversion: 6, strict: false */
2
+ import renderCharts from "charts";
3
+
4
+ // NOTE: this file is a bit disorganized. Please do not use it as a template for how to organize a JS module.
5
+
6
+ const DEFAULT_POLL_INTERVAL_SECONDS = 30;
7
+ const MINIMUM_POLL_INTERVAL = 1000;
8
+
9
+ function getStorage(key) {
10
+ const value = localStorage.getItem('good_job-' + key);
11
+
12
+ if (value === 'true') {
13
+ return true;
14
+ } else if (value === 'false') {
15
+ return false;
16
+ } else {
17
+ return value;
18
+ }
19
+ }
20
+
21
+ function setStorage(key, value) {
22
+ localStorage.setItem('good_job-' + key, value);
23
+ }
24
+
25
+ function updatePageContent(newContent) {
26
+ const domParser = new DOMParser();
27
+ const parsedDOM = domParser.parseFromString(newContent, "text/html");
28
+
29
+ const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]');
30
+
31
+ for (let i = 0; i < newElements.length; i++) {
32
+ const newEl = newElements[i];
33
+ const oldEl = document.getElementById(newEl.id);
34
+
35
+ if (oldEl) {
36
+ oldEl.replaceWith(newEl);
37
+ }
38
+ }
39
+
40
+ renderCharts(false);
41
+ }
42
+
43
+ function refreshPage() {
44
+ fetch(window.location.href)
45
+ .then(resp => resp.text())
46
+ .then(updatePageContent);
47
+ }
48
+
49
+ const Poller = {
50
+ start: () => {
51
+ Poller.updateSettings();
52
+ Poller.pollUpdates();
53
+
54
+ const checkbox = document.querySelector('input[name="toggle-poll"]');
55
+ checkbox.addEventListener('change', Poller.togglePoll)
56
+ },
57
+
58
+ togglePoll: (event) => {
59
+ Poller.pollEnabled = event.currentTarget.checked;
60
+ setStorage('pollEnabled', Poller.pollEnabled);
61
+ },
62
+
63
+ updateSettings: () => {
64
+ const queryString = window.location.search;
65
+ const urlParams = new URLSearchParams(queryString);
66
+
67
+ if (urlParams.has('poll')) {
68
+ const parsedInterval = (parseInt(urlParams.get('poll')) || DEFAULT_POLL_INTERVAL_SECONDS) * 1000;
69
+ Poller.pollInterval = Math.max(parsedInterval, MINIMUM_POLL_INTERVAL);
70
+ setStorage('pollInterval', Poller.pollInterval);
71
+
72
+ Poller.pollEnabled = true;
73
+ } else {
74
+ Poller.pollInterval = getStorage('pollInterval') || (DEFAULT_POLL_INTERVAL_SECONDS * 1000);
75
+ Poller.pollEnabled = getStorage('pollEnabled') || false;
76
+ }
77
+
78
+ document.getElementById('toggle-poll').checked = Poller.pollEnabled;
79
+ },
80
+
81
+ pollUpdates: () => {
82
+ setTimeout(() => {
83
+ if (Poller.pollEnabled === true) {
84
+ refreshPage();
85
+ Poller.pollUpdates();
86
+ } else {
87
+ Poller.pollUpdates();
88
+ }
89
+ }, Poller.pollInterval);
90
+ },
91
+ };
92
+
93
+ export { Poller as default };
@@ -0,0 +1,8 @@
1
+ export default function showToasts() {
2
+ const toasts = document.querySelectorAll('.toast');
3
+
4
+ for (let i = 0; i < toasts.length; i++) {
5
+ var toast = new bootstrap.Toast(toasts[i])
6
+ toast.show()
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ /*jshint esversion: 6, strict: false */
2
+
3
+ import "application"; // ./modules/good_job/application.js
@@ -24,7 +24,13 @@
24
24
  height: 200px;
25
25
  }
26
26
 
27
- body {
28
- /* Make room for the sticky footer */
29
- margin-bottom: 100px;
27
+ /* Break out of a container */
28
+ .break-out {
29
+ width:100vw;
30
+ position:relative;
31
+ left:calc(-1 * (100vw - 100%)/2);
32
+ }
33
+
34
+ .toast-container {
35
+ z-index: 1;
30
36
  }
@@ -0,0 +1 @@
1
+ (function(){const noop=()=>{};const e=document.querySelector("script[type=esms-options]");const t=e?JSON.parse(e.innerHTML):{};Object.assign(t,self.esmsInitOptions||{});let r=!!t.shimMode;const s=globalHook(r&&t.onimport);const a=globalHook(r&&t.resolve);let n=t.fetch?globalHook(t.fetch):fetch;const i=t.meta?globalHook(shimModule&&t.meta):noop;const c=t.skip?new RegExp(t.skip):null;let f=t.nonce;const te=t.mapOverrides;if(!f){const e=document.querySelector("script[nonce]");e&&(f=e.nonce||e.getAttribute("nonce"))}const re=globalHook(t.onerror||noop);const se=t.onpolyfill?globalHook(t.onpolyfill):()=>console.log("%c^^ Module TypeError above is polyfilled and can be ignored ^^","font-weight:900;color:#391");const{revokeBlobURLs:ae,noLoadEventRetriggers:ne,enforceIntegrity:ie}=t;function globalHook(e){return"string"===typeof e?self[e]:e}const oe=Array.isArray(t.polyfillEnable)?t.polyfillEnable:[];const ce=oe.includes("css-modules");const le=oe.includes("json-modules");function setShimMode(){r=true}const fe=!navigator.userAgentData&&!!navigator.userAgent.match(/Edge\/\d+\.\d+/);const ue=document.baseURI;function createBlob(e,t="text/javascript"){return URL.createObjectURL(new Blob([e],{type:t}))}const eoop=e=>setTimeout((()=>{throw e}));const throwError=e=>{(window.reportError||window.safari&&console.error||eoop)(e),void re(e)};function fromParent(e){return e?` imported from ${e}`:""}const de=/\\/g;function isURL(e){if(-1===e.indexOf(":"))return false;try{new URL(e);return true}catch(e){return false}}function resolveUrl(e,t){return resolveIfNotPlainOrUrl(e,t)||(isURL(e)?e:resolveIfNotPlainOrUrl("./"+e,t))}function resolveIfNotPlainOrUrl(e,t){const r=t.indexOf("?",-1===t.indexOf("#")?t.indexOf("#"):t.length);-1!==r&&(t=t.slice(0,r));-1!==e.indexOf("\\")&&(e=e.replace(de,"/"));if("/"===e[0]&&"/"===e[1])return t.slice(0,t.indexOf(":")+1)+e;if("."===e[0]&&("/"===e[1]||"."===e[1]&&("/"===e[2]||2===e.length&&(e+="/"))||1===e.length&&(e+="/"))||"/"===e[0]){const r=t.slice(0,t.indexOf(":")+1);let s;if("/"===t[r.length+1])if("file:"!==r){s=t.slice(r.length+2);s=s.slice(s.indexOf("/")+1)}else s=t.slice(8);else s=t.slice(r.length+("/"===t[r.length]));if("/"===e[0])return t.slice(0,t.length-s.length-1)+e;const a=s.slice(0,s.lastIndexOf("/")+1)+e;const n=[];let i=-1;for(let e=0;e<a.length;e++)if(-1===i){if("."===a[e]){if("."===a[e+1]&&("/"===a[e+2]||e+2===a.length)){n.pop();e+=2;continue}if("/"===a[e+1]||e+1===a.length){e+=1;continue}}while("/"===a[e])e++;i=e}else if("/"===a[e]){n.push(a.slice(i,e+1));i=-1}-1!==i&&n.push(a.slice(i));return t.slice(0,t.length-s.length)+n.join("")}}function resolveAndComposeImportMap(e,t,r){const s={imports:Object.assign({},r.imports),scopes:Object.assign({},r.scopes)};e.imports&&resolveAndComposePackages(e.imports,s.imports,t,r);if(e.scopes)for(let a in e.scopes){const n=resolveUrl(a,t);resolveAndComposePackages(e.scopes[a],s.scopes[n]||(s.scopes[n]={}),t,r)}return s}function getMatch(e,t){if(t[e])return e;let r=e.length;do{const s=e.slice(0,r+1);if(s in t)return s}while(-1!==(r=e.lastIndexOf("/",r-1)))}function applyPackages(e,t){const r=getMatch(e,t);if(r){const s=t[r];if(null===s)return;return s+e.slice(r.length)}}function resolveImportMap(e,t,r){let s=r&&getMatch(r,e.scopes);while(s){const r=applyPackages(t,e.scopes[s]);if(r)return r;s=getMatch(s.slice(0,s.lastIndexOf("/")),e.scopes)}return applyPackages(t,e.imports)||-1!==t.indexOf(":")&&t}function resolveAndComposePackages(e,t,s,a){for(let n in e){const i=resolveIfNotPlainOrUrl(n,s)||n;if((!r||!te)&&t[i]&&t[i]!==e[i])throw Error(`Rejected map override "${i}" from ${t[i]} to ${e[i]}.`);let c=e[n];if("string"!==typeof c)continue;const f=resolveImportMap(a,resolveIfNotPlainOrUrl(c,s)||c,s);f?t[i]=f:console.warn(`Mapping "${n}" -> "${e[n]}" does not resolve`)}}let pe;window.addEventListener("error",(e=>pe=e));function dynamicImportScript(e,{errUrl:t=e}={}){pe=void 0;const r=createBlob(`import*as m from'${e}';self._esmsi=m`);const s=Object.assign(document.createElement("script"),{type:"module",src:r});s.setAttribute("nonce",f);s.setAttribute("noshim","");const a=new Promise(((e,a)=>{s.addEventListener("error",cb);s.addEventListener("load",cb);function cb(n){document.head.removeChild(s);if(self._esmsi){e(self._esmsi,ue);self._esmsi=void 0}else{a(!(n instanceof Event)&&n||pe&&pe.error||new Error(`Error loading or executing the graph of ${t} (check the console for ${r}).`));pe=void 0}}}));document.head.appendChild(s);return a}let be=dynamicImportScript;const he=dynamicImportScript(createBlob("export default u=>import(u)")).then((e=>{e&&(be=e.default);return!!e}),noop);let ke=false;let me=false;let we=false;let ge=false;let ve=false;const ye=Promise.resolve(he).then((e=>{if(e){ve=true;return Promise.all([be(createBlob("import.meta")).then((()=>we=true),noop),ce&&be(createBlob('import"data:text/css,{}"assert{type:"css"}')).then((()=>me=true),noop),le&&be(createBlob('import"data:text/json,{}"assert{type:"json"}')).then((()=>ke=true),noop),new Promise((e=>{self._$s=r=>{document.head.removeChild(t);r&&(ge=true);delete self._$s;e()};const t=document.createElement("iframe");t.style.display="none";t.srcdoc=`<script type=importmap nonce="${f}">{"imports":{"x":"data:text/javascript,"}}<\/script><script nonce="${f}">import('x').then(()=>1,()=>0).then(v=>parent._$s(v))<\/script>`;document.head.appendChild(t)}))])}}));let Se,$e,Le,Oe=2<<19;const Ce=1===new Uint8Array(new Uint16Array([1]).buffer)[0]?function(e,t){const r=e.length;let s=0;for(;s<r;)t[s]=e.charCodeAt(s++)}:function(e,t){const r=e.length;let s=0;for(;s<r;){const r=e.charCodeAt(s);t[s++]=(255&r)<<8|r>>>8}},Ae="xportmportlassetafromssertvoyiedeleinstantyreturdebuggeawaithrwhileforifcatcfinallels";let Ue,Ie,Me;function parse(e,t="@"){Ue=e,Ie=t;const r=2*Ue.length+(2<<18);if(r>Oe||!Se){for(;r>Oe;)Oe*=2;$e=new ArrayBuffer(Oe),Ce(Ae,new Uint16Array($e,16,85)),Se=function(e,t,r){"use asm";var s=new e.Int8Array(r),a=new e.Int16Array(r),n=new e.Int32Array(r),i=new e.Uint8Array(r),c=new e.Uint16Array(r),f=992;function b(e){e=e|0;var t=0,r=0,i=0,te=0,re=0,se=0,ae=0;ae=f;f=f+11520|0;re=ae+2048|0;s[763]=1;a[377]=0;a[378]=0;a[379]=0;a[380]=-1;n[57]=n[2];s[764]=0;n[56]=0;s[762]=0;n[58]=ae+10496;n[59]=ae+2304;n[60]=ae;s[765]=0;e=(n[3]|0)+-2|0;n[61]=e;t=e+(n[54]<<1)|0;n[62]=t;e:while(1){r=e+2|0;n[61]=r;if(e>>>0>=t>>>0){te=18;break}t:do{switch(a[r>>1]|0){case 9:case 10:case 11:case 12:case 13:case 32:break;case 101:{if((((a[379]|0)==0?D(r)|0:0)?(m(e+4|0,16,10)|0)==0:0)?(k(),(s[763]|0)==0):0){te=9;break e}else te=17;break}case 105:{if(D(r)|0?(m(e+4|0,26,10)|0)==0:0){l();te=17}else te=17;break}case 59:{te=17;break}case 47:switch(a[e+4>>1]|0){case 47:{j();break t}case 42:{y(1);break t}default:{te=16;break e}}default:{te=16;break e}}}while(0);if((te|0)==17){te=0;n[57]=n[61]}e=n[61]|0;t=n[62]|0}if((te|0)==9){e=n[61]|0;n[57]=e;te=19}else if((te|0)==16){s[763]=0;n[61]=e;te=19}else if((te|0)==18)if(!(s[762]|0)){e=r;te=19}else e=0;do{if((te|0)==19){e:while(1){t=e+2|0;n[61]=t;i=t;if(e>>>0>=(n[62]|0)>>>0){te=75;break}t:do{switch(a[t>>1]|0){case 9:case 10:case 11:case 12:case 13:case 32:break;case 101:{if(((a[379]|0)==0?D(t)|0:0)?(m(e+4|0,16,10)|0)==0:0){k();te=74}else te=74;break}case 105:{if(D(t)|0?(m(e+4|0,26,10)|0)==0:0){l();te=74}else te=74;break}case 99:{if((D(t)|0?(m(e+4|0,36,8)|0)==0:0)?M(a[e+12>>1]|0)|0:0){s[765]=1;te=74}else te=74;break}case 40:{r=n[57]|0;i=n[59]|0;te=a[379]|0;a[379]=te+1<<16>>16;n[i+((te&65535)<<2)>>2]=r;te=74;break}case 41:{t=a[379]|0;if(!(t<<16>>16)){te=36;break e}t=t+-1<<16>>16;a[379]=t;r=a[378]|0;if(r<<16>>16!=0?(se=n[(n[60]|0)+((r&65535)+-1<<2)>>2]|0,(n[se+20>>2]|0)==(n[(n[59]|0)+((t&65535)<<2)>>2]|0)):0){t=se+4|0;if(!(n[t>>2]|0))n[t>>2]=i;n[se+12>>2]=e+4;a[378]=r+-1<<16>>16;te=74}else te=74;break}case 123:{te=n[57]|0;i=n[51]|0;e=te;do{if((a[te>>1]|0)==41&(i|0)!=0?(n[i+4>>2]|0)==(te|0):0){t=n[52]|0;n[51]=t;if(!t){n[47]=0;break}else{n[t+28>>2]=0;break}}}while(0);r=a[379]|0;te=r&65535;s[re+te>>0]=s[765]|0;s[765]=0;i=n[59]|0;a[379]=r+1<<16>>16;n[i+(te<<2)>>2]=e;te=74;break}case 125:{e=a[379]|0;if(!(e<<16>>16)){te=49;break e}r=e+-1<<16>>16;a[379]=r;t=a[380]|0;if(e<<16>>16!=t<<16>>16)if(t<<16>>16!=-1&(r&65535)<(t&65535)){te=53;break e}else{te=74;break t}else{i=n[58]|0;te=(a[377]|0)+-1<<16>>16;a[377]=te;a[380]=a[i+((te&65535)<<1)>>1]|0;h();te=74;break t}}case 39:{d(39);te=74;break}case 34:{d(34);te=74;break}case 47:switch(a[e+4>>1]|0){case 47:{j();break t}case 42:{y(1);break t}default:{t=n[57]|0;r=a[t>>1]|0;r:do{if(!(U(r)|0)){switch(r<<16>>16){case 41:if(q(n[(n[59]|0)+(c[379]<<2)>>2]|0)|0){te=71;break r}else{te=68;break r}case 125:break;default:{te=68;break r}}e=c[379]|0;if(!(p(n[(n[59]|0)+(e<<2)>>2]|0)|0)?(s[re+e>>0]|0)==0:0)te=68;else te=71}else switch(r<<16>>16){case 46:if(((a[t+-2>>1]|0)+-48&65535)<10){te=68;break r}else{te=71;break r}case 43:if((a[t+-2>>1]|0)==43){te=68;break r}else{te=71;break r}case 45:if((a[t+-2>>1]|0)==45){te=68;break r}else{te=71;break r}default:{te=71;break r}}}while(0);r:do{if((te|0)==68){te=0;if(!(o(t)|0)){switch(r<<16>>16){case 0:{te=71;break r}case 47:break;default:{e=1;break r}}if(!(s[764]|0))e=1;else te=71}else te=71}}while(0);if((te|0)==71){g();e=0}s[764]=e;te=74;break t}}case 96:{h();te=74;break}default:te=74}}while(0);if((te|0)==74){te=0;n[57]=n[61]}e=n[61]|0}if((te|0)==36){L();e=0;break}else if((te|0)==49){L();e=0;break}else if((te|0)==53){L();e=0;break}else if((te|0)==75){e=(a[380]|0)==-1&(a[379]|0)==0&(s[762]|0)==0&(a[378]|0)==0;break}}}while(0);f=ae;return e|0}function k(){var e=0,t=0,r=0,i=0,c=0,f=0;c=n[61]|0;f=c+12|0;n[61]=f;t=w(1)|0;e=n[61]|0;if(!((e|0)==(f|0)?!(I(t)|0):0))i=3;e:do{if((i|0)==3){t:do{switch(t<<16>>16){case 100:{B(e,e+14|0);break e}case 97:{n[61]=e+10;w(1)|0;e=n[61]|0;i=6;break}case 102:{i=6;break}case 99:{if((m(e+2|0,36,8)|0)==0?(r=e+10|0,$(a[r>>1]|0)|0):0){n[61]=r;c=w(1)|0;f=n[61]|0;E(c)|0;B(f,n[61]|0);n[61]=(n[61]|0)+-2;break e}e=e+4|0;n[61]=e;i=13;break}case 108:case 118:{i=13;break}case 123:{n[61]=e+2;e=w(1)|0;r=n[61]|0;while(1){if(N(e)|0){d(e);e=(n[61]|0)+2|0;n[61]=e}else{E(e)|0;e=n[61]|0}w(1)|0;e=C(r,e)|0;if(e<<16>>16==44){n[61]=(n[61]|0)+2;e=w(1)|0}t=r;r=n[61]|0;if(e<<16>>16==125){i=32;break}if((r|0)==(t|0)){i=29;break}if(r>>>0>(n[62]|0)>>>0){i=31;break}}if((i|0)==29){L();break e}else if((i|0)==31){L();break e}else if((i|0)==32){n[61]=r+2;i=34;break t}break}case 42:{n[61]=e+2;w(1)|0;i=n[61]|0;C(i,i)|0;i=34;break}default:{}}}while(0);if((i|0)==6){n[61]=e+16;e=w(1)|0;if(e<<16>>16==42){n[61]=(n[61]|0)+2;e=w(1)|0}f=n[61]|0;E(e)|0;B(f,n[61]|0);n[61]=(n[61]|0)+-2;break}else if((i|0)==13){e=e+4|0;n[61]=e;s[763]=0;t:while(1){n[61]=e+2;f=w(1)|0;e=n[61]|0;switch((E(f)|0)<<16>>16){case 91:case 123:{i=15;break t}default:{}}t=n[61]|0;if((t|0)==(e|0))break e;B(e,t);switch((w(1)|0)<<16>>16){case 61:{i=19;break t}case 44:break;default:{i=20;break t}}e=n[61]|0}if((i|0)==15){n[61]=(n[61]|0)+-2;break}else if((i|0)==19){n[61]=(n[61]|0)+-2;break}else if((i|0)==20){n[61]=(n[61]|0)+-2;break}}else if((i|0)==34)t=w(1)|0;e=n[61]|0;if(t<<16>>16==102?(m(e+2|0,52,6)|0)==0:0){n[61]=e+8;u(c,w(1)|0);break}n[61]=e+-2}}while(0);return}function l(){var e=0,t=0,r=0,i=0,c=0;c=n[61]|0;t=c+12|0;n[61]=t;e:do{switch((w(1)|0)<<16>>16){case 40:{e=n[61]|0;t=n[59]|0;r=a[379]|0;a[379]=r+1<<16>>16;n[t+((r&65535)<<2)>>2]=e;if((a[n[57]>>1]|0)!=46){e=n[61]|0;n[61]=e+2;r=w(1)|0;v(c,n[61]|0,0,e);e=n[51]|0;t=n[60]|0;c=a[378]|0;a[378]=c+1<<16>>16;n[t+((c&65535)<<2)>>2]=e;switch(r<<16>>16){case 39:{d(39);break}case 34:{d(34);break}default:{n[61]=(n[61]|0)+-2;break e}}e=(n[61]|0)+2|0;n[61]=e;switch((w(1)|0)<<16>>16){case 44:{n[61]=(n[61]|0)+2;w(1)|0;r=n[51]|0;n[r+4>>2]=e;c=n[61]|0;n[r+16>>2]=c;s[r+24>>0]=1;n[61]=c+-2;break e}case 41:{a[379]=(a[379]|0)+-1<<16>>16;c=n[51]|0;n[c+4>>2]=e;n[c+12>>2]=(n[61]|0)+2;s[c+24>>0]=1;a[378]=(a[378]|0)+-1<<16>>16;break e}default:{n[61]=(n[61]|0)+-2;break e}}}break}case 46:{n[61]=(n[61]|0)+2;if(((w(1)|0)<<16>>16==109?(e=n[61]|0,(m(e+2|0,44,6)|0)==0):0)?(a[n[57]>>1]|0)!=46:0)v(c,c,e+8|0,2);break}case 42:case 39:case 34:{i=16;break}case 123:{e=n[61]|0;if(a[379]|0){n[61]=e+-2;break e}while(1){if(e>>>0>=(n[62]|0)>>>0)break;e=w(1)|0;if(!(N(e)|0)){if(e<<16>>16==125){i=31;break}}else d(e);e=(n[61]|0)+2|0;n[61]=e}if((i|0)==31)n[61]=(n[61]|0)+2;w(1)|0;e=n[61]|0;if(m(e,50,8)|0){L();break e}n[61]=e+8;e=w(1)|0;if(N(e)|0){u(c,e);break e}else{L();break e}}default:if((n[61]|0)!=(t|0))i=16}}while(0);do{if((i|0)==16){if(a[379]|0){n[61]=(n[61]|0)+-2;break}e=n[62]|0;t=n[61]|0;while(1){if(t>>>0>=e>>>0){i=23;break}r=a[t>>1]|0;if(N(r)|0){i=21;break}i=t+2|0;n[61]=i;t=i}if((i|0)==21){u(c,r);break}else if((i|0)==23){L();break}}}while(0);return}function u(e,t){e=e|0;t=t|0;var r=0,s=0;r=(n[61]|0)+2|0;switch(t<<16>>16){case 39:{d(39);s=5;break}case 34:{d(34);s=5;break}default:L()}do{if((s|0)==5){v(e,r,n[61]|0,1);n[61]=(n[61]|0)+2;s=(w(0)|0)<<16>>16==97;t=n[61]|0;if(s?(m(t+2|0,58,10)|0)==0:0){n[61]=t+12;if((w(1)|0)<<16>>16!=123){n[61]=t;break}e=n[61]|0;r=e;e:while(1){n[61]=r+2;r=w(1)|0;switch(r<<16>>16){case 39:{d(39);n[61]=(n[61]|0)+2;r=w(1)|0;break}case 34:{d(34);n[61]=(n[61]|0)+2;r=w(1)|0;break}default:r=E(r)|0}if(r<<16>>16!=58){s=16;break}n[61]=(n[61]|0)+2;switch((w(1)|0)<<16>>16){case 39:{d(39);break}case 34:{d(34);break}default:{s=20;break e}}n[61]=(n[61]|0)+2;switch((w(1)|0)<<16>>16){case 125:{s=25;break e}case 44:break;default:{s=24;break e}}n[61]=(n[61]|0)+2;if((w(1)|0)<<16>>16==125){s=25;break}r=n[61]|0}if((s|0)==16){n[61]=t;break}else if((s|0)==20){n[61]=t;break}else if((s|0)==24){n[61]=t;break}else if((s|0)==25){s=n[51]|0;n[s+16>>2]=e;n[s+12>>2]=(n[61]|0)+2;break}}n[61]=t+-2}}while(0);return}function o(e){e=e|0;e:do{switch(a[e>>1]|0){case 100:switch(a[e+-2>>1]|0){case 105:{e=S(e+-4|0,68,2)|0;break e}case 108:{e=S(e+-4|0,72,3)|0;break e}default:{e=0;break e}}case 101:{switch(a[e+-2>>1]|0){case 115:break;case 116:{e=S(e+-4|0,78,4)|0;break e}default:{e=0;break e}}switch(a[e+-4>>1]|0){case 108:{e=O(e+-6|0,101)|0;break e}case 97:{e=O(e+-6|0,99)|0;break e}default:{e=0;break e}}}case 102:{if((a[e+-2>>1]|0)==111?(a[e+-4>>1]|0)==101:0)switch(a[e+-6>>1]|0){case 99:{e=S(e+-8|0,86,6)|0;break e}case 112:{e=S(e+-8|0,98,2)|0;break e}default:{e=0;break e}}else e=0;break}case 110:{e=e+-2|0;if(O(e,105)|0)e=1;else e=S(e,102,5)|0;break}case 111:{e=O(e+-2|0,100)|0;break}case 114:{e=S(e+-2|0,112,7)|0;break}case 116:{e=S(e+-2|0,126,4)|0;break}case 119:switch(a[e+-2>>1]|0){case 101:{e=O(e+-4|0,110)|0;break e}case 111:{e=S(e+-4|0,134,3)|0;break e}default:{e=0;break e}}default:e=0}}while(0);return e|0}function h(){var e=0,t=0,r=0;t=n[62]|0;r=n[61]|0;e:while(1){e=r+2|0;if(r>>>0>=t>>>0){t=8;break}switch(a[e>>1]|0){case 96:{t=9;break e}case 36:{if((a[r+4>>1]|0)==123){t=6;break e}break}case 92:{e=r+4|0;break}default:{}}r=e}if((t|0)==6){n[61]=r+4;e=a[380]|0;t=n[58]|0;r=a[377]|0;a[377]=r+1<<16>>16;a[t+((r&65535)<<1)>>1]=e;r=(a[379]|0)+1<<16>>16;a[379]=r;a[380]=r}else if((t|0)==8){n[61]=e;L()}else if((t|0)==9)n[61]=e;return}function w(e){e=e|0;var t=0,r=0,s=0;r=n[61]|0;e:do{t=a[r>>1]|0;t:do{if(t<<16>>16!=47)if(e)if(M(t)|0)break;else break e;else if(z(t)|0)break;else break e;else switch(a[r+2>>1]|0){case 47:{j();break t}case 42:{y(e);break t}default:{t=47;break e}}}while(0);s=n[61]|0;r=s+2|0;n[61]=r}while(s>>>0<(n[62]|0)>>>0);return t|0}function d(e){e=e|0;var t=0,r=0,s=0,i=0;i=n[62]|0;t=n[61]|0;while(1){s=t+2|0;if(t>>>0>=i>>>0){t=9;break}r=a[s>>1]|0;if(r<<16>>16==e<<16>>16){t=10;break}if(r<<16>>16==92){r=t+4|0;if((a[r>>1]|0)==13){t=t+6|0;t=(a[t>>1]|0)==10?t:r}else t=r}else if(T(r)|0){t=9;break}else t=s}if((t|0)==9){n[61]=s;L()}else if((t|0)==10)n[61]=s;return}function v(e,t,r,a){e=e|0;t=t|0;r=r|0;a=a|0;var i=0,c=0;i=n[55]|0;n[55]=i+32;c=n[51]|0;n[((c|0)==0?188:c+28|0)>>2]=i;n[52]=c;n[51]=i;n[i+8>>2]=e;if(2==(a|0))e=r;else e=1==(a|0)?r+2|0:0;n[i+12>>2]=e;n[i>>2]=t;n[i+4>>2]=r;n[i+16>>2]=0;n[i+20>>2]=a;s[i+24>>0]=1==(a|0)&1;n[i+28>>2]=0;return}function A(){var e=0,t=0,r=0;r=n[62]|0;t=n[61]|0;e:while(1){e=t+2|0;if(t>>>0>=r>>>0){t=6;break}switch(a[e>>1]|0){case 13:case 10:{t=6;break e}case 93:{t=7;break e}case 92:{e=t+4|0;break}default:{}}t=e}if((t|0)==6){n[61]=e;L();e=0}else if((t|0)==7){n[61]=e;e=93}return e|0}function C(e,t){e=e|0;t=t|0;var r=0,s=0;r=n[61]|0;s=a[r>>1]|0;if(s<<16>>16==97){n[61]=r+4;r=w(1)|0;e=n[61]|0;if(N(r)|0){d(r);t=(n[61]|0)+2|0;n[61]=t}else{E(r)|0;t=n[61]|0}s=w(1)|0;r=n[61]|0}if((r|0)!=(e|0))B(e,t);return s|0}function g(){var e=0,t=0,r=0;e:while(1){e=n[61]|0;t=e+2|0;n[61]=t;if(e>>>0>=(n[62]|0)>>>0){r=7;break}switch(a[t>>1]|0){case 13:case 10:{r=7;break e}case 47:break e;case 91:{A()|0;break}case 92:{n[61]=e+4;break}default:{}}}if((r|0)==7)L();return}function p(e){e=e|0;switch(a[e>>1]|0){case 62:{e=(a[e+-2>>1]|0)==61;break}case 41:case 59:{e=1;break}case 104:{e=S(e+-2|0,160,4)|0;break}case 121:{e=S(e+-2|0,168,6)|0;break}case 101:{e=S(e+-2|0,180,3)|0;break}default:e=0}return e|0}function y(e){e=e|0;var t=0,r=0,s=0,i=0,c=0;i=(n[61]|0)+2|0;n[61]=i;r=n[62]|0;while(1){t=i+2|0;if(i>>>0>=r>>>0)break;s=a[t>>1]|0;if(!e?T(s)|0:0)break;if(s<<16>>16==42?(a[i+4>>1]|0)==47:0){c=8;break}i=t}if((c|0)==8){n[61]=t;t=i+4|0}n[61]=t;return}function m(e,t,r){e=e|0;t=t|0;r=r|0;var a=0,n=0;e:do{if(!r)e=0;else{while(1){a=s[e>>0]|0;n=s[t>>0]|0;if(a<<24>>24!=n<<24>>24)break;r=r+-1|0;if(!r){e=0;break e}else{e=e+1|0;t=t+1|0}}e=(a&255)-(n&255)|0}}while(0);return e|0}function I(e){e=e|0;e:do{switch(e<<16>>16){case 38:case 37:case 33:{e=1;break}default:if((e&-8)<<16>>16==40|(e+-58&65535)<6)e=1;else{switch(e<<16>>16){case 91:case 93:case 94:{e=1;break e}default:{}}e=(e+-123&65535)<4}}}while(0);return e|0}function U(e){e=e|0;e:do{switch(e<<16>>16){case 38:case 37:case 33:break;default:if(!((e+-58&65535)<6|(e+-40&65535)<7&e<<16>>16!=41)){switch(e<<16>>16){case 91:case 94:break e;default:{}}return e<<16>>16!=125&(e+-123&65535)<4|0}}}while(0);return 1}function x(e){e=e|0;var t=0,r=0,s=0,i=0;r=f;f=f+16|0;s=r;n[s>>2]=0;n[54]=e;t=n[3]|0;i=t+(e<<1)|0;e=i+2|0;a[i>>1]=0;n[s>>2]=e;n[55]=e;n[47]=0;n[51]=0;n[49]=0;n[48]=0;n[53]=0;n[50]=0;f=r;return t|0}function S(e,t,r){e=e|0;t=t|0;r=r|0;var s=0,i=0;s=e+(0-r<<1)|0;i=s+2|0;e=n[3]|0;if(i>>>0>=e>>>0?(m(i,t,r<<1)|0)==0:0)if((i|0)==(e|0))e=1;else e=$(a[s>>1]|0)|0;else e=0;return e|0}function O(e,t){e=e|0;t=t|0;var r=0;r=n[3]|0;if(r>>>0<=e>>>0?(a[e>>1]|0)==t<<16>>16:0)if((r|0)==(e|0))r=1;else r=$(a[e+-2>>1]|0)|0;else r=0;return r|0}function $(e){e=e|0;e:do{if((e+-9&65535)<5)e=1;else{switch(e<<16>>16){case 32:case 160:{e=1;break e}default:{}}e=e<<16>>16!=46&(I(e)|0)}}while(0);return e|0}function j(){var e=0,t=0,r=0;e=n[62]|0;r=n[61]|0;e:while(1){t=r+2|0;if(r>>>0>=e>>>0)break;switch(a[t>>1]|0){case 13:case 10:break e;default:r=t}}n[61]=t;return}function B(e,t){e=e|0;t=t|0;var r=0,s=0;r=n[55]|0;n[55]=r+12;s=n[53]|0;n[((s|0)==0?192:s+8|0)>>2]=r;n[53]=r;n[r>>2]=e;n[r+4>>2]=t;n[r+8>>2]=0;return}function E(e){e=e|0;while(1){if(M(e)|0)break;if(I(e)|0)break;e=(n[61]|0)+2|0;n[61]=e;e=a[e>>1]|0;if(!(e<<16>>16)){e=0;break}}return e|0}function P(){var e=0;e=n[(n[49]|0)+20>>2]|0;switch(e|0){case 1:{e=-1;break}case 2:{e=-2;break}default:e=e-(n[3]|0)>>1}return e|0}function q(e){e=e|0;if(!(S(e,140,5)|0)?!(S(e,150,3)|0):0)e=S(e,156,2)|0;else e=1;return e|0}function z(e){e=e|0;switch(e<<16>>16){case 160:case 32:case 12:case 11:case 9:{e=1;break}default:e=0}return e|0}function D(e){e=e|0;if((n[3]|0)==(e|0))e=1;else e=$(a[e+-2>>1]|0)|0;return e|0}function F(){var e=0;e=n[(n[49]|0)+12>>2]|0;if(!e)e=-1;else e=e-(n[3]|0)>>1;return e|0}function G(){var e=0;e=n[(n[49]|0)+16>>2]|0;if(!e)e=-1;else e=e-(n[3]|0)>>1;return e|0}function H(){var e=0;e=n[(n[49]|0)+4>>2]|0;if(!e)e=-1;else e=e-(n[3]|0)>>1;return e|0}function J(){var e=0;e=n[49]|0;e=n[((e|0)==0?188:e+28|0)>>2]|0;n[49]=e;return(e|0)!=0|0}function K(){var e=0;e=n[50]|0;e=n[((e|0)==0?192:e+8|0)>>2]|0;n[50]=e;return(e|0)!=0|0}function L(){s[762]=1;n[56]=(n[61]|0)-(n[3]|0)>>1;n[61]=(n[62]|0)+2;return}function M(e){e=e|0;return(e|128)<<16>>16==160|(e+-9&65535)<5|0}function N(e){e=e|0;return e<<16>>16==39|e<<16>>16==34|0}function Q(){return(n[(n[49]|0)+8>>2]|0)-(n[3]|0)>>1|0}function R(){return(n[(n[50]|0)+4>>2]|0)-(n[3]|0)>>1|0}function T(e){e=e|0;return e<<16>>16==13|e<<16>>16==10|0}function V(){return(n[n[49]>>2]|0)-(n[3]|0)>>1|0}function W(){return(n[n[50]>>2]|0)-(n[3]|0)>>1|0}function X(){return i[(n[49]|0)+24>>0]|0|0}function Y(e){e=e|0;n[3]=e;return}function Z(){return(s[763]|0)!=0|0}function _(){return n[56]|0}function ee(e){e=e|0;f=e+992+15&-16;return 992}return{su:ee,ai:G,e:_,ee:R,es:W,f:Z,id:P,ie:H,ip:X,is:V,p:b,re:K,ri:J,sa:x,se:F,ses:Y,ss:Q}}("undefined"!=typeof self?self:global,{},$e),Le=Se.su(Oe-(2<<17))}const s=Ue.length+1;Se.ses(Le),Se.sa(s-1),Ce(Ue,new Uint16Array($e,Le,s)),Se.p()||(Me=Se.e(),o());const a=[],n=[];for(;Se.ri();){const e=Se.is(),t=Se.ie(),r=Se.ai(),s=Se.id(),n=Se.ss(),i=Se.se();let c;Se.ip()&&(c=b(-1===s?e:e+1,Ue.charCodeAt(-1===s?e-1:e))),a.push({n:c,s:e,e:t,ss:n,se:i,d:s,a:r})}for(;Se.re();){const e=Se.es(),t=Ue.charCodeAt(e);n.push(34===t||39===t?b(e+1,t):Ue.slice(Se.es(),Se.ee()))}return[a,n,!!Se.f()]}function b(e,t){Me=e;let r="",s=Me;for(;;){Me>=Ue.length&&o();const e=Ue.charCodeAt(Me);if(e===t)break;92===e?(r+=Ue.slice(s,Me),r+=k(),s=Me):(8232===e||8233===e||u(e)&&o(),++Me)}return r+=Ue.slice(s,Me++),r}function k(){let e=Ue.charCodeAt(++Me);switch(++Me,e){case 110:return"\n";case 114:return"\r";case 120:return String.fromCharCode(l(2));case 117:return function(){let e;123===Ue.charCodeAt(Me)?(++Me,e=l(Ue.indexOf("}",Me)-Me),++Me,e>1114111&&o()):e=l(4);return e<=65535?String.fromCharCode(e):(e-=65536,String.fromCharCode(55296+(e>>10),56320+(1023&e)))}();case 116:return"\t";case 98:return"\b";case 118:return"\v";case 102:return"\f";case 13:10===Ue.charCodeAt(Me)&&++Me;case 10:return"";case 56:case 57:o();default:if(e>=48&&e<=55){let t=Ue.substr(Me-1,3).match(/^[0-7]+/)[0],r=parseInt(t,8);return r>255&&(t=t.slice(0,-1),r=parseInt(t,8)),Me+=t.length-1,e=Ue.charCodeAt(Me),"0"===t&&56!==e&&57!==e||o(),String.fromCharCode(r)}return u(e)?"":String.fromCharCode(e)}}function l(e){const t=Me;let r=0,s=0;for(let t=0;t<e;++t,++Me){let e,a=Ue.charCodeAt(Me);if(95!==a){if(a>=97)e=a-97+10;else if(a>=65)e=a-65+10;else{if(!(a>=48&&a<=57))break;e=a-48}if(e>=16)break;s=a,r=16*r+e}else 95!==s&&0!==t||o(),s=a}return 95!==s&&Me-t===e||o(),r}function u(e){return 13===e||10===e}function o(){throw Object.assign(Error(`Parse error ${Ie}:${Ue.slice(0,Me).split("\n").length}:${Me-Ue.lastIndexOf("\n",Me-1)}`),{idx:Me})}async function _resolve(e,t){const r=resolveIfNotPlainOrUrl(e,t);return{r:resolveImportMap(Ee,r||e,t)||throwUnresolved(e,t),b:!r&&!isURL(e)}}const Pe=a?async(e,t)=>{let r=a(e,t,defaultResolve);r&&r.then&&(r=await r);return r?{r:r,b:!resolveIfNotPlainOrUrl(e,t)&&!isURL(e)}:_resolve(e,t)}:_resolve;async function importShim(e,...t){let a=t[t.length-1];"string"!==typeof a&&(a=ue);await Ne;s&&await s(e,"string"!==typeof t[1]?t[1]:{},a);if(Te||r||!Re){processImportMaps();r||(Te=false)}await _e;return topLevelLoad((await Pe(e,a)).r,{credentials:"same-origin"})}self.importShim=importShim;function defaultResolve(e,t){return resolveImportMap(Ee,resolveIfNotPlainOrUrl(e,t)||e,t)||throwUnresolved(e,t)}function throwUnresolved(e,t){throw Error(`Unable to resolve specifier '${e}'${fromParent(t)}`)}const resolveSync=(e,t=ue)=>{t=`${t}`;const r=a&&a(e,t,defaultResolve);return r&&!r.then?r:defaultResolve(e,t)};function metaResolve(e,t=this.url){return resolveSync(e,t)}importShim.resolve=resolveSync;importShim.getImportMap=()=>JSON.parse(JSON.stringify(Ee));const xe=importShim._r={};async function loadAll(e,t){if(!e.b&&!t[e.u]){t[e.u]=1;await e.L;await Promise.all(e.d.map((e=>loadAll(e,t))));e.n||(e.n=e.d.some((e=>e.n)))}}let Ee={imports:{},scopes:{}};let je=false;let Re;const Ne=ye.then((()=>{if(!r)if(document.querySelectorAll("script[type=module-shim],script[type=importmap-shim],link[rel=modulepreload-shim]").length)setShimMode();else{let e=false;for(const t of document.querySelectorAll("script[type=module],script[type=importmap]"))if(e){if("importmap"===t.type){je=true;break}}else"module"===t.type&&(e=true)}Re=true!==t.polyfillEnable&&ve&&we&&ge&&(!le||ke)&&(!ce||me)&&!je&&true;if(!r&&Re);else{new MutationObserver((e=>{for(const t of e)if("childList"===t.type)for(const e of t.addedNodes)if("SCRIPT"===e.tagName){e.type===(r?"module-shim":"module")&&processScript(e);e.type===(r?"importmap-shim":"importmap")&&processImportMap(e)}else"LINK"===e.tagName&&e.rel===(r?"modulepreload-shim":"modulepreload")&&processPreload(e)})).observe(document,{childList:true,subtree:true});processImportMaps();processScriptsAndPreloads()}}));let _e=Ne;let Be=true;let Te=true;async function topLevelLoad(e,t,a,n,i){r||(Te=false);await _e;s&&await s(id,"string"!==typeof args[1]?args[1]:{},parentUrl);if(!r&&Re){if(n)return null;await i;return be(a?createBlob(a):e,{errUrl:e||a})}const c=getOrCreateLoad(e,t,null,a);const f={};await loadAll(c,f);Fe=void 0;resolveDeps(c,f);await i;if(a&&!r&&!c.n&&true){const e=await be(createBlob(a),{errUrl:a});ae&&revokeObjectURLs(Object.keys(f));return e}if(Be&&!r&&c.n&&n){se();Be=false}const te=await be(r||c.n||!n?c.b:c.u,{errUrl:c.u});c.s&&(await be(c.s)).u$_(te);ae&&revokeObjectURLs(Object.keys(f));return te}function revokeObjectURLs(e){let t=0;const r=e.length;const s=self.requestIdleCallback?self.requestIdleCallback:self.requestAnimationFrame;s(cleanup);function cleanup(){const a=100*t;if(!(a>r)){for(const t of e.slice(a,a+100)){const e=xe[t];e&&URL.revokeObjectURL(e.b)}t++;s(cleanup)}}}function urlJsString(e){return`'${e.replace(/'/g,"\\'")}'`}let Fe;function resolveDeps(e,t){if(e.b||!t[e.u])return;t[e.u]=0;for(const c of e.d)resolveDeps(c,t);const[r]=e.a;const s=e.S;let a=fe&&Fe?`import '${Fe}';`:"";if(r.length){let f=0,te=0,re=[];function pushStringTo(t){while(re[re.length-1]<t){const t=re.pop();a+=`${s.slice(f,t)}, ${urlJsString(e.r)}`;f=t}a+=s.slice(f,t);f=t}for(const{s:se,ss:ae,se:ne,d:ie}of r)if(-1===ie){let oe=e.d[te++],ce=oe.b,le=!ce;le&&((ce=oe.s)||(ce=oe.s=createBlob(`export function u$_(m){${oe.a[1].map((e=>"default"===e?"d$_=m.default":`${e}=m.${e}`)).join(",")}}${oe.a[1].map((e=>"default"===e?"let d$_;export{d$_ as default}":`export let ${e}`)).join(";")}\n//# sourceURL=${oe.r}?cycle`)));pushStringTo(se-1);a+=`/*${s.slice(se-1,ne)}*/${urlJsString(ce)}`;if(!le&&oe.s){a+=`;import*as m$_${te} from'${oe.b}';import{u$_ as u$_${te}}from'${oe.s}';u$_${te}(m$_${te})`;oe.s=void 0}f=ne}else if(-2===ie){e.m={url:e.r,resolve:metaResolve};i(e.m,e.u);pushStringTo(se);a+=`importShim._r[${urlJsString(e.u)}].m`;f=ne}else{pushStringTo(ae+6);a+="Shim(";re.push(ne-1);f=se}pushStringTo(s.length)}else a+=s;let n=false;a=a.replace(He,((t,r,s)=>(n=!r,t.replace(s,(()=>new URL(s,e.r))))));n||(a+="\n//# sourceURL="+e.r);e.b=Fe=createBlob(a);e.S=void 0}const He=/\n\/\/# source(Mapping)?URL=([^\n]+)\s*((;|\/\/[^#][^\n]*)\s*)*$/;const qe=/^(text|application)\/(x-)?javascript(;|$)/;const De=/^(text|application)\/json(;|$)/;const Je=/^(text|application)\/css(;|$)/;const Ke=/url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g;let ze=[];let Ge=0;function pushFetchPool(){if(++Ge>100)return new Promise((e=>ze.push(e)))}function popFetchPool(){Ge--;ze.length&&ze.shift()()}async function doFetch(e,t,r){if(ie&&!t.integrity)throw Error(`No integrity for ${e}${fromParent(r)}.`);const s=pushFetchPool();s&&await s;try{var a=await n(e,t)}catch(t){t.message=`Unable to fetch ${e}${fromParent(r)} - see network log for details.\n`+t.message;throw t}finally{popFetchPool()}if(!a.ok)throw Error(`${a.status} ${a.statusText} ${a.url}${fromParent(r)}`);return a}async function fetchModule(e,t,r){const s=await doFetch(e,t,r);const a=s.headers.get("content-type");if(qe.test(a))return{r:s.url,s:await s.text(),t:"js"};if(De.test(a))return{r:s.url,s:`export default ${await s.text()}`,t:"json"};if(Je.test(a))return{r:s.url,s:`var s=new CSSStyleSheet();s.replaceSync(${JSON.stringify((await s.text()).replace(Ke,((t,r="",s,a)=>`url(${r}${resolveUrl(s||a,e)}${r})`)))});export default s;`,t:"css"};throw Error(`Unsupported Content-Type "${a}" loading ${e}${fromParent(r)}. Modules must be served with a valid MIME type like application/javascript.`)}function getOrCreateLoad(e,t,s,a){let n=xe[e];if(n&&!a)return n;n={u:e,r:a?e:void 0,f:void 0,S:void 0,L:void 0,a:void 0,d:void 0,b:void 0,s:void 0,n:false,t:null,m:null};if(xe[e]){let e=0;while(xe[n.u+ ++e]);n.u+=e}xe[n.u]=n;n.f=(async()=>{if(!a){let i;({r:n.r,s:a,t:i}=await(Xe[e]||fetchModule(e,t,s)));if(i&&!r){if("css"===i&&!ce||"json"===i&&!le)throw Error(`${i}-modules require <script type="esms-options">{ "polyfillEnable": ["${i}-modules"] }<\/script>`);("css"===i&&!me||"json"===i&&!ke)&&(n.n=true)}}try{n.a=parse(a,n.u)}catch(e){throwError(e);n.a=[[],[],false]}n.S=a;return n})();n.L=n.f.then((async()=>{let e=t;n.d=(await Promise.all(n.a[0].map((async({n:t,d:r})=>{(r>=0&&!ve||2===r&&!we)&&(n.n=true);if(-1!==r||!t)return;const{r:s,b:a}=await Pe(t,n.r||n.u);!a||ge&&!je||(n.n=true);if(c&&c.test(s))return{b:s};e.integrity&&(e=Object.assign({},e,{integrity:void 0}));return getOrCreateLoad(s,e,n.r).f})))).filter((e=>e))}));return n}function processScriptsAndPreloads(){for(const e of document.querySelectorAll(r?"script[type=module-shim]":"script[type=module]"))processScript(e);for(const e of document.querySelectorAll(r?"link[rel=modulepreload-shim]":"link[rel=modulepreload]"))processPreload(e)}function processImportMaps(){for(const e of document.querySelectorAll(r?'script[type="importmap-shim"]':'script[type="importmap"]'))processImportMap(e)}function getFetchOpts(e){const t={};e.integrity&&(t.integrity=e.integrity);e.referrerpolicy&&(t.referrerPolicy=e.referrerpolicy);"use-credentials"===e.crossorigin?t.credentials="include":"anonymous"===e.crossorigin?t.credentials="omit":t.credentials="same-origin";return t}let Qe=Promise.resolve();let Ve=1;function domContentLoadedCheck(){0!==--Ve||ne||document.dispatchEvent(new Event("DOMContentLoaded"))}document.addEventListener("DOMContentLoaded",(async()=>{await Ne;domContentLoadedCheck();if(r||!Re){processImportMaps();processScriptsAndPreloads()}}));let We=1;"complete"===document.readyState?readyStateCompleteCheck():document.addEventListener("readystatechange",(async()=>{processImportMaps();await Ne;readyStateCompleteCheck()}));function readyStateCompleteCheck(){0!==--We||ne||document.dispatchEvent(new Event("readystatechange"))}function processImportMap(e){if(!e.ep&&(e.src||e.innerHTML)){e.ep=true;if(e.src){if(!r)return;je=true}if(Te){_e=_e.then((async()=>{Ee=resolveAndComposeImportMap(e.src?await(await doFetch(e.src,getFetchOpts(e))).json():JSON.parse(e.innerHTML),e.src||ue,Ee)})).catch(throwError);r||(Te=false)}}}function processScript(e){if(e.ep)return;if(null!==e.getAttribute("noshim"))return;if(!e.src&&!e.innerHTML)return;e.ep=true;const t=We>0;const s=Ve>0;t&&We++;s&&Ve++;const a=null===e.getAttribute("async")&&t;const n=topLevelLoad(e.src||ue,getFetchOpts(e),!e.src&&e.innerHTML,!r,a&&Qe).catch(throwError);a&&(Qe=n.then(readyStateCompleteCheck));s&&n.then(domContentLoadedCheck)}const Xe={};function processPreload(e){if(!e.ep){e.ep=true;Xe[e.href]||(Xe[e.href]=fetchModule(e.href,getFetchOpts(e)))}}})();
@@ -3,32 +3,49 @@ module GoodJob
3
3
  class AssetsController < ActionController::Base # rubocop:disable Rails/ApplicationController
4
4
  skip_before_action :verify_authenticity_token, raise: false
5
5
 
6
+ def self.js_modules
7
+ @_js_modules ||= GoodJob::Engine.root.join("app", "assets", "good_job", "modules").children.select(&:file?).each_with_object({}) do |file, modules|
8
+ key = File.basename(file.basename.to_s, ".js").to_sym
9
+ modules[key] = file
10
+ end
11
+ end
12
+
6
13
  before_action do
7
14
  expires_in 1.year, public: true
8
15
  end
9
16
 
17
+ def es_module_shims_js
18
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "es_module_shims.js")
19
+ end
20
+
10
21
  def bootstrap_css
11
- render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap.min.css")
22
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.min.css")
12
23
  end
13
24
 
14
25
  def bootstrap_js
15
- render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap.bundle.min.js")
26
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "bootstrap", "bootstrap.bundle.min.js")
16
27
  end
17
28
 
18
29
  def chartjs_js
19
- render file: GoodJob::Engine.root.join("app", "assets", "vendor", "chartjs", "chart.min.js")
30
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "chartjs", "chart.min.js")
20
31
  end
21
32
 
22
33
  def rails_ujs_js
23
- render file: GoodJob::Engine.root.join("app", "assets", "vendor", "rails_ujs.js")
34
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "vendor", "rails_ujs.js")
24
35
  end
25
36
 
26
37
  def scripts_js
27
- render file: GoodJob::Engine.root.join("app", "assets", "scripts.js")
38
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "scripts.js")
28
39
  end
29
40
 
30
41
  def style_css
31
- render file: GoodJob::Engine.root.join("app", "assets", "style.css")
42
+ render file: GoodJob::Engine.root.join("app", "assets", "good_job", "style.css")
43
+ end
44
+
45
+ def modules_js
46
+ module_name = params[:module].to_sym
47
+ module_file = self.class.js_modules.fetch(module_name) { raise ActionController::RoutingError, 'Not Found' }
48
+ render file: module_file
32
49
  end
33
50
  end
34
51
  end
@@ -1,14 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  class ExecutionsController < GoodJob::ApplicationController
4
- def index
5
- @filter = ExecutionsFilter.new(params)
6
- end
7
-
8
4
  def destroy
9
5
  deleted_count = GoodJob::Execution.where(id: params[:id]).delete_all
10
6
  message = deleted_count.positive? ? { notice: "Job execution deleted" } : { alert: "Job execution not deleted" }
11
- redirect_back fallback_location: root_path, **message
7
+ redirect_back fallback_location: jobs_path, **message
12
8
  end
13
9
  end
14
10
  end
@@ -12,7 +12,7 @@ module GoodJob
12
12
  def show
13
13
  @executions = GoodJob::Execution.active_job_id(params[:id])
14
14
  .order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
15
- redirect_to root_path, alert: "Executions for Active Job #{params[:id]} not found" if @executions.empty?
15
+ redirect_to jobs_path, alert: "Executions for Active Job #{params[:id]} not found" if @executions.empty?
16
16
  end
17
17
 
18
18
  def discard
@@ -1,4 +1,6 @@
1
- <h1 class="mb-3">Cron Entry Key: <code><%= @cron_entry.id %></code></h1>
1
+ <% title = capture do %>
2
+ Cron Entry Key: <code><%= @cron_entry.id %></code>
3
+ <% end %>
2
4
 
3
- <%= render 'good_job/shared/filter', filter: @jobs_filter %>
5
+ <%= render 'good_job/shared/filter', title: title, filter: @jobs_filter %>
4
6
  <%= render 'good_job/jobs/table', jobs: @jobs_filter.records %>
@@ -1,6 +1,6 @@
1
- <div class="card my-3" data-gj-poll-replace id="executions-table">
1
+ <div class="my-3" data-gj-poll-replace id="executions-table">
2
2
  <div class="table-responsive">
3
- <table class="table card-table table-bordered table-hover table-sm mb-0" id="executions_index_table">
3
+ <table class="table table-hover table-sm mb-0" id="executions_index_table">
4
4
  <thead>
5
5
  <tr>
6
6
  <th>ActiveJob ID</th>
@@ -1,6 +1,6 @@
1
- <div class="card my-3" data-gj-poll-replace id="jobs-table">
1
+ <div class="my-3" data-gj-poll-replace id="jobs-table">
2
2
  <div class="table-responsive">
3
- <table class="table card-table table-bordered table-hover table-sm mb-0">
3
+ <table class="table table-hover table-sm mb-0">
4
4
  <thead>
5
5
  <tr>
6
6
  <th>ActiveJob ID</th>
@@ -1,13 +1,5 @@
1
- <div class="my-3 flex">
2
- <h2>All Jobs</h2>
3
- </div>
4
-
5
- <div class="card my-3 p-6" data-gj-poll-replace id="jobs-chart">
6
- <%= render 'good_job/shared/chart', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
7
- </div>
8
-
9
- <%= render 'good_job/shared/filter', filter: @filter %>
10
-
1
+ <%= render 'good_job/shared/filter', title: "Jobs", filter: @filter %>
2
+ <%= render 'good_job/shared/chart', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
11
3
  <%= render 'good_job/jobs/table', jobs: @filter.records %>
12
4
 
13
5
  <% if @filter.records.present? %>
@@ -1,13 +1,20 @@
1
- <% if notice %>
2
- <div class="alert alert-success alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
3
- <%= render "good_job/shared/icons/check", class: "flex-shrink-0 me-2" %>
4
- <div><%= notice %></div>
5
- <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
6
- </div>
7
- <% elsif alert %>
8
- <div class="alert alert-warning alert-dismissible fade show d-flex align-items-center offset-md-3 col-6" role="alert">
9
- <%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 me-2" %>
10
- <div><%= alert %></div>
11
- <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
12
- </div>
13
- <% end %>
1
+ <div class="toast-container position-fixed p-3 start-50 translate-middle-x">
2
+ <% if notice %>
3
+ <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
4
+ <div class="toast-body d-flex align-items-center gap-2">
5
+ <%= render "good_job/shared/icons/check", class: "flex-shrink-0 text-success" %>
6
+ <div class="flex-fill"><%= notice %></div>
7
+ <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
8
+ </div>
9
+ </div>
10
+ <% end %>
11
+ <% if alert %>
12
+ <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
13
+ <div class="toast-body d-flex align-items-center gap-2">
14
+ <%= render "good_job/shared/icons/exclamation", class: "flex-shrink-0 text-danger" %>
15
+ <div class="flex-fill"><%= alert %></div>
16
+ <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ </div>
@@ -1,3 +1,5 @@
1
- <div class="chart-wrapper">
2
- <canvas class="chart" data-json="<%= chart_data.to_json %>"></canvas>
1
+ <div class="py-4" data-gj-poll-replace id="chart">
2
+ <div class="chart-wrapper container-fluid">
3
+ <canvas class="chart" data-json="<%= chart_data.to_json %>"></canvas>
4
+ </div>
3
5
  </div>
@@ -1,59 +1,67 @@
1
- <%= form_with(url: "", method: :get, local: true, id: "filter_form") do |form| %>
2
- <%= hidden_field_tag :poll, value: params[:poll] %>
3
- <div class="d-flex flex-row w-100">
4
- <div class="me-2">
5
- <label for="job_class_filter">Job class</label>
6
- <select name="job_class" id="job_class_filter" class="form-select">
7
- <option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
8
-
9
- <% filter.job_classes.each do |name, count| %>
10
- <option value="<%= name.to_param %>" <%= "selected='selected'" if params[:job_class] == name %>><%= name %> (<%= count %>)</option>
11
- <% end %>
12
- </select>
13
- </div>
1
+ <div data-gj-poll-replace id="filter">
2
+ <div class="bg-light break-out">
3
+ <h2 class="container-fluid pt-3 pb-2"><%= title %></h2>
14
4
 
15
- <div class="me-2">
16
- <label for="job_state_filter">State</label>
17
- <select name="state" id="job_state_filter" class="form-select">
18
- <option value="" <%= "selected='selected'" if params[:state].blank? %>>All states</option>
5
+ <ul class="nav nav-tabs bg-light px-3 mb-3">
6
+ <li class="nav-item">
7
+ <%= link_to "All", url_for(state: nil), class: "nav-link #{"active" unless params[:state]}" %>
8
+ </li>
19
9
 
20
- <% filter.states.each do |name, count| %>
21
- <option value="<%= name.to_param %>" <%= "selected='selected'" if params[:state] == name %>><%= name %> (<%= count %>)</option>
22
- <% end %>
23
- </select>
24
- </div>
10
+ <% filter.states.each do |name, count| %>
11
+ <li class="nav-item">
12
+ <%= link_to url_for({state: name}), class: "nav-link #{"active" if params[:state] == name}" do %>
13
+ <%= name.titleize %>
14
+ <span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= count %></span>
15
+ <% end %>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ </div>
25
20
 
26
- <div class="me-2">
27
- <label for="job_queue_filter">Queue</label>
28
- <select name="queue_name" id="job_queue_filter" class="form-select">
29
- <option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
21
+ <%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "container-fluid") do |form| %>
22
+ <%= hidden_field_tag :poll, params[:poll] %>
23
+ <%= hidden_field_tag :state, params[:state] %>
24
+ <div class="d-flex flex-row w-100">
25
+ <div class="me-2">
26
+ <select name="job_class" id="job_class_filter" class="form-select form-select-sm">
27
+ <option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
30
28
 
31
- <% filter.queues.each do |name, count| %>
32
- <option value="<%= name.to_param %>" <%= "selected='selected'" if params[:queue_name] == name %>><%= name %> (<%= count %>)</option>
33
- <% end %>
34
- </select>
35
- </div>
29
+ <% filter.job_classes.each do |name, count| %>
30
+ <option value="<%= name.to_param %>" <%= "selected='selected'" if params[:job_class] == name %>><%= name %> (<%= count %>)</option>
31
+ <% end %>
32
+ </select>
33
+ </div>
36
34
 
37
- <div class="me-2 flex-fill d-flex flex-col align-items-end">
38
- <label class="visually-hidden" for="query" aria-label="Search by class, job id, job params, and error text.">Search by class, job id, job params, and error text.</label>
39
- <%= search_field_tag "query", params[:query], class: "form-control", placeholder: "Search by class, job id, job params, and error text." %>
40
- </div>
35
+ <div class="me-2">
36
+ <select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
37
+ <option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
41
38
 
42
- <div class="d-flex flex-col align-items-end">
43
- <div>
44
- <%= form.submit "Search", name: nil, class: "btn btn-primary" %>
45
- <%= link_to "Clear all", filter.to_params(job_class: nil, state: nil, queue_name: nil, query: nil), class: "btn btn-secondary" %>
39
+ <% filter.queues.each do |name, count| %>
40
+ <option value="<%= name.to_param %>" <%= "selected='selected'" if params[:queue_name] == name %>><%= name %> (<%= count %>)</option>
41
+ <% end %>
42
+ </select>
43
+ </div>
44
+
45
+ <div class="me-2 flex-fill">
46
+ <%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: "Search by class, job id, job params, and error text." %>
47
+ </div>
48
+
49
+ <div class="d-flex flex-col align-items-end">
50
+ <div>
51
+ <%= form.submit "Search", name: nil, class: "btn btn-primary btn-sm" %>
52
+ <%= link_to "Clear all", filter.to_params(job_class: nil, state: nil, queue_name: nil, query: nil), class: "btn btn-secondary btn-sm" %>
53
+ </div>
46
54
  </div>
47
55
  </div>
48
- </div>
49
- <% end %>
50
-
51
- <%= javascript_tag nonce: true do %>
52
- document.addEventListener("DOMContentLoaded", () => {
53
- document.querySelectorAll("#job_class_filter, #job_state_filter, #job_queue_filter").forEach((filter) => {
54
- filter.addEventListener("change", () => {
55
- document.querySelector("#filter_form").submit();
56
- });
56
+ <% end %>
57
+
58
+ <%= javascript_tag nonce: true do %>
59
+ document.addEventListener("DOMContentLoaded", () => {
60
+ document.querySelectorAll("#job_class_filter, #job_queue_filter").forEach((filter) => {
61
+ filter.addEventListener("change", () => {
62
+ document.querySelector("#filter_form").submit();
63
+ });
64
+ })
57
65
  })
58
- })
59
- <% end %>
66
+ <% end %>
67
+ </div>
@@ -1,10 +1,8 @@
1
- <footer class="footer mt-auto py-3 bg-light fixed-bottom" id="footer" data-gj-poll-replace>
1
+ <footer class="footer mt-auto py-3 bg-light border-top text-muted small" id="footer" data-gj-poll-replace>
2
2
  <div class="container-fluid">
3
3
  <div class="row">
4
4
  <div class="col-6">
5
- <span class="text-muted">
6
- <%= t(".last_update_html", time: Time.current.utc.iso8601) %>
7
- </span>
5
+ <%= t(".last_update_html", time: Time.current.utc.iso8601) %>
8
6
  </div>
9
7
 
10
8
  <div class="col-6 text-end">
@@ -1,4 +1,4 @@
1
- <nav class="navbar navbar-expand-lg navbar-light bg-light">
1
+ <nav class="navbar navbar-expand-lg navbar-light border-bottom bg-white sticky-top shadow-sm">
2
2
  <div class="container-fluid">
3
3
  <%= link_to t(".name"), root_path, class: "navbar-brand mb-0 h1" %>
4
4
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
@@ -8,21 +8,18 @@
8
8
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
9
9
  <ul class="navbar-nav me-auto">
10
10
  <li class="nav-item">
11
- <%= link_to t(".executions"), root_path, class: ["nav-link", ("active" if current_page?(root_path))] %>
11
+ <%= link_to t(".jobs"), jobs_path, class: ["nav-link", ("active" if controller_name == 'jobs')] %>
12
12
  </li>
13
13
  <li class="nav-item">
14
- <%= link_to t(".jobs"), jobs_path, class: ["nav-link", ("active" if current_page?(jobs_path))] %>
14
+ <%= link_to t(".cron_schedules"), cron_entries_path, class: ["nav-link", ("active" if controller_name == 'cron_entries')] %>
15
15
  </li>
16
16
  <li class="nav-item">
17
- <%= link_to t(".cron_schedules"), cron_entries_path, class: ["nav-link", ("active" if current_page?(cron_entries_path))] %>
18
- </li>
19
- <li class="nav-item">
20
- <%= link_to t(".processes"), processes_path, class: ["nav-link", ("active" if current_page?(processes_path))] %>
17
+ <%= link_to t(".processes"), processes_path, class: ["nav-link", ("active" if controller_name == 'processes')] %>
21
18
  </li>
22
19
  </ul>
23
20
  <div class="nav-item pe-2">
24
21
  <div class="form-check">
25
- <input type="checkbox" id="toggle-poll" name="toggle-poll" data-gj-action='change#togglePoll' <%= 'checked' if params[:poll].present? %>>
22
+ <input type="checkbox" id="toggle-poll" name="toggle-poll" <%= 'checked' if params[:poll].present? %>>
26
23
  <label for="toggle-poll"><%= t(".live_poll") %></label>
27
24
  </div>
28
25
  </div>
@@ -7,25 +7,30 @@
7
7
  <%= csrf_meta_tags %>
8
8
  <%= csp_meta_tag %>
9
9
 
10
- <%# Assets must use *_url route helpers to avoid being overriden by config.asset_host %>
11
- <%= stylesheet_link_tag bootstrap_url(format: :css, v: GoodJob::VERSION), skip_pipeline: true %>
12
- <%= stylesheet_link_tag style_url(format: :css, v: GoodJob::VERSION) %>
10
+ <%# Do not use asset tag helpers to avoid paths being overriden by config.asset_host %>
11
+ <%= tag.link rel: "stylesheet", media: "screen", href: bootstrap_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
12
+ <%= tag.link rel: "stylesheet", media: "screen", href: style_path(format: :css, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
13
13
 
14
- <%= javascript_include_tag bootstrap_url(format: :js, v: GoodJob::VERSION), nonce: true %>
15
- <%= javascript_include_tag chartjs_url(format: :js, v: GoodJob::VERSION), nonce: true %>
16
- <%= javascript_include_tag scripts_url(format: :js, v: GoodJob::VERSION), nonce: true %>
14
+ <%= tag.script "", src: bootstrap_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
15
+ <%= tag.script "", src: chartjs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
16
+ <%= tag.script "", src: rails_ujs_path(format: :js, v: GoodJob::VERSION, locale: nil), nonce: content_security_policy_nonce %>
17
17
 
18
- <%= javascript_include_tag rails_ujs_url(format: :js, v: GoodJob::VERSION), nonce: true %>
18
+ <%= tag.script "", src: es_module_shims_path(format: :js, v: GoodJob::VERSION, locale: nil), async: true, nonce: content_security_policy_nonce %>
19
+ <% importmaps = { imports: GoodJob::AssetsController.js_modules.keys.each_with_object({}) { |module_name, imports| imports[module_name] = modules_path(module_name, format: :js, locale: nil, v: GoodJob::VERSION) } } %>
20
+ <%= tag.script importmaps.to_json.html_safe, type: "importmap", nonce: content_security_policy_nonce %>
21
+ <%= tag.script "", src: scripts_path(format: :js, v: GoodJob::VERSION, locale: nil), type: "module", nonce: content_security_policy_nonce %>
19
22
  </head>
20
23
  <body>
21
- <%= render "good_job/shared/navbar" %>
24
+ <div class="d-flex flex-column min-vh-100">
25
+ <%= render "good_job/shared/navbar" %>
22
26
 
23
- <div class="container-fluid">
24
- <%= render "good_job/shared/alert" %>
27
+ <div class="container-fluid flex-grow-1 relative">
28
+ <%= render "good_job/shared/alert" %>
25
29
 
26
- <%= yield %>
27
- </div>
30
+ <%= yield %>
31
+ </div>
28
32
 
29
33
  <%= render "good_job/shared/footer" %>
34
+ </div>
30
35
  </body>
31
36
  </html>
@@ -45,9 +45,8 @@ en:
45
45
  last_update_html: Last updated <time id="page-updated-at" datetime="%{time}">%{time}</time>
46
46
  wording: Remember, you're doing a Good Job too!
47
47
  navbar:
48
- cron_schedules: Cron Schedules
49
- executions: All Executions
50
- jobs: All Jobs
48
+ cron_schedules: Cron
49
+ jobs: Jobs
51
50
  live_poll: Live Poll
52
51
  name: "GoodJob 👍"
53
52
  processes: Processes
@@ -45,8 +45,7 @@ es:
45
45
  last_update_html: Última actualización <time id="page-updated-at" datetime="%{time}">%{time}</time>
46
46
  wording: "¡Recuerda, también tú estás haciendo un buen trabajo!"
47
47
  navbar:
48
- cron_schedules: Tareas Programadas
49
- executions: Ejecuciones
48
+ cron_schedules: Cron
50
49
  jobs: Tareas
51
50
  live_poll: En vivo
52
51
  name: "GoodJob 👍"
@@ -45,9 +45,8 @@ nl:
45
45
  last_update_html: Laatst bijgewerkt <time id="page-updated-at" datetime="%{time}">%{time}</time>
46
46
  wording: 'Onthoud: jij levert ook goed werk!'
47
47
  navbar:
48
- cron_schedules: Cron Schema
49
- executions: Alle Uitvoeringen
50
- jobs: Alle Taken
48
+ cron_schedules: Cron
49
+ jobs: Taken
51
50
  live_poll: Live Poll
52
51
  name: "GoodJob 👍"
53
52
  processes: Processen
@@ -3,40 +3,64 @@ ru:
3
3
  datetime:
4
4
  distance_in_words:
5
5
  about_x_hours:
6
+ few: около %{count} часов
7
+ many: около %{count} часов
6
8
  one: около 1 часа
7
- other: около %{count} часов
9
+ other: около %{count} часа
8
10
  about_x_months:
11
+ few: около %{count} месяцев
12
+ many: около %{count} месяцев
9
13
  one: около 1 месяца
10
- other: около %{count} месяцев
14
+ other: около %{count} месяца
11
15
  about_x_years:
16
+ few: около %{count} лет
17
+ many: около %{count} лет
12
18
  one: около 1 года
13
19
  other: около %{count} лет
14
20
  almost_x_years:
21
+ few: почти %{count} года
22
+ many: почти %{count} лет
15
23
  one: почти 1 год
16
- other: почти %{count} года
17
- half_a_minute: пол минуты
24
+ other: почти %{count} лет
25
+ half_a_minute: полминуты
18
26
  less_than_x_minutes:
27
+ few: меньше %{count} минут
28
+ many: меньше %{count} минут
19
29
  one: меньше 1 минуты
20
- other: меньше %{count} минут
30
+ other: меньше %{count} минуты
21
31
  less_than_x_seconds:
32
+ few: меньше %{count} секунд
33
+ many: меньше %{count} секунд
22
34
  one: меньше 1 секунды
23
- other: меньше %{count} секунд
35
+ other: меньше %{count} секунды
24
36
  over_x_years:
37
+ few: больше %{count} лет
38
+ many: больше %{count} лет
25
39
  one: больше 1 года
26
40
  other: больше %{count} лет
27
41
  x_days:
42
+ few: "%{count} дня"
43
+ many: "%{count} дней"
28
44
  one: 1 день
29
- other: "%{count} дней"
45
+ other: "%{count} дня"
30
46
  x_minutes:
31
- one: 1 минута
32
- other: "%{count} минут"
47
+ few: "%{count} минуты"
48
+ many: "%{count} минут"
49
+ one: 1 минуту
50
+ other: "%{count} минуты"
33
51
  x_months:
52
+ few: "%{count} месяца"
53
+ many: "%{count} месяцев"
34
54
  one: 1 месяц
35
55
  other: "%{count} месяца"
36
56
  x_seconds:
37
- one: 1 секунда
38
- other: "%{count} секунд"
57
+ few: "%{count} секунды"
58
+ many: "%{count} секунд"
59
+ one: 1 секунду
60
+ other: "%{count} секунды"
39
61
  x_years:
62
+ few: "%{count} года"
63
+ many: "%{count} лет"
40
64
  one: 1 год
41
65
  other: "%{count} года"
42
66
  good_job:
@@ -45,9 +69,8 @@ ru:
45
69
  last_update_html: Последнее обновление <time id="page-updated-at" datetime="%{time}">%{time}</time>
46
70
  wording: Запомни, ты делаешь Good Job тоже!
47
71
  navbar:
48
- cron_schedules: Cron Расписания
49
- executions: Все Исполнения
50
- jobs: Все Задачи
72
+ cron_schedules: Cron
73
+ jobs: Задачи
51
74
  live_poll: Живой Опрос
52
75
  name: "GoodJob 👍"
53
76
  processes: Процессы
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  GoodJob::Engine.routes.draw do
3
- root to: 'executions#index'
3
+ root to: redirect(path: 'jobs')
4
4
 
5
5
  resources :executions, only: %i[destroy]
6
6
 
@@ -20,7 +20,7 @@ GoodJob::Engine.routes.draw do
20
20
 
21
21
  resources :processes, only: %i[index]
22
22
 
23
- scope controller: :assets do
23
+ scope :assets, controller: :assets do
24
24
  constraints(format: :css) do
25
25
  get :bootstrap, action: :bootstrap_css
26
26
  get :style, action: :style_css
@@ -28,8 +28,10 @@ GoodJob::Engine.routes.draw do
28
28
 
29
29
  constraints(format: :js) do
30
30
  get :bootstrap, action: :bootstrap_js
31
- get :rails_ujs, action: :rails_ujs_js
32
31
  get :chartjs, action: :chartjs_js
32
+ get :rails_ujs, action: :rails_ujs_js
33
+ get :es_module_shims, action: :es_module_shims_js
34
+ get "modules/:module", action: :modules_js, as: :modules
33
35
  get :scripts, action: :scripts_js
34
36
  end
35
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.12.2'
4
+ VERSION = '2.13.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: good_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.2
4
+ version: 2.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-18 00:00:00.000000000 Z
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -360,12 +360,18 @@ files:
360
360
  - CHANGELOG.md
361
361
  - LICENSE.txt
362
362
  - README.md
363
- - engine/app/assets/scripts.js
364
- - engine/app/assets/style.css
365
- - engine/app/assets/vendor/bootstrap/bootstrap.bundle.min.js
366
- - engine/app/assets/vendor/bootstrap/bootstrap.min.css
367
- - engine/app/assets/vendor/chartjs/chart.min.js
368
- - engine/app/assets/vendor/rails_ujs.js
363
+ - engine/app/assets/good_job/modules/application.js
364
+ - engine/app/assets/good_job/modules/charts.js
365
+ - engine/app/assets/good_job/modules/document_ready.js
366
+ - engine/app/assets/good_job/modules/poller.js
367
+ - engine/app/assets/good_job/modules/toasts.js
368
+ - engine/app/assets/good_job/scripts.js
369
+ - engine/app/assets/good_job/style.css
370
+ - engine/app/assets/good_job/vendor/bootstrap/bootstrap.bundle.min.js
371
+ - engine/app/assets/good_job/vendor/bootstrap/bootstrap.min.css
372
+ - engine/app/assets/good_job/vendor/chartjs/chart.min.js
373
+ - engine/app/assets/good_job/vendor/es_module_shims.js
374
+ - engine/app/assets/good_job/vendor/rails_ujs.js
369
375
  - engine/app/charts/good_job/scheduled_by_queue_chart.rb
370
376
  - engine/app/controllers/good_job/application_controller.rb
371
377
  - engine/app/controllers/good_job/assets_controller.rb
@@ -374,13 +380,11 @@ files:
374
380
  - engine/app/controllers/good_job/jobs_controller.rb
375
381
  - engine/app/controllers/good_job/processes_controller.rb
376
382
  - engine/app/filters/good_job/base_filter.rb
377
- - engine/app/filters/good_job/executions_filter.rb
378
383
  - engine/app/filters/good_job/jobs_filter.rb
379
384
  - engine/app/helpers/good_job/application_helper.rb
380
385
  - engine/app/views/good_job/cron_entries/index.html.erb
381
386
  - engine/app/views/good_job/cron_entries/show.html.erb
382
387
  - engine/app/views/good_job/executions/_table.erb
383
- - engine/app/views/good_job/executions/index.html.erb
384
388
  - engine/app/views/good_job/jobs/_table.erb
385
389
  - engine/app/views/good_job/jobs/index.html.erb
386
390
  - engine/app/views/good_job/jobs/show.html.erb
@@ -1,133 +0,0 @@
1
- /*jshint esversion: 6, strict: false */
2
- const GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS = 30;
3
- const GOOD_JOB_MINIMUM_POLL_INTERVAL = 1000;
4
-
5
- const GoodJob = {
6
- // Register functions to execute when the DOM is ready
7
- ready: (callback) => {
8
- if (document.readyState !== "loading") {
9
- callback();
10
- } else {
11
- document.addEventListener("DOMContentLoaded", callback);
12
- }
13
- },
14
-
15
- init: () => {
16
- GoodJob.updateSettings();
17
- GoodJob.addListeners();
18
- GoodJob.pollUpdates();
19
- GoodJob.renderCharts(true);
20
- },
21
-
22
- addListeners: () => {
23
- const gjActionEls = document.querySelectorAll('[data-gj-action]');
24
-
25
- for (let i = 0; i < gjActionEls.length; i++) {
26
- const el = gjActionEls[i];
27
- const [eventName, func] = el.dataset.gjAction.split('#');
28
-
29
- el.addEventListener(eventName, GoodJob[func]);
30
- }
31
- },
32
-
33
- updateSettings: () => {
34
- const queryString = window.location.search;
35
- const urlParams = new URLSearchParams(queryString);
36
-
37
- // live poll interval and enablement
38
- if (urlParams.has('poll')) {
39
- const parsedInterval = (parseInt(urlParams.get('poll')) || GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS) * 1000;
40
- GoodJob.pollInterval = Math.max(parsedInterval, GOOD_JOB_MINIMUM_POLL_INTERVAL);
41
- GoodJob.setStorage('pollInterval', GoodJob.pollInterval);
42
-
43
- GoodJob.pollEnabled = true;
44
- } else {
45
- GoodJob.pollInterval = GoodJob.getStorage('pollInterval') || (GOOD_JOB_DEFAULT_POLL_INTERVAL_SECONDS * 1000);
46
- GoodJob.pollEnabled = GoodJob.getStorage('pollEnabled') || false;
47
- }
48
-
49
- document.getElementById('toggle-poll').checked = GoodJob.pollEnabled;
50
- },
51
-
52
- togglePoll: (ev) => {
53
- GoodJob.pollEnabled = ev.currentTarget.checked;
54
- GoodJob.setStorage('pollEnabled', GoodJob.pollEnabled);
55
- },
56
-
57
- pollUpdates: () => {
58
- setTimeout(() => {
59
- if (GoodJob.pollEnabled === true) {
60
- fetch(window.location.href)
61
- .then(resp => resp.text())
62
- .then(GoodJob.updateContent)
63
- .finally(GoodJob.pollUpdates);
64
- } else {
65
- GoodJob.pollUpdates();
66
- }
67
- }, GoodJob.pollInterval);
68
- },
69
-
70
- updateContent: (newContent) => {
71
- const domParser = new DOMParser();
72
- const parsedDOM = domParser.parseFromString(newContent, "text/html");
73
-
74
- const newElements = parsedDOM.querySelectorAll('[data-gj-poll-replace]');
75
-
76
- for (let i = 0; i < newElements.length; i++) {
77
- const newEl = newElements[i];
78
- const oldEl = document.getElementById(newEl.id);
79
-
80
- if (oldEl) {
81
- oldEl.replaceWith(newEl);
82
- }
83
- }
84
-
85
- GoodJob.renderCharts(false);
86
- },
87
-
88
- renderCharts: (animate) => {
89
- const charts = document.querySelectorAll('.chart');
90
-
91
- for (let i = 0; i < charts.length; i++) {
92
- const chartEl = charts[i];
93
- const chartData = JSON.parse(chartEl.dataset.json);
94
-
95
- const ctx = chartEl.getContext('2d');
96
- const chart = new Chart(ctx, {
97
- type: 'line',
98
- data: {
99
- labels: chartData.labels,
100
- datasets: chartData.datasets
101
- },
102
- options: {
103
- animation: animate,
104
- responsive: true,
105
- maintainAspectRatio: false,
106
- scales: {
107
- y: {
108
- beginAtZero: true
109
- }
110
- }
111
- }
112
- });
113
- }
114
- },
115
-
116
- getStorage: (key) => {
117
- const value = localStorage.getItem('good_job-' + key);
118
-
119
- if (value === 'true') {
120
- return true;
121
- } else if (value === 'false') {
122
- return false;
123
- } else {
124
- return value;
125
- }
126
- },
127
-
128
- setStorage: (key, value) => {
129
- localStorage.setItem('good_job-' + key, value);
130
- }
131
- };
132
-
133
- GoodJob.ready(GoodJob.init);
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
- module GoodJob
3
- class ExecutionsFilter < BaseFilter
4
- def states
5
- @_states ||= {
6
- 'finished' => base_query.finished.count,
7
- 'unfinished' => base_query.unfinished.count,
8
- 'running' => base_query.running.count,
9
- 'errors' => base_query.where.not(error: nil).count,
10
- }
11
- end
12
-
13
- def filtered_query
14
- query = base_query
15
- query = query.job_class(params[:job_class]) if params[:job_class].present?
16
- query = query.where(queue_name: params[:queue_name]) if params[:queue_name].present?
17
- query = query.search_text(params[:query]) if params[:query].present?
18
-
19
- if params[:state]
20
- case params[:state]
21
- when 'finished'
22
- query = query.finished
23
- when 'unfinished'
24
- query = query.unfinished
25
- when 'running'
26
- query = query.running
27
- when 'errors'
28
- query = query.where.not(error: nil)
29
- end
30
- end
31
-
32
- query
33
- end
34
-
35
- private
36
-
37
- def default_base_query
38
- GoodJob::Execution.all
39
- end
40
- end
41
- end
@@ -1,19 +0,0 @@
1
- <div class="card my-3 p-6" data-gj-poll-replace id="executions-chart">
2
- <%= render 'good_job/shared/chart', chart_data: GoodJob::ScheduledByQueueChart.new(@filter).data %>
3
- </div>
4
-
5
- <%= render 'good_job/shared/filter', filter: @filter %>
6
-
7
- <%= render 'good_job/executions/table', executions: @filter.records %>
8
-
9
- <% if @filter.records.present? %>
10
- <nav aria-label="Job pagination" class="mt-3" data-gj-poll-replace id="executions-pagination">
11
- <ul class="pagination">
12
- <li class="page-item">
13
- <%= link_to({ after_scheduled_at: (@filter.last.scheduled_at || @filter.last.created_at), after_id: @filter.last.id }, class: "page-link") do %>
14
- Older executions <span aria-hidden="true">&raquo;</span>
15
- <% end %>
16
- </li>
17
- </ul>
18
- </nav>
19
- <% end %>