#125 coreos-download: clean up AWS cloud launchable regions
Merged 4 months ago by dustymabe. Opened 4 months ago by abai.
fedora-web/ abai/websites coreos-download  into  master

file modified
+281 -205
@@ -79,13 +79,13 @@ 

  function getDownloadsFromFormat(formatData, downloads) {

    for (var download in formatData) {

      downloadData = formatData[download];

-     entry = {location: downloadData.location, signature: downloadData.signature, sha256: downloadData.sha256};

+     entry = { location: downloadData.location, signature: downloadData.signature, sha256: downloadData.sha256 };

      downloads[download] = entry;

    }

  }

  var coreos_download_app = new Vue({

    el: '#coreos-download-app',

-   created: function() { this.refreshStream() },

+   created: function () { this.refreshStream() },

    data: {

      // currently shown tab

      shownId: IdPool.cloud_launchable,
@@ -109,10 +109,10 @@ 

      },

    },

    watch: {

-     stream: function() {

+     stream: function () {

        this.refreshStream();

      },

-     loading: function() {

+     loading: function () {

        // Scrolls the view to navigation bar after user has clicked one of the download button

        // This needs to watch loading variable since the whole page is re-rendered

        Vue.nextTick(function () {
@@ -125,7 +125,7 @@ 

        });

      },

      // watching nested data: https://stackoverflow.com/a/46331968

-     "streamDataAll.stable": function(newVal, oldVal) {

+     "streamDataAll.stable": function (newVal, oldVal) {

        if (isEmptyObj(this.streamDataAll.stable)) {

          return

        }
@@ -137,7 +137,7 @@ 

        $("#stable-json").append(`<a class="text-gray-600" href="${baseUrl}/stable.json">JSON</a>`);

        $("#stable-json").append(` — <span>${this.timeSince(this.streamDataAll.stable.metadata['last-modified'])}</span>`);

      },

-     "streamDataAll.testing": function(newVal, oldVal) {

+     "streamDataAll.testing": function (newVal, oldVal) {

        if (isEmptyObj(this.streamDataAll.testing)) {

          return

        }
@@ -149,7 +149,7 @@ 

        $("#testing-json").append(`<a class="text-gray-600" href="${baseUrl}/testing.json">JSON</a>`);

        $("#testing-json").append(` — <span>${this.timeSince(this.streamDataAll.testing.metadata['last-modified'])}</span>`);

      },

-     "streamDataAll.next": function(newVal, oldVal) {

+     "streamDataAll.next": function (newVal, oldVal) {

        if (isEmptyObj(this.streamDataAll.next)) {

          return

        }
@@ -163,11 +163,11 @@ 

      }

    },

    methods: {

-     getObjectUrl: function(path) {

+     getObjectUrl: function (path) {

        return getArtifactUrl(this.streamUrl, path);

      },

      // Adapted from https://stackoverflow.com/a/6109105

-     timeSince: function(rfc3339_timestamp) {

+     timeSince: function (rfc3339_timestamp) {

        var current = Date.now();

        var timestamp = Date.parse(rfc3339_timestamp);

        var elapsed = current - timestamp;
@@ -180,24 +180,24 @@ 

          return n + ` ${s}` + (n == 1 ? "" : "s") + ' ago';

        };

        if (elapsed < msPerMinute) {

-         return stringize(Math.floor(elapsed/1000), "second");

+         return stringize(Math.floor(elapsed / 1000), "second");

        } else if (elapsed < msPerHour) {

-         return stringize(Math.floor(elapsed/msPerMinute), "minute");

+         return stringize(Math.floor(elapsed / msPerMinute), "minute");

        } else if (elapsed < msPerDay) {

-         return stringize(Math.floor(elapsed/msPerHour), "hour");

+         return stringize(Math.floor(elapsed / msPerHour), "hour");

        } else if (elapsed < msPerMonth) {

-         return stringize(Math.floor(elapsed/msPerDay), "day");

+         return stringize(Math.floor(elapsed / msPerDay), "day");

        } else if (elapsed < msPerYear) {

-         return stringize(Math.floor(elapsed/msPerMonth), "month");

+         return stringize(Math.floor(elapsed / msPerMonth), "month");

        } else {

-         return stringize(Math.floor(elapsed/msPerYear), "year");

+         return stringize(Math.floor(elapsed / msPerYear), "year");

        }

      },

      // Callback function for the navigation bar

      // Effects:

      // - hides the other tabs other than the clicked one

      // - replace the current URL parameter with the clicked one

-     toggleHidden: function(e) {

+     toggleHidden: function (e) {

        const idList = Object.values(IdPool);

        Object.entries(tabInnerText).map(pair => {

          const key = pair[0];
@@ -212,24 +212,24 @@ 

        });

      },

      // Render a navbar section

-     getNavbar: function(h) {

+     getNavbar: function (h) {

        cloudIcon = h('i', { class: "fas fa-cloud mr-2" })

-       navCloudLaunchableBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.cloud_launchable ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloudIcon, tabInnerText.cloud_launchable ]);

-       navCloudLaunchable = h('li', { class: "nav-item col-12 col-sm-4" }, [ navCloudLaunchableBtn ]);

+       navCloudLaunchableBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.cloud_launchable ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [cloudIcon, tabInnerText.cloud_launchable]);

+       navCloudLaunchable = h('li', { class: "nav-item col-12 col-sm-4" }, [navCloudLaunchableBtn]);

  

        serverIcon = h('i', { class: "fas fa-server mr-2" })

-       navMetalVirtBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.metal_virtualized ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ serverIcon, tabInnerText.metal_virtualized ]);

-       navMetalVirt = h('li', { class: "nav-item col-12 col-sm-4" }, [ navMetalVirtBtn ]);

+       navMetalVirtBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.metal_virtualized ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [serverIcon, tabInnerText.metal_virtualized]);

+       navMetalVirt = h('li', { class: "nav-item col-12 col-sm-4" }, [navMetalVirtBtn]);

  

        cloudUploadIcon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })

-       navCloudOperatorsBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.cloud_operators ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloudUploadIcon, tabInnerText.cloud_operators ]);

-       navCloudOperators = h('li', { class: "nav-item col-12 col-sm-4" }, [ navCloudOperatorsBtn ]);

+       navCloudOperatorsBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.shownId === IdPool.cloud_operators ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [cloudUploadIcon, tabInnerText.cloud_operators]);

+       navCloudOperators = h('li', { class: "nav-item col-12 col-sm-4" }, [navCloudOperatorsBtn]);

  

-       navbar = h('ul', { class: "nav nav-tabs mt-3" }, [ navCloudLaunchable, navMetalVirt, navCloudOperators ]);

+       navbar = h('ul', { class: "nav nav-tabs mt-3" }, [navCloudLaunchable, navMetalVirt, navCloudOperators]);

        return navbar;

      },

      // Introduction section for streams, the section above the navigation bar for platforms

-     getStreamIntro: function(h) {

+     getStreamIntro: function (h) {

  

        function onClick(e) {

          e.preventDefault();
@@ -260,7 +260,7 @@ 

          class: "fas fa-shield-alt fa-2x rounded-circle bg-fedora-blue text-white p-3 ml-4",

        }, "");

        stableHeading = h("h3", { class: "font-weight-light" }, "Stable");

-       stableReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "stable-version" }}, isEmptyObj(this.streamDataAll.stable) ? "" : "v " + this.streamDataAll.stable.architectures.x86_64.artifacts.metal.release);

+       stableReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "stable-version" } }, isEmptyObj(this.streamDataAll.stable) ? "" : "v " + this.streamDataAll.stable.architectures.x86_64.artifacts.metal.release);

        stableJSON = h('p', { class: "text-gray-500", attrs: { id: "stable-json" } }, [

          h('span', {}, [

            h('a', { class: "font-weight-bold text-gray-500", attrs: { href: `${baseUrl}/stable.json` } }, "JSON")
@@ -268,26 +268,26 @@ 

          (isEmptyObj(this.streamDataAll.stable)) ? null : " — ",

          (isEmptyObj(this.streamDataAll.stable)) ? null : h('span', { class: "font-weight-normal" }, this.timeSince(this.streamDataAll.stable.metadata['last-modified']))

        ]);

-       stableIconContainer = h("div", { class: "col-4" }, [ stableIcon ])

-       stableReleaseJSONContainer = h("div", { class: "col-8" }, [ stableHeading, stableReleaseVersion, stableJSON ])

-       stableHeadingContainer = h("div", { class: "row" }, [ stableIconContainer, stableReleaseJSONContainer ])

+       stableIconContainer = h("div", { class: "col-4" }, [stableIcon])

+       stableReleaseJSONContainer = h("div", { class: "col-8" }, [stableHeading, stableReleaseVersion, stableJSON])

+       stableHeadingContainer = h("div", { class: "row" }, [stableIconContainer, stableReleaseJSONContainer])

  

        stableIntroText = h("p", { class: "pl-3 pr-2", style: { height: "9em" } }, "The Stable stream should be used by production clusters. Versions of Fedora CoreOS are battle-tested within the Testing and Next streams before being promoted.");

        stableReleaseLink = h('button',

-       {

-         class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-blue",

-         attrs: { id: "stable" },

-         on: {

-           click: onClick

-         }

-       }, "Show Downloads");

+         {

+           class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-blue",

+           attrs: { id: "stable" },

+           on: {

+             click: onClick

+           }

+         }, "Show Downloads");

  

        // then Testing stream

        testingIcon = h("i", {

          class: "fas fa-flask fa-2x rounded-circle bg-fedora-green text-white p-3 ml-4",

        }, "");

        testingHeading = h("h3", { class: "font-weight-light" }, "Testing");

-       testingReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "testing-version" }}, isEmptyObj(this.streamDataAll.testing) ? "" : "v " + this.streamDataAll.testing.architectures.x86_64.artifacts.metal.release);

+       testingReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "testing-version" } }, isEmptyObj(this.streamDataAll.testing) ? "" : "v " + this.streamDataAll.testing.architectures.x86_64.artifacts.metal.release);

        testingJSON = h('p', { class: "text-gray-500", attrs: { id: "testing-json" } }, [

          h('span', {}, [

            h('a', { class: "font-weight-bold text-gray-500", attrs: { href: `${baseUrl}/testing.json` } }, "JSON")
@@ -295,26 +295,26 @@ 

          (isEmptyObj(this.streamDataAll.testing)) ? null : " — ",

          (isEmptyObj(this.streamDataAll.testing)) ? null : h('span', { class: "font-weight-normal" }, this.timeSince(this.streamDataAll.testing.metadata['last-modified']))

        ]);

-       testingIconContainer = h("div", { class: "col-4" }, [ testingIcon ])

-       testingReleaseJSONContainer = h("div", { class: "col-8" }, [ testingHeading, testingReleaseVersion, testingJSON ])

-       testingHeadingContainer = h("div", { class: "row" }, [ testingIconContainer, testingReleaseJSONContainer ])

+       testingIconContainer = h("div", { class: "col-4" }, [testingIcon])

+       testingReleaseJSONContainer = h("div", { class: "col-8" }, [testingHeading, testingReleaseVersion, testingJSON])

+       testingHeadingContainer = h("div", { class: "row" }, [testingIconContainer, testingReleaseJSONContainer])

  

        testingIntroText = h("p", { class: "pl-3 pr-2", style: { height: "9em" } }, "The Testing stream consists of promoted Next releases. Mix a few Testing machines into your production clusters to catch any bugs specific to your hardware or configuration.");

        testingReleaseLink = h('button',

-       {

-         class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-green",

-         attrs: { id: "testing" },

-         on: {

-           click: onClick

-         }

-       }, "Show Downloads");

+         {

+           class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-green",

+           attrs: { id: "testing" },

+           on: {

+             click: onClick

+           }

+         }, "Show Downloads");

  

        // then Next stream

        nextIcon = h("i", {

          class: "fas fa-layer-group fa-2x rounded-circle bg-fedora-orange text-white p-3 ml-4",

        }, "");

        nextHeading = h("h3", { class: "font-weight-light" }, "Next");

-       nextReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "next-version" }}, isEmptyObj(this.streamDataAll.next) ? "" : "v " + this.streamDataAll.next.architectures.x86_64.artifacts.metal.release);

+       nextReleaseVersion = h("h6", { class: "text-gray-500 mb-0", attrs: { id: "next-version" } }, isEmptyObj(this.streamDataAll.next) ? "" : "v " + this.streamDataAll.next.architectures.x86_64.artifacts.metal.release);

        nextJSON = h('p', { class: "text-gray-500", attrs: { id: "next-json" } }, [

          h('span', {}, [

            h('a', { class: "font-weight-bold text-gray-500", attrs: { href: `${baseUrl}/next.json` } }, "JSON")
@@ -322,20 +322,20 @@ 

          (isEmptyObj(this.streamDataAll.next)) ? null : " — ",

          (isEmptyObj(this.streamDataAll.next)) ? null : h('span', { class: "font-weight-normal" }, this.timeSince(this.streamDataAll.next.metadata['last-modified']))

        ]);

-       nextIconContainer = h("div", { class: "col-4" }, [ nextIcon ])

-       nextReleaseJSONContainer = h("div", { class: "col-8" }, [ nextHeading, nextReleaseVersion, nextJSON ])

-       nextHeadingContainer = h("div", { class: "row" }, [ nextIconContainer, nextReleaseJSONContainer ])

+       nextIconContainer = h("div", { class: "col-4" }, [nextIcon])

+       nextReleaseJSONContainer = h("div", { class: "col-8" }, [nextHeading, nextReleaseVersion, nextJSON])

+       nextHeadingContainer = h("div", { class: "row" }, [nextIconContainer, nextReleaseJSONContainer])

  

  

        nextIntroText = h("p", { class: "pl-3 pr-2", style: { height: "9em" } }, "The Next stream closely tracks current development work and is released frequently. The newest versions of the Linux kernel, Systemd, and other components will be available for testing.");

        nextReleaseLink = h('button',

-       {

-         class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-orange",

-         attrs: { id: "next" },

-         on: {

-           click: onClick

-         }

-       }, "Show Downloads");

+         {

+           class: "d-block mx-auto mb-4 py-1 px-3 btn btn-sm btn-fedora-orange",

+           attrs: { id: "next" },

+           on: {

+             click: onClick

+           }

+         }, "Show Downloads");

  

        stableDiv = h('div', {

          class: "col-12 col-lg-4 border-left border-fedora-blue pt-3",
@@ -373,7 +373,7 @@ 

          h('a', {

            class: "font-weight-bold text-gray-500",

            attrs: {

-             href: overviewPageUrl

+             href: `${overviewPageUrl}?stream=${coreos_download_app.stream}`

            }

          }, `View Releases`),

          `)`
@@ -382,25 +382,25 @@ 

        wrapperDiv = h('div', {}, [title, streamsIntroDiv, displayedStreamTitle]);

        return wrapperDiv;

      },

-     isAws: function(platform) {

+     isAws: function (platform) {

        return platform == "aws";

      },

-     isGcp: function(platform) {

+     isGcp: function (platform) {

        return platform == "gcp";

      },

-     isVirtualizedImage: function(platform) {

+     isVirtualizedImage: function (platform) {

        return virtualizedImages.includes(platform);

      },

-     isCloudImage: function(platform) {

+     isCloudImage: function (platform) {

        return cloudImages.includes(platform);

      },

-     isBareMetalImage: function(platform) {

+     isBareMetalImage: function (platform) {

        return platform == "metal";

      },

      // Load stream information to display. Note that `loadStreamDisplay` does

      // not deep-copy information from `streamData` or elsewhere into

      // `streamDisplay`.

-     loadStreamDisplay: function() {

+     loadStreamDisplay: function () {

        this.streamDisplay = {

          cloudLaunchable: {},

          bareMetal: {},
@@ -431,36 +431,36 @@ 

              for (var region in regions) {

                const release = getMember(regions[region], "release");

                const image = getMember(regions[region], "image");

-               displayEntries.push({platform: prettyPlatform, region: region, release: release, image: image});

+               displayEntries.push({ platform: prettyPlatform, region: region, release: release, image: image });

              }

              // put 'us', 'eu', and 'ap' first since those are the cheapest and

              // most popular; then everything else

              const continentOrdering = ["us", "eu", "ap"];

-             displayEntries = displayEntries.sort(function(a, b) {

-                 const aIdx = continentOrdering.indexOf(a.region.slice(0, 2));

-                 const bIdx = continentOrdering.indexOf(b.region.slice(0, 2));

-                 if (aIdx == bIdx) {

-                     return a.region.localeCompare(b.region);

-                 } else if (aIdx == -1) {

-                     return 1;

-                 } else if (bIdx == -1){

-                     return -1;

-                 } else {

-                     return aIdx - bIdx;

-                 }

+             displayEntries = displayEntries.sort(function (a, b) {

+               const aIdx = continentOrdering.indexOf(a.region.slice(0, 2));

+               const bIdx = continentOrdering.indexOf(b.region.slice(0, 2));

+               if (aIdx == bIdx) {

+                 return a.region.localeCompare(b.region);

+               } else if (aIdx == -1) {

+                 return 1;

+               } else if (bIdx == -1) {

+                 return -1;

+               } else {

+                 return aIdx - bIdx;

+               }

              });

            }

-           Vue.set(this.streamDisplay.cloudLaunchable, platform, {list: displayEntries});

+           Vue.set(this.streamDisplay.cloudLaunchable, platform, { list: displayEntries });

          }

          else if (this.isGcp(platform)) {

            const name = getMember(images[platform], "name");

            const family = getMember(images[platform], "family");

            const project = getMember(images[platform], "project");

-           Vue.set(this.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, name, family, project});

+           Vue.set(this.streamDisplay.cloudLaunchable, platform, { platform: prettyPlatform, name, family, project });

          }

          else {

            const image = getMember(images[platform], "image");

-           Vue.set(this.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});

+           Vue.set(this.streamDisplay.cloudLaunchable, platform, { platform: prettyPlatform, image: image });

          }

        }

        const artifacts = getMember(architectureData, "artifacts");
@@ -475,10 +475,10 @@ 

            // in the case where each individual format has a separate pretty

            // name, we want the artifacts listed in alphabetical order

            for (var format in formats) {

-               pretty = getPrettyPlatform(getPrettyPlatform(platform, format));

-               prettyFormats.push({format: format, pretty: pretty});

+             pretty = getPrettyPlatform(getPrettyPlatform(platform, format));

+             prettyFormats.push({ format: format, pretty: pretty });

            }

-           prettyFormats.sort(function(a, b) { return a.pretty.localeCompare(b.pretty); });

+           prettyFormats.sort(function (a, b) { return a.pretty.localeCompare(b.pretty); });

            for (i = 0; i < prettyFormats.length; i++) {

              const format = prettyFormats[i].format;

              const prettyPlatform = prettyFormats[i].pretty;
@@ -491,7 +491,7 @@ 

              function addDisplayEntry(display, platform, format, formats, release, prettyPlatform, extension) {

                downloads = {};

                getDownloadsFromFormat(formats[format], downloads);

-               displayEntry = {platform: prettyPlatform, release: release, downloads: downloads, extension: extension};

+               displayEntry = { platform: prettyPlatform, release: release, downloads: downloads, extension: extension };

                Vue.set(display, platform + "-" + format, displayEntry);

              }

              if (this.isCloudImage(platform)) {
@@ -508,46 +508,46 @@ 

        }

      },

      // Load all stream metadata for rendering

-     refreshStream: function() {

+     refreshStream: function () {

        const self = this;

        self.loading = true

        self.streamUrl = baseUrl

        fetchStreamData(baseUrl, "stable")

-       .then(streamData => {

-         self.streamDataAll.stable = streamData;

-         return fetchStreamData(baseUrl, "testing");

-       })

-       .then(streamData => {

-         self.streamDataAll.testing = streamData;

-         return fetchStreamData(baseUrl, "next");

-       })

-       .then(streamData => {

-         self.streamDataAll.next = streamData;

-         return;

-       })

-       .then(() =>{

-         const streamData = self.streamDataAll[self.stream];

-         self.loading = false;

-         self.streamData = isEmptyObj(streamData) ? null : streamData;

-         self.loadStreamDisplay();

-       })

+         .then(streamData => {

+           self.streamDataAll.stable = streamData;

+           return fetchStreamData(baseUrl, "testing");

+         })

+         .then(streamData => {

+           self.streamDataAll.testing = streamData;

+           return fetchStreamData(baseUrl, "next");

+         })

+         .then(streamData => {

+           self.streamDataAll.next = streamData;

+           return;

+         })

+         .then(() => {

+           const streamData = self.streamDataAll[self.stream];

+           self.loading = false;

+           self.streamData = isEmptyObj(streamData) ? null : streamData;

+           self.loadStreamDisplay();

+         })

      },

      // Render the `Verify signature & SHA256` modal template

-     getSignatureAndShaModal: function(h) {

-       return h('div', { class: "modal", attrs: { id: "signatureAndShaModal", tabindex: "-1", role: "dialog", "aria-labelledby": "signatureAndShaModalLabel", "aria-hidden": "true" }}, [

-         h('div', { class: "modal-dialog modal-lg modal-dialog-centered", attrs: { role: "document" }}, [

+     getSignatureAndShaModal: function (h) {

+       return h('div', { class: "modal", attrs: { id: "signatureAndShaModal", tabindex: "-1", role: "dialog", "aria-labelledby": "signatureAndShaModalLabel", "aria-hidden": "true" } }, [

+         h('div', { class: "modal-dialog modal-lg modal-dialog-centered", attrs: { role: "document" } }, [

            h('div', { class: "modal-content" }, [

              h('div', { class: "modal-header" }, [

-               h('h5', { class: "modal-title", attrs: { id: "signatureAndShaModalTitle" }}, [

+               h('h5', { class: "modal-title", attrs: { id: "signatureAndShaModalTitle" } }, [

                  "Verify signature & SHA256"

                ]),

-               h('button', { class: "close", attrs: { type: "button", "data-dismiss": "modal", "aria-label": "Close" }}, [

-                 h('span', { attrs: { "aria-hidden": "true" }}, [ "×" ])

+               h('button', { class: "close", attrs: { type: "button", "data-dismiss": "modal", "aria-label": "Close" } }, [

+                 h('span', { attrs: { "aria-hidden": "true" } }, ["×"])

                ])

              ]),

-             h('div', { class: "modal-body", attrs: { id: "modal-body" }}, [ "Loading..." ]),

+             h('div', { class: "modal-body", attrs: { id: "modal-body" } }, ["Loading..."]),

              h('div', { class: "modal-footer" }, [

-               h('button', { class: "btn btn-secondary", attrs: { type: "button", "data-dismiss": "modal" }}, [

+               h('button', { class: "btn btn-secondary", attrs: { type: "button", "data-dismiss": "modal" } }, [

                  "Close"

                ])

              ])
@@ -556,7 +556,7 @@ 

        ])

      }

    },

-   render: function(h) {

+   render: function (h) {

      if (window.location.href.match(/^.*\/coreos\/download/) == null) {

        return

      }
@@ -564,7 +564,7 @@ 

      searchParams = new URLSearchParams(window.location.search);

      // switch to specified tab if `tab` parameter is set

      if (searchParams.has('tab')) {

-       switch(searchParams.get('tab')) {

+       switch (searchParams.get('tab')) {

          case 'cloud_launchable':

            this.shownId = IdPool.cloud_launchable;

            break;
@@ -582,7 +582,7 @@ 

      }

      // switch to specified stream if `stream` parameter is set

      if (searchParams.has('stream')) {

-       switch(searchParams.get('stream')) {

+       switch (searchParams.get('stream')) {

          case 'stable':

            this.stream = "stable";

            break;
@@ -603,85 +603,161 @@ 

  

      var signatureSha256VerificationModal = this.getSignatureAndShaModal(h);

      h1Title = h('h1', { class: "font-weight-light text-center my-5" }, "Download Fedora CoreOS");

-     streamSelectContainer = h('div', { class: "pb-0 mb-3" }, [ this.getStreamIntro(h), this.getNavbar(h) ]);

+     streamSelectContainer = h('div', { class: "pb-0 mb-3" }, [this.getStreamIntro(h), this.getNavbar(h)]);

      if (this.loading) {

-       streamInfoDiv = h('div', { class: "bg-light" }, [ h('div', { class: "container font-weight-light" }, [ streamSelectContainer ]) ]);

-       downloadDiv = h('div', { class: "bg-white pb-5" }, [ h('div', { class: "container font-weight-light" }, "Loading...") ]);

-       return h('div', {}, [ h1Title, streamInfoDiv, downloadDiv ]);

+       streamInfoDiv = h('div', { class: "bg-light" }, [h('div', { class: "container font-weight-light" }, [streamSelectContainer])]);

+       downloadDiv = h('div', { class: "bg-white pb-5" }, [h('div', { class: "container font-weight-light" }, "Loading...")]);

+       return h('div', {}, [h1Title, streamInfoDiv, downloadDiv]);

      }

      else if (this.streamData) {

        cloudLaunchableSection = {};

        cloudLaunchable = {};

-       virtualizedTitle = h('h3', { class:"font-weight-light" }, "Virtualized");

+       virtualizedTitle = h('h3', { class: "font-weight-light" }, "Virtualized");

        virtualizedSection = {};

        virtualized = {};

-       bareMetalTitle = h('h3', { class:"font-weight-light" }, "Bare Metal");

+       bareMetalTitle = h('h3', { class: "font-weight-light" }, "Bare Metal");

        bareMetalSection = {};

        bareMetal = {};

        cloudSection = {};

        cloud = {};

  

        if (this.streamDisplay.cloudLaunchable) {

-         cloudLaunchableSection = h('div', {}, Object.entries(this.streamDisplay.cloudLaunchable).map(function(entry) {

+         cloudLaunchableSection = Object.entries(this.streamDisplay.cloudLaunchable).map(function (entry) {

            platform = entry[0];

            displayInfo = entry[1];

            if (coreos_download_app.isAws(platform)) {

              if (displayInfo.list) {

-               return h('div', {}, displayInfo.list.map(function(amiInfo) {

-                 return h('div', { class: "p-2 m-2" }, [

-                   amiInfo.platform ? h('div', { class: "font-weight-bold" }, amiInfo.platform) : null,

-                   amiInfo.region ? h('div', {}, [ "(", amiInfo.region, ")" ]) : null,

-                   amiInfo.release ? h('div', { class: "ml-2" }, [

-                     h('span', {}, [ amiInfo.release, " " ]),

-                     h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)

-                   ]) : null,

-                   amiInfo.image ? h('div', { class: "ml-2" }, [

-                     h('a', {

-                       attrs: {

-                         href: "https://console.aws.amazon.com/ec2/home?region=" + amiInfo.region + "#launchAmi=" + amiInfo.image

-                       }

-                     }, amiInfo.image)

-                   ]) : null

-                 ])

-               }));

+               const toggleRegion = function (e) {

+                 e.preventDefault();

+                 let awsCloudLaunchableDiv = document.getElementById("aws-cloud-launchables");

+                 awsCloudLaunchableDiv.childNodes.forEach(childNode => {

+                   if (childNode.id == null || childNode.id.length == 0) return;

+                   if (childNode.id == e.target.innerText) {

+                     childNode.hidden = false;

+                   } else {

+                     childNode.hidden = true;

+                   }

+                 });

+               };

+               return h('div', { class: "col-12 col-lg-6", attrs: { id: "aws-cloud-launchables" } }, [

+                 h('div', { class: "px-2 mx-2 pt-2 mt-2 pb-0 mb-0" }, [

+                   h('div', { class: "font-weight-bold" }, getPrettyPlatform(platform, null)),

+                   h('span', { class: "text-secondary" }, [

+                     coreos_download_app.streamData.stream,

+                     h('div', { class: "dropdown" }, [

+                       h('a', {

+                         class: "dropdown-toggle",

+                         attrs: {

+                           "href": "#",

+                           "role": "button",

+                           "data-toggle": "dropdown",

+                           "aria-haspopup": true,

+                           "aria-expanded": false

+                         },

+                         on: {

+                           click: function (e) {

+                             e.preventDefault();

+                           }

+                         }

+                       }, "Regions"),

+                       h('div', { class: "dropdown-menu" }, [

+                         h('div', { class: "container" }, [

+                           h('div', { class: "row" }, [

+                             // display region list in two columns

+                             h('div', { class: "col-12 col-sm-6 border-right px-0" }, [

+                               displayInfo.list.slice(0, Math.ceil(displayInfo.list.length / 2)).map(amiInfo => {

+                                 if (amiInfo.region == null || amiInfo.region.length == 0) return;

+                                 return h('a', {

+                                   class: "dropdown-item",

+                                   attrs: {

+                                     href: "#",

+                                   },

+                                   on: {

+                                     click: toggleRegion

+                                   }

+                                 },

+                                   amiInfo.region);

+                               })

+                             ]),

+                             h('div', { class: "col-12 col-sm-6 px-0" }, [

+                               displayInfo.list.slice(Math.ceil(displayInfo.list.length / 2), displayInfo.list.length).map(amiInfo => {

+                                 if (amiInfo.region == null || amiInfo.region.length == 0) return;

+                                 return h('a', {

+                                   class: "dropdown-item",

+                                   attrs: {

+                                     href: "#",

+                                   },

+                                   on: {

+                                     click: toggleRegion

+                                   }

+                                 },

+                                   amiInfo.region);

+                               })

+                             ])

+                           ])

+                         ])

+                       ]),

+                     ]),

+                   ])

+                 ]),

+                 displayInfo.list.map(function (amiInfo) {

+                   // by default, only show us-east-1

+                   return h('div', { class: "px-2 mx-2 pb-2 mb-2 pt-0 mt-0", attrs: { id: amiInfo.region, hidden: amiInfo.region != "us-east-1" } }, [

+                     amiInfo.region ? h('div', {}, ["- Region: ", amiInfo.region]) : null,

+                     amiInfo.release ? h('div', { class: "ml-2" }, [

+                       h('span', {}, ["Release: ", amiInfo.release])

+                     ]) : null,

+                     amiInfo.image ? h('div', { class: "ml-2" }, [

+                       "Image: ",

+                       h('a', {

+                         attrs: {

+                           href: "https://console.aws.amazon.com/ec2/home?region=" + amiInfo.region + "#launchAmi=" + amiInfo.image

+                         }

+                       }, amiInfo.image)

+                     ]) : null

+                   ])

+                 })

+               ]);

              }

            }

            if (coreos_download_app.isGcp(platform)) {

-             return h('div', { class: "p-2 m-2" }, [

-               displayInfo.platform ? h('div', { class: "font-weight-bold" }, displayInfo.platform) : null,

-               coreos_download_app.streamData.stream ? h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream) : null,

-               displayInfo.project ? h('div', { class: "ml-2" }, [ "Project: ", displayInfo.project ]) : null,

-               displayInfo.family ? h('div', { class: "ml-2" }, [

-                 "Family: ",

-                 h('a', {

-                   attrs: {

-                     href: `https://console.cloud.google.com/marketplace/details/fedora-coreos-cloud/fedora-coreos-${coreos_download_app.streamData.stream}`

-                   }

-                 }, displayInfo.family),

-                 " (",

-                 h('a', {

-                   attrs: {

-                     href: "#"

-                   },

-                   on: {

-                     click: function(e) {

-                       e.preventDefault();

-                       let gcpNameElement = e.target.parentElement.nextSibling;

-                       gcpNameElement.hidden = !gcpNameElement.hidden;

+             return h('div', { class: "col-12 col-lg-6" }, [

+               h('div', { class: "p-2 m-2" }, [

+                 displayInfo.platform ? h('div', { class: "font-weight-bold" }, displayInfo.platform) : null,

+                 coreos_download_app.streamData.stream ? h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream) : null,

+                 displayInfo.project ? h('div', { class: "ml-2" }, ["Project: ", displayInfo.project]) : null,

+                 displayInfo.family ? h('div', { class: "ml-2" }, [

+                   "Family: ",

+                   h('a', {

+                     attrs: {

+                       href: `https://console.cloud.google.com/marketplace/details/fedora-coreos-cloud/fedora-coreos-${coreos_download_app.streamData.stream}`

                      }

-                   }

-                 }, 'details'),

-                 ")"]

-               ) : null,

-               displayInfo.name ? h('div', { class: "ml-2", attrs: { hidden: true } }, [

-                 h('span', {}, [

-                   `- The current latest image in the`,

-                   h('span', { class: "font-weight-normal font-italic" }, ` ${displayInfo.family}`),

-                   " image family is ",

-                   h('span', { class: "font-weight-normal font-italic" }, displayInfo.name),

-                   "."

-                 ])

-               ]) : null

+                   }, displayInfo.family),

+                   " (",

+                   h('a', {

+                     attrs: {

+                       href: "#"

+                     },

+                     on: {

+                       click: function (e) {

+                         e.preventDefault();

+                         let gcpNameElement = e.target.parentElement.nextSibling;

+                         gcpNameElement.hidden = !gcpNameElement.hidden;

+                       }

+                     }

+                   }, 'details'),

+                   ")"]

+                 ) : null,

+                 displayInfo.name ? h('div', { class: "ml-2", attrs: { hidden: true } }, [

+                   h('span', {}, [

+                     `- The current latest image in the`,

+                     h('span', { class: "font-weight-normal font-italic" }, ` ${displayInfo.family}`),

+                     " image family is ",

+                     h('span', { class: "font-weight-normal font-italic" }, displayInfo.name),

+                     "."

+                   ])

+                 ]) : null

+               ])

              ]);

            }

            else {
@@ -690,16 +766,16 @@ 

                displayInfo.image ? h('div', { class: "ml-2" }, displayInfo.image) : null

              ]);

            }

-         }));

+         });

        }

        else {

          cloudLaunchableSection = h('div', {}, "No cloud launchable images found.");

        }

-       cloudLaunchable = h('div', { class: "col-12 py-2 my-2" }, [ cloudLaunchableSection ]);

+       cloudLaunchable = h('div', { class: "row col-12 py-2 my-2" }, [cloudLaunchableSection]);

  

        function createDownloadsSubSection(displayDownloads, contentType, showTitle, imageType) {

          verifyBlurb =

-         `<div class="mb-3">

+           `<div class="mb-3">

            Verify your download using the detached signature after importing <a href="https://getfedora.org/security/">Fedora's GPG signing keys</a>.

            The detached signature is for the released artifact itself. If there is a good signature from one of the Fedora keys, and the SHA256 checksum matches, then the download is valid.

          </div>`
@@ -711,7 +787,7 @@ 

            h('div', {}, [

              h('button', {

                on: {

-                 click: function(e) {

+                 click: function (e) {

                    // on click edit the content of popup modal

                    if (e.target !== e.currentTarget) {

                      return;
@@ -722,21 +798,21 @@ 

                      let aChecksum = null;

                      let aSignature = null;

                      // Show SHA256 and initialize the <a> tags if data is available

-                     if(displayDownloads.sha256) {

+                     if (displayDownloads.sha256) {

                        let d = document.createElement('div');

                        $(d).addClass("overflow-auto")

-                           .html("SHA256: " + displayDownloads.sha256)

-                           .appendTo(p);

+                         .html("SHA256: " + displayDownloads.sha256)

+                         .appendTo(p);

  

                        aChecksum = document.createElement('a');

                        $(aChecksum).attr("href", "data:text/plain;charset=utf-8," + encodeURIComponent("SHA256 (" + getFilename(displayDownloads.location) + ") = " + displayDownloads.sha256))

-                                    .attr("download", getFilename(displayDownloads.location) + "-CHECKSUM")

-                                    .html("checksum file");

+                         .attr("download", getFilename(displayDownloads.location) + "-CHECKSUM")

+                         .html("checksum file");

                      }

-                     if(displayDownloads.signature) {

+                     if (displayDownloads.signature) {

                        aSignature = document.createElement('a');

                        $(aSignature).attr("href", displayDownloads.signature)

-                                     .html("signature");

+                         .html("signature");

                      }

                      $(p).appendTo("#modal-body");

                      $(verifyBlurb).appendTo("#modal-body");
@@ -747,9 +823,9 @@ 

                        let li = document.createElement('li');

                        p = document.createElement('p');

                        $(p).append("Download the ")

-                           .append(aChecksum)

-                           .append(aChecksum && aSignature ? " and " : "")

-                           .append(aSignature);

+                         .append(aChecksum)

+                         .append(aChecksum && aSignature ? " and " : "")

+                         .append(aSignature);

                        $(p).appendTo(li);

                        $(li).appendTo(ol);

                      }
@@ -761,7 +837,7 @@ 

                      code = document.createElement('code');

                      pre = document.createElement('pre');

                      $(code).html("curl https://getfedora.org/static/fedora.gpg | gpg --import")

-                            .appendTo(pre);

+                       .appendTo(pre);

                      $(p).appendTo(li);

                      $(pre).appendTo(li);

                      $(li).appendTo(ol);
@@ -773,7 +849,7 @@ 

                      code = document.createElement('code');

                      pre = document.createElement('pre');

                      $(code).html("gpg --verify " + getFilename(displayDownloads.signature) + " " + getFilename(displayDownloads.location))

-                            .appendTo(pre);

+                       .appendTo(pre);

                      $(p).appendTo(li);

                      $(pre).appendTo(li);

                      $(li).appendTo(ol);
@@ -785,7 +861,7 @@ 

                      code = document.createElement('code');

                      pre = document.createElement('pre');

                      $(code).html("sha256sum -c " + getFilename(displayDownloads.location) + "-CHECKSUM")

-                            .appendTo(pre);

+                       .appendTo(pre);

                      $(p).appendTo(li);

                      $(pre).appendTo(li);

                      $(li).appendTo(ol);
@@ -806,14 +882,14 @@ 

          ]) : null

        }

        function createArtifactsSection(displayArtifacts, imageType) {

-         return h('div', {}, Object.entries(displayArtifacts).map(function(entry) {

+         return h('div', { class: imageType == "cloud" ? "row" : "" }, Object.entries(displayArtifacts).map(function (entry) {

            platformFormat = entry[0];

            displayInfo = entry[1];

-           return h('div', { class: "p-2 m-2" }, [

+           return h('div', { class: imageType == "cloud" ? "py-2 my-2 col-12 col-lg-6" : "p-2 m-2" }, [

              displayInfo.platform ? h('div', { class: "font-weight-bold" }, displayInfo.platform) : null,

-             displayInfo.extension ? h('div', {}, [ "(", displayInfo.extension, ")" ]) : null,

+             displayInfo.extension ? h('div', {}, ["(", displayInfo.extension, ")"]) : null,

              displayInfo.release ? h('div', { class: "ml-2" }, [

-               h('span', {}, [ displayInfo.release, " " ]),

+               h('span', {}, [displayInfo.release, " "]),

                h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)

              ]) : null,

              displayInfo.downloads ? h('div', { class: "ml-2" }, [
@@ -830,7 +906,7 @@ 

        else {

          bareMetalSection = h('div', {}, "No bare metal images found.");

        }

-       bareMetal = h('div', { class: "col-12 py-2 my-2" }, [ bareMetalSection ]);

+       bareMetal = h('div', { class: "col-12" }, [bareMetalSection]);

  

        if (this.streamDisplay.virtualized) {

          virtualizedSection = createArtifactsSection(this.streamDisplay.virtualized, 'virtualized');
@@ -838,7 +914,7 @@ 

        else {

          virtualizedSection = h('div', {}, "No virtualized images found.");

        }

-       virtualized = h('div', { class: "col-12 py-2 my-2" }, [ virtualizedSection ]);

+       virtualized = h('div', { class: "col-12" }, [virtualizedSection]);

  

        if (this.streamDisplay.cloud) {

          cloudSection = createArtifactsSection(this.streamDisplay.cloud, 'cloud');
@@ -846,16 +922,16 @@ 

        else {

          cloudSection = h('div', {}, "No cloud images found.");

        }

-       cloud = h('div', { class: "col-12 py-2 my-2" }, [ cloudSection ]);

+       cloud = h('div', { class: "col-12 py-2 my-2" }, [cloudSection]);

  

-       let bareMetalContainer = h('div', { class: "col-lg-6" }, [ bareMetalTitle, bareMetal ]);

-       let virtualizedContainer = h('div', { class: "col-lg-6" }, [ virtualizedTitle, virtualized ]);

+       let bareMetalContainer = h('div', { class: "col-lg-6 my-2 py-2" }, [bareMetalTitle, bareMetal]);

+       let virtualizedContainer = h('div', { class: "col-lg-6 my-2 py-2" }, [virtualizedTitle, virtualized]);

  

-       let cloudLaunchableContainer = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [ cloudLaunchable ]);

-       let metalVirtContainer = h('div', { class: "row col-12 py-2 my-2", attrs: { id: IdPool.metal_virtualized, hidden: this.shownId !== IdPool.metal_virtualized } }, [ bareMetalContainer, virtualizedContainer ]);

-       let cloudOperatorsContainer = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_operators, hidden: this.shownId !== IdPool.cloud_operators } }, [ cloud ]);

+       let cloudLaunchableContainer = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [cloudLaunchable]);

+       let metalVirtContainer = h('div', { class: "row col-12 py-2 my-2", attrs: { id: IdPool.metal_virtualized, hidden: this.shownId !== IdPool.metal_virtualized } }, [bareMetalContainer, virtualizedContainer]);

+       let cloudOperatorsContainer = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_operators, hidden: this.shownId !== IdPool.cloud_operators } }, [cloud]);

  

-       streamInfoDiv = h('div', { class: "bg-light" }, [ h('div', { class: "container font-weight-light" }, [ streamSelectContainer ]) ]);

+       streamInfoDiv = h('div', { class: "bg-light" }, [h('div', { class: "container font-weight-light" }, [streamSelectContainer])]);

        downloadDiv = h('div', { class: "bg-white pb-5" }, [

          h('div', { class: "container font-weight-light" }, [

            signatureSha256VerificationModal,
@@ -868,8 +944,8 @@ 

        return h('div', {}, [h1Title, streamInfoDiv, downloadDiv]);

      }

      else {

-       errorDiv = h('div', { class: "bg-transparent py-5" }, [ h('div', { class: "container font-weight-light" }, "No stream data found!") ]);

-       return h('div', {}, [ errorDiv ]);

+       errorDiv = h('div', { class: "bg-transparent py-5" }, [h('div', { class: "container font-weight-light" }, "No stream data found!")]);

+       return h('div', {}, [errorDiv]);

      }

    }

  })

@@ -30,26 +30,26 @@ 

  }

  

  function getBaseUrl(stream, developer) {

-     return stream != "developer"

-         ? `${baseProdUrl}/${stream}`

-         : `${baseDevelUrl}/${developer}`;

+   return stream != "developer"

+     ? `${baseProdUrl}/${stream}`

+     : `${baseDevelUrl}/${developer}`;

  }

  

  function sortPkgDiff(meta) {

    if ("pkgdiff" in meta) {

-       var newdiff = {};

-       diffType.forEach(t => newdiff[t] = []);

-       meta["pkgdiff"].forEach(d => newdiff[diffType[d[1]]].push(d));

-       meta["pkgdiff"] = newdiff;

+     var newdiff = {};

+     diffType.forEach(t => newdiff[t] = []);

+     meta["pkgdiff"].forEach(d => newdiff[diffType[d[1]]].push(d));

+     meta["pkgdiff"] = newdiff;

    }

  }

  

  function findImportantPkgs(commitmeta) {

    var r = [];

    commitmeta["rpmostree.rpmdb.pkglist"].forEach(pkg => {

-       if (importantPkgs.includes(pkg[0])) {

-           r.push(pkg);

-       }

+     if (importantPkgs.includes(pkg[0])) {

+       r.push(pkg);

+     }

    });

    return r;

  }
@@ -57,24 +57,24 @@ 

  // The actual fetch function for `releases.json`

  function fetchReleases(base) {

    return fetch(`${base}/releases.json`)

-       .then(response => response.ok ? response.json() : {"releases": []})

-       .then(data => {

-           return data.releases.map(release => release.version);

-       });

+     .then(response => response.ok ? response.json() : { "releases": [] })

+     .then(data => {

+       return data.releases.map(release => release.version);

+     });

  }

  

  // The actual fetch function for `builds.json`

  function fetchBuilds(base) {

-     return fetch(`${base}/builds.json`)

-         .then(response => response.ok ? response.json() : {"builds": []})

-         .then(data => {

-             if (!('schema-version' in data) || data["schema-version"] != "1.0.0") {

-                 // in legacy mode, just assume we only built x86_64

-                 return [true, data.builds.map(id => ({'id': id, 'arches': ['x86_64'], 'meta': null, 'commitmeta': null}))];

-             } else {

-                 return [false, data.builds.map(build => ({'id': build.id, 'arches': build.arches, 'meta': null, 'commitmeta': null}))];

-             }

-         });

+   return fetch(`${base}/builds.json`)

+     .then(response => response.ok ? response.json() : { "builds": [] })

+     .then(data => {

+       if (!('schema-version' in data) || data["schema-version"] != "1.0.0") {

+         // in legacy mode, just assume we only built x86_64

+         return [true, data.builds.map(id => ({ 'id': id, 'arches': ['x86_64'], 'meta': null, 'commitmeta': null }))];

+       } else {

+         return [false, data.builds.map(build => ({ 'id': build.id, 'arches': build.arches, 'meta': null, 'commitmeta': null }))];

+       }

+     });

  }

  

  // Gather a metadata list of builds between releases
@@ -113,10 +113,10 @@ 

  // Get an accumulated pkgdiff given a list of metadata

  // e.g. given a list of build metadata fetched between two consecutive releases

  // we can compute the overall accumulated pkgdiff between two releases

- function getPkgDiffFromMetaList (metaList) {

+ function getPkgDiffFromMetaList(metaList) {

    function getPkgDiffReducer(pkgDiffAcc, currentMeta) {

      // NOTE: pkgDiffAcc is the most recent diff accumulated, and currentMeta has the older pkgdiff

-     if (! ("pkgdiff" in currentMeta[1])) {

+     if (!("pkgdiff" in currentMeta[1])) {

        return pkgDiffAcc;

      }

      currentMeta[1].pkgdiff.map(d => {
@@ -287,12 +287,12 @@ 

  // The actual fetch function for `meta.json`

  function fetchBuildMeta(base, build, legacy) {

    if (legacy) {

-       return fetch(`${base}/${build.id}/meta.json`)

-           .then(response => Promise.all([build.arches[0], response.ok ? response.json() : {}]));

+     return fetch(`${base}/${build.id}/meta.json`)

+       .then(response => Promise.all([build.arches[0], response.ok ? response.json() : {}]));

    }

    // XXX: just fetch the meta for the first arch right now

    return fetch(`${base}/${build.id}/${build.arches[0]}/meta.json`)

-       .then(response => Promise.all([build.arches[0], response.ok ? response.json() : {}]));

+     .then(response => Promise.all([build.arches[0], response.ok ? response.json() : {}]));

  

    // return Promise.all(build.arches.map(arch => {

    //     fetch(`${base}/${build.id}/${arch}/meta.json`)
@@ -303,16 +303,16 @@ 

  // The actual fetch function for `commitmeta.json`

  function fetchBuildCommitMeta(base, build, basearch, legacy) {

    if (legacy) {

-       return fetch(`${base}/${build.id}/commitmeta.json`)

-           .then(response => response.ok ? response.json() : {});

+     return fetch(`${base}/${build.id}/commitmeta.json`)

+       .then(response => response.ok ? response.json() : {});

    }

    return fetch(`${base}/${build.id}/${basearch}/commitmeta.json`)

-       .then(response => response.ok ? response.json() : {});

+     .then(response => response.ok ? response.json() : {});

  }

  

  var coreos_release_notes = new Vue({

    el: '#coreos-release-notes',

-   created: function() { this.refreshBuilds() },

+   created: function () { this.refreshBuilds() },

    data: {

      // source of truth for streams

      streamList: ['stable', 'testing', 'next'],
@@ -336,54 +336,54 @@ 

      loading: true

    },

    watch: {

-     stream: function() {

+     stream: function () {

        this.refreshBuilds();

      }

    },

    methods: {

-     getPkgNevra: function(tuple) {

+     getPkgNevra: function (tuple) {

        return `${tuple[0]}-${tuple[1]}.${tuple[2]}`;

      },

-     getPkgNevraFull: function(tuple) {

-         if (tuple[1] != 0) {

-             return `${tuple[0]}-${tuple[1]}:${tuple[2]}-${tuple[3]}.${tuple[4]}`;

-         }

-         return `${tuple[0]}-${tuple[2]}-${tuple[3]}.${tuple[4]}`;

+     getPkgNevraFull: function (tuple) {

+       if (tuple[1] != 0) {

+         return `${tuple[0]}-${tuple[1]}:${tuple[2]}-${tuple[3]}.${tuple[4]}`;

+       }

+       return `${tuple[0]}-${tuple[2]}-${tuple[3]}.${tuple[4]}`;

      },

-     getPkgEvra: function(tuple) {

-         return `${tuple[1]}.${tuple[2]}`;

+     getPkgEvra: function (tuple) {

+       return `${tuple[1]}.${tuple[2]}`;

      },

-     getNavbar: function(h) {

+     getNavbar: function (h) {

        const self = this;

        const changeStream = e => {

          if (e.target.innerText === "Stable Stream") {

-             self.stream = "stable"

+           self.stream = "stable"

          }

          if (e.target.innerText === "Testing Stream") {

-             self.stream = "testing"

+           self.stream = "testing"

          }

          if (e.target.innerText === "Next Stream") {

-             self.stream = "next"

+           self.stream = "next"

          }

          const overviewPageUrl = window.location.href.match(/^.*\/coreos/)[0];

          history.replaceState(null, null, `${overviewPageUrl}?stream=${self.stream}`);

        }

        let shieldIcon = h('i', { class: "fas fa-shield-alt mr-2" })

-       let navStableBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "stable" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [ shieldIcon, "Stable Stream" ]);

-       let navStable = h('li', { class: "nav-item col-12 col-sm-4" }, [ navStableBtn ]);

+       let navStableBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "stable" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [shieldIcon, "Stable Stream"]);

+       let navStable = h('li', { class: "nav-item col-12 col-sm-4" }, [navStableBtn]);

  

        let flaskIcon = h('i', { class: "fas fa-flask mr-2" })

-       let navTestingBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "testing" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [ flaskIcon, "Testing Stream" ]);

-       let navTesting = h('li', { class: "nav-item col-12 col-sm-4" }, [ navTestingBtn ]);

+       let navTestingBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "testing" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [flaskIcon, "Testing Stream"]);

+       let navTesting = h('li', { class: "nav-item col-12 col-sm-4" }, [navTestingBtn]);

  

        let layerIcon = h('i', { class: "fas fa-layer-group mr-2" })

-       let navNextBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "next" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [ layerIcon, "Next Stream" ]);

-       let navNext = h('li', { class: "nav-item col-12 col-sm-4" }, [ navNextBtn ]);

+       let navNextBtn = h('button', { class: "nav-link col-12 h-100 overflow-hidden".concat(this.stream === "next" ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: changeStream } }, [layerIcon, "Next Stream"]);

+       let navNext = h('li', { class: "nav-item col-12 col-sm-4" }, [navNextBtn]);

  

-       let navbar = h('ul', { class: "nav nav-tabs" }, [ navStable, navTesting, navNext ]);

+       let navbar = h('ul', { class: "nav nav-tabs" }, [navStable, navTesting, navNext]);

        return navbar;

      },

-     getReleaseNoteCards: function(h) {

+     getReleaseNoteCards: function (h) {

        const self = this;

        // check if all release metadata has been fetched

        if (self.loading) {
@@ -403,7 +403,7 @@ 

          build.arches.forEach((arch, _) => {

            headingListArches.push(h('h6', {}, arch));

          });

-         let leftPane = h('div', { class: "col-lg-2" }, [ headingBuildId, headingListArches ]);

+         let leftPane = h('div', { class: "col-lg-2" }, [headingBuildId, headingListArches]);

  

          // Right pane consists of detailed package information

          let date = h('p', {}, `Release Date: ${timestampToPrettyString(build.meta['coreos-assembler.build-timestamp'])}`);
@@ -423,7 +423,7 @@ 

                  href: "#"

                },

                on: {

-                 click: function(e) {

+                 click: function (e) {

                    e.preventDefault();

                    let totalPkgListElement = e.target.parentElement.nextSibling;

                    if (totalPkgListElement.hidden == true) {
@@ -444,29 +444,29 @@ 

            pkgSummaryElements = pkgSummaryElements.concat(

              `${build.meta.pkgdiff.added.length} added (`

            )

-           .concat(

-             h('a', {

-               attrs: {

-                 href: "#"

-               },

-               on: {

-                 click: function(e) {

-                   e.preventDefault();

-                   let totalPkgListElement = e.target.parentElement

-                                              .nextSibling

-                                              .nextSibling;

-                   if (totalPkgListElement.hidden == true) {

-                     totalPkgListElement.hidden = false;

-                     e.target.innerText = 'collapse';

-                   } else {

-                     totalPkgListElement.hidden = true;

-                     e.target.innerText = 'expand';

+             .concat(

+               h('a', {

+                 attrs: {

+                   href: "#"

+                 },

+                 on: {

+                   click: function (e) {

+                     e.preventDefault();

+                     let totalPkgListElement = e.target.parentElement

+                       .nextSibling

+                       .nextSibling;

+                     if (totalPkgListElement.hidden == true) {

+                       totalPkgListElement.hidden = false;

+                       e.target.innerText = 'collapse';

+                     } else {

+                       totalPkgListElement.hidden = true;

+                       e.target.innerText = 'expand';

+                     }

                    }

                  }

-               }

-             }, 'expand')

-           )

-           .concat('); ');

+               }, 'expand')

+             )

+             .concat('); ');

          }

  

          // `removed` summary and expand button
@@ -474,30 +474,30 @@ 

            pkgSummaryElements = pkgSummaryElements.concat(

              `${build.meta.pkgdiff.removed.length} removed (`

            )

-           .concat(

-             h('a', {

-               attrs: {

-                 href: "#"

-               },

-               on: {

-                 click: function(e) {

-                   e.preventDefault();

-                   let totalPkgListElement = e.target.parentElement

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling;

-                   if (totalPkgListElement.hidden == true) {

-                     totalPkgListElement.hidden = false;

-                     e.target.innerText = 'collapse';

-                   } else {

-                     totalPkgListElement.hidden = true;

-                     e.target.innerText = 'expand';

+             .concat(

+               h('a', {

+                 attrs: {

+                   href: "#"

+                 },

+                 on: {

+                   click: function (e) {

+                     e.preventDefault();

+                     let totalPkgListElement = e.target.parentElement

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling;

+                     if (totalPkgListElement.hidden == true) {

+                       totalPkgListElement.hidden = false;

+                       e.target.innerText = 'collapse';

+                     } else {

+                       totalPkgListElement.hidden = true;

+                       e.target.innerText = 'expand';

+                     }

                    }

                  }

-               }

-             }, 'expand')

-           )

-           .concat('); ');

+               }, 'expand')

+             )

+             .concat('); ');

          }

  

          // `upgraded` summary and expand button
@@ -505,31 +505,31 @@ 

            pkgSummaryElements = pkgSummaryElements.concat(

              `${build.meta.pkgdiff.upgraded.length} upgraded (`

            )

-           .concat(

-             h('a', {

-               attrs: {

-                 href: "#"

-               },

-               on: {

-                 click: function(e) {

-                   e.preventDefault();

-                   let totalPkgListElement = e.target.parentElement

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling;

-                   if (totalPkgListElement.hidden == true) {

-                     totalPkgListElement.hidden = false;

-                     e.target.innerText = 'collapse';

-                   } else {

-                     totalPkgListElement.hidden = true;

-                     e.target.innerText = 'expand';

+             .concat(

+               h('a', {

+                 attrs: {

+                   href: "#"

+                 },

+                 on: {

+                   click: function (e) {

+                     e.preventDefault();

+                     let totalPkgListElement = e.target.parentElement

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling;

+                     if (totalPkgListElement.hidden == true) {

+                       totalPkgListElement.hidden = false;

+                       e.target.innerText = 'collapse';

+                     } else {

+                       totalPkgListElement.hidden = true;

+                       e.target.innerText = 'expand';

+                     }

                    }

                  }

-               }

-             }, 'expand')

-           )

-           .concat('); ');

+               }, 'expand')

+             )

+             .concat('); ');

          }

  

          // `downgraded` summary and expand button
@@ -537,32 +537,32 @@ 

            pkgSummaryElements = pkgSummaryElements.concat(

              `${build.meta.pkgdiff.downgraded.length} downgraded (`

            )

-           .concat(

-             h('a', {

-               attrs: {

-                 href: "#"

-               },

-               on: {

-                 click: function(e) {

-                   e.preventDefault();

-                   let totalPkgListElement = e.target.parentElement

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling

-                                              .nextSibling;

-                   if (totalPkgListElement.hidden == true) {

-                     totalPkgListElement.hidden = false;

-                     e.target.innerText = 'collapse';

-                   } else {

-                     totalPkgListElement.hidden = true;

-                     e.target.innerText = 'expand';

+             .concat(

+               h('a', {

+                 attrs: {

+                   href: "#"

+                 },

+                 on: {

+                   click: function (e) {

+                     e.preventDefault();

+                     let totalPkgListElement = e.target.parentElement

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling

+                       .nextSibling;

+                     if (totalPkgListElement.hidden == true) {

+                       totalPkgListElement.hidden = false;

+                       e.target.innerText = 'collapse';

+                     } else {

+                       totalPkgListElement.hidden = true;

+                       e.target.innerText = 'expand';

+                     }

                    }

                  }

-               }

-             }, 'expand')

-           )

-           .concat('); ');

+               }, 'expand')

+             )

+             .concat('); ');

          }

  

          let pkgSummaryDiv = h('div', { class: "mt-3" }, pkgSummaryElements);
@@ -576,7 +576,7 @@ 

            });

            totalPkgsHeading = h('p', { class: "mt-3" }, "Package List:")

          }

-         let totalPkgsElements = h('div', { attrs: { hidden: true } }, [ totalPkgsHeading, h('ul', {}, totalPkgsElementsList) ]);

+         let totalPkgsElements = h('div', { attrs: { hidden: true } }, [totalPkgsHeading, h('ul', {}, totalPkgsElementsList)]);

  

          // Added package list

          let addedPkgsElementsList = [];
@@ -587,7 +587,7 @@ 

            });

            addedPkgsHeading = h('p', { class: "mt-3" }, "Added:")

          }

-         let addedPkgsElements = h('div', { attrs: { hidden: true } }, [ addedPkgsHeading, h('ul', {}, addedPkgsElementsList) ]);

+         let addedPkgsElements = h('div', { attrs: { hidden: true } }, [addedPkgsHeading, h('ul', {}, addedPkgsElementsList)]);

  

          // Removed package list

          let removedPkgsElementsList = [];
@@ -598,7 +598,7 @@ 

            });

            removedPkgsHeading = h('p', { class: "mt-3" }, "Removed:");

          }

-         let removedPkgsElements = h('div', { attrs: { hidden: true } }, [ removedPkgsHeading, h('ul', {}, removedPkgsElementsList) ]);

+         let removedPkgsElements = h('div', { attrs: { hidden: true } }, [removedPkgsHeading, h('ul', {}, removedPkgsElementsList)]);

  

          // Upgraded package list

          let upgradedPkgsElementsList = [];
@@ -609,7 +609,7 @@ 

            });

            upgradedPkgsHeading = h('p', { class: "mt-3" }, "Upgraded:");

          }

-         let upgradedPkgsElements = h('div', { attrs: { hidden: true } }, [ upgradedPkgsHeading, h('ul', {}, upgradedPkgsElementsList) ]);

+         let upgradedPkgsElements = h('div', { attrs: { hidden: true } }, [upgradedPkgsHeading, h('ul', {}, upgradedPkgsElementsList)]);

  

          // Downgraded package list

          let downgradedPkgsElementsList = [];
@@ -620,43 +620,43 @@ 

            });

            downgradedPkgsHeading = h('p', { class: "mt-3" }, "Downgraded:");

          }

-         let downgradedPkgsElements = h('div', { attrs: { hidden: true } }, [ downgradedPkgsHeading, h('ul', {}, downgradedPkgsElementsList) ]);

+         let downgradedPkgsElements = h('div', { attrs: { hidden: true } }, [downgradedPkgsHeading, h('ul', {}, downgradedPkgsElementsList)]);

  

-         let rightPane = h('div', { class: "col-lg-10 border-bottom mb-5 pb-4" }, [ date, importantPkgsElements, pkgSummaryDiv, totalPkgsElements, addedPkgsElements, removedPkgsElements, upgradedPkgsElements, downgradedPkgsElements ]);

-         let row = h('div', { class: "row" }, [ leftPane, rightPane ]);

+         let rightPane = h('div', { class: "col-lg-10 border-bottom mb-5 pb-4" }, [date, importantPkgsElements, pkgSummaryDiv, totalPkgsElements, addedPkgsElements, removedPkgsElements, upgradedPkgsElements, downgradedPkgsElements]);

+         let row = h('div', { class: "row" }, [leftPane, rightPane]);

          rows.push(row);

        })

        return h('div', { class: "my-5" }, rows);

      },

-     refreshBuilds: function() {

-         this.loading = true

-         this.releasesUrl = getBaseUrl(this.stream, this.developer);

-         this.buildsUrl = getBaseUrl(this.stream, this.developer) + "/builds";

-         fetchReleases(this.releasesUrl).then(releaseVersions => {

-           fetchBuilds(this.buildsUrl).then(result => {

-             [legacy, builds] = result;

-             // first populate and show the build list

-             this.legacy = legacy;

-             this.releases = [];

-             this.unshown_builds = [];

-             // counter for the number of release metadata fetched since fetch is asnyc operation

-             let counter = 0;

- 

-             // get the index list of release builds in the build list

-             const releaseIdxList = builds.map((build, idx) => releaseVersions.includes(build.id) ? idx : -1).filter(idx => idx != -1);

-             const numReleases = releaseIdxList.length;

- 

-             // fetch the metadata and compute the pkgdiff for subsequent releases

-             // since the oldest release does not have a pkgdiff, the pkgdiff for oldest release is an empty array

-             for (let i = 0; i < numReleases; i++) {

-               const releaseIdx = releaseIdxList[i];

-               // in case of oldest release, there's no older release

-               const nextReleaseIdx = releaseIdxList[i + 1] == null ? releaseIdxList[i] : releaseIdxList[i + 1];

-               if (i < initialBuildsShown) {

-                 // NOTE: here only the `builds` array have the actual values, all other variables are pointers to the elements of this array

-                 this.releases.push(builds[releaseIdx]);

-                 // fetchBuild mutates the `builds` array

-                 fetchBuild(this.buildsUrl, this.legacy, builds, releaseIdx, nextReleaseIdx)

+     refreshBuilds: function () {

+       this.loading = true

+       this.releasesUrl = getBaseUrl(this.stream, this.developer);

+       this.buildsUrl = getBaseUrl(this.stream, this.developer) + "/builds";

+       fetchReleases(this.releasesUrl).then(releaseVersions => {

+         fetchBuilds(this.buildsUrl).then(result => {

+           [legacy, builds] = result;

+           // first populate and show the build list

+           this.legacy = legacy;

+           this.releases = [];

+           this.unshown_builds = [];

+           // counter for the number of release metadata fetched since fetch is asnyc operation

+           let counter = 0;

+ 

+           // get the index list of release builds in the build list

+           const releaseIdxList = builds.map((build, idx) => releaseVersions.includes(build.id) ? idx : -1).filter(idx => idx != -1);

+           const numReleases = releaseIdxList.length;

+ 

+           // fetch the metadata and compute the pkgdiff for subsequent releases

+           // since the oldest release does not have a pkgdiff, the pkgdiff for oldest release is an empty array

+           for (let i = 0; i < numReleases; i++) {

+             const releaseIdx = releaseIdxList[i];

+             // in case of oldest release, there's no older release

+             const nextReleaseIdx = releaseIdxList[i + 1] == null ? releaseIdxList[i] : releaseIdxList[i + 1];

+             if (i < initialBuildsShown) {

+               // NOTE: here only the `builds` array have the actual values, all other variables are pointers to the elements of this array

+               this.releases.push(builds[releaseIdx]);

+               // fetchBuild mutates the `builds` array

+               fetchBuild(this.buildsUrl, this.legacy, builds, releaseIdx, nextReleaseIdx)

                  .then(() => {

                    counter++;

                    if (counter === numReleases) {
@@ -664,31 +664,31 @@ 

                      this.loading = false;

                    }

                  });

-               } else {

-                 // XXX: unshown/unprocessed releases, could be handled later according to needs

-                 this.unshown_builds.push(builds[releaseIdx]);

-                 counter++;

-                 if (counter === numReleases) {

-                   // fetched all metadata

-                   this.loading = false;

-                 }

+             } else {

+               // XXX: unshown/unprocessed releases, could be handled later according to needs

+               this.unshown_builds.push(builds[releaseIdx]);

+               counter++;

+               if (counter === numReleases) {

+                 // fetched all metadata

+                 this.loading = false;

                }

              }

-           });

+           }

          });

+       });

      }

    },

-   render: function(h) {

+   render: function (h) {

      // Duplicate logic from coreos-download.js

      // URL paramters checking and setting default values

-     if(window.location.href.match(/^.*\/coreos/) == null) {

+     if (window.location.href.match(/^.*\/coreos/) == null) {

        return

      }

      const overviewPageUrl = window.location.href.match(/^.*\/coreos/)[0];

      searchParams = new URLSearchParams(window.location.search);

      // switch to specified stream if `stream` parameter is set

      if (searchParams.has('stream')) {

-       switch(searchParams.get('stream')) {

+       switch (searchParams.get('stream')) {

          case 'stable':

            this.stream = "stable";

            break;
@@ -710,11 +710,11 @@ 

      let navBar = this.getNavbar(h);

  

      if (this.loading) {

-       let loadingDiv = h('div', { class: "bg-white pb-5" }, [ h('div', { class: "container font-weight-light" }, "Loading...") ]);

-       return h('div', {}, [ navBar, loadingDiv ]);

+       let loadingDiv = h('div', { class: "bg-white pb-5" }, [h('div', { class: "container font-weight-light" }, "Loading...")]);

+       return h('div', {}, [navBar, loadingDiv]);

      } else {

        let releaseNoteCards = this.getReleaseNoteCards(h);

-       return h('div', {}, [ navBar, releaseNoteCards ]);

+       return h('div', {}, [navBar, releaseNoteCards]);

      }

    }

  });

This changes cleans up AWS cloud launchable regions such that by default only us-east-1 is displayed and only display other regions when pressing the "show all regions" button.

Additional changes include updating the "View Releases" button to link to corresponding stream in release note page instead of to default stable stream, and minor styling modifications. Also, split cloud operators tab into two columns.

Signed-off-by: Allen Bai abai@redhat.com

rebased onto ba82f0c2de3053548922153c6448cbe6fe888920

4 months ago

1 new commit added

  • coreos-download: format coreos-release-notes and coreos-download
4 months ago

Formatted coreos-release-notes.js and coreos-download.js as a follow-up commit.

Image demo:
hide regions
show regions
two-column cloud images

GIF demo:
demo

Yes, looks good to me as well!

It looks like the "testing" stream name moved from beside the version to under the cloud name? Is there a reason for that?

Re. the "show all regions" button, it feels a bit odd IMO to have it beside the stream name. I think it'd make more sense on its own beside the cloud name? (Or maybe just on its own, right under).

@jlebon
I was thinking the original version might have repeated the stream too many times so changed to this to get some reviews from ppl. I don't have strong preference so I'm happy to revert it back but I do wanted to get some feedback. Also, what do you think about the "verbose" level of current display, I wonder if the newly added "Region, Release, Image" keys will distract users from finding essential information.

re. "show all regions": Sure, I will try both ways locally, but I do think beside the cloud name "AWS" might be cleaner.

If the release is always the same for all regions maybe we should only show it once at the top?

This looks great. One question: Would it make sense to just have a dropdown box where you select a region instead of the button to "show all regions"? That way the UI wouldn't all of a sudden get 12 more entries and the users could just select the region they want.

@dustymabe that makes sense, will update with a dropdown selector if it works

rebased onto cd4acc1

4 months ago

Updated with a region selector dropdown:
GIF:
gif demo
Screenshot_0:
screenshot0
Screenshot_1:
screenshot1

Ready for another look :)

Looks much better to me!

The only thing that looks a bit odd is how the GCP field doesn't have Release but AWS does. Maybe we could add Release to GCP too and move Release for both AWS and GCP up to the top directly under (or maybe even beside) the stream name.

Perfectly happy to merge this the way it is, though!

I think the reason for that is that the stream metadata accommodates the possibility for some regions to be on older releases if necessary. For parity, the .images.gcp should probably also have a release field, which we could then print here when expanding details on the GCP image?

+1 to merge as is and do more tweaks as follow-ups!

Pull-Request has been merged by dustymabe

4 months ago