From 4a865acc2a318eeb27ce50302d35fc2544544eba Mon Sep 17 00:00:00 2001
From: Allen Bai <abai@redhat.com>
Date: Jan 13 2020 16:37:24 +0000
Subject: [PATCH 1/7] coreos-download: add icons and change style for buttons


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

---

diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index 2ddd562..59de952 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -264,16 +264,19 @@ var coreos_download_app = new Vue({
       }
     },
     getNavbar: function(h) {
-      nav_cloud_launchable_btn = h('button', { class: "nav-link active", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.cloud_launchable);
-      nav_cloud_launchable = h('li', { class: "nav-item mr-3 ml-3" }, [ nav_cloud_launchable_btn ]);
+      cloud_icon = h('i', { class: "fas fa-cloud mr-2" })
+      nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, this.tabInnerText.cloud_launchable ]);
+      nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]);
 
-      nav_metal_virt_btn = h('button', { class: "nav-link", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.metal_virt);
-      nav_metal_virt = h('li', { class: "nav-item mr-3" }, [ nav_metal_virt_btn ]);
+      server_icon = h('i', { class: "fas fa-server mr-2" })
+      nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, this.tabInnerText.metal_virt ]);
+      nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]);
 
-      nav_cloud_operator_btn = h('button', { class: "nav-link", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, this.tabInnerText.cloud_operator);
-      nav_cloud_operator = h('li', { class: "nav-item" }, [ nav_cloud_operator_btn ]);
+      cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })
+      nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, this.tabInnerText.cloud_operator ]);
+      nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);
 
-      navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
+      navbar = h('ul', { class: "nav nav-tabs col-12 p-0" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
       return navbar
     }
   },

From ee4741c94c197609b9f0d46989ffcb201f7d0151 Mon Sep 17 00:00:00 2001
From: Allen Bai <abai@redhat.com>
Date: Jan 13 2020 16:37:24 +0000
Subject: [PATCH 2/7] coreos-download: use jumbotron instead of container for tabs


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

---

diff --git a/sites/getfedora.org/site/coreos/download/index.html b/sites/getfedora.org/site/coreos/download/index.html
index 5954170..e286c82 100644
--- a/sites/getfedora.org/site/coreos/download/index.html
+++ b/sites/getfedora.org/site/coreos/download/index.html
@@ -22,8 +22,15 @@
       </div>
     </div>
     <br>
-    <div id="coreos-download-app">
+  </div>
+  <div class="jumbotron py-0">
+    <div class="text-center py-3">
+      <h5 class="font-weight-light">{% trans trimmed %}Fedora CoreOS is available on multiple platforms in 3 main categories:{% endtrans %}</h5>
     </div>
+    <div id="jumbotron-buttons"></div>
+  </div>
+  <div class="container">
+    <div id="coreos-download-app"></div>
   </div>
 </div>
 {% endblock %}
diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index 59de952..b2ca7a9 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -29,6 +29,30 @@ const prettyPlatforms = {
   "vmware": "VMware",
   "openstack": "OpenStack"
 }
+var data = {
+  // currently selected stream
+  stream: 'testing',
+  // currently selected architecture
+  architecture: 'x86_64',
+  // current url to dir for stream
+  streamUrl: "",
+  // fetched {stream, metadata, architectures, updates} object from stream.json
+  streamData: null,
+  loading: false,
+  // loaded stream data to render
+  streamDisplay: {
+    cloudLaunchable: {},
+    bareMetal: {},
+    virtualized: {},
+    cloud: {}
+  },
+  // innerText of tab button
+  tabInnerText: {
+    cloud_launchable: "Cloud Launchable",
+    metal_virt: "Bare Metal & Virtualized",
+    cloud_operator: "For Cloud Operators"
+  }
+}
 function getMember(obj, member) {
   return (member in obj) ? obj[member] : null;
 }
@@ -66,38 +90,58 @@ function getDownloadsFromFormat(formatData, downloads) {
     downloads[download] = entry;
   }
 }
-
-var coreos_download_app = new Vue({
-  el: '#coreos-download-app',
-  data: {
-    // currently selected stream
-    stream: 'testing',
-    // currently selected architecture
-    architecture: 'x86_64',
-    // current url to dir for stream
-    streamUrl: "",
-    // fetched {stream, metadata, architectures, updates} object from stream.json
-    streamData: null,
-    loading: false,
-    // loaded stream data to render
-    streamDisplay: {
-      cloudLaunchable: {},
-      bareMetal: {},
-      virtualized: {},
-      cloud: {}
+var jumbotron_buttons = new Vue ({
+  el: '#jumbotron-buttons',
+  data: data,
+  methods: {
+    toggleHidden: function(e) {
+      const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];
+      switch(e.target.innerText) {
+        case data.tabInnerText.cloud_launchable:
+          show_id = 'cloud-launchable';
+          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
+          break;
+        case data.tabInnerText.metal_virt:
+          show_id = 'metal-virt';
+          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
+          break;
+        case data.tabInnerText.cloud_operator:
+          show_id = 'cloud-operator';
+          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
+          break;
+      }
     },
-    // innerText of tab button
-    tabInnerText: {
-      cloud_launchable: "Cloud Launchable",
-      metal_virt: "Bare Metal & Virtualized",
-      cloud_operator: "For Cloud Operators"
+    getNavbar: function(h) {
+      cloud_icon = h('i', { class: "fas fa-cloud mr-2" })
+      nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, data.tabInnerText.cloud_launchable ]);
+      nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]);
+
+      server_icon = h('i', { class: "fas fa-server mr-2" })
+      nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, data.tabInnerText.metal_virt ]);
+      nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]);
+
+      cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })
+      nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, data.tabInnerText.cloud_operator ]);
+      nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);
+
+      navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
+      container = h('div', { class: "container" }, [ navbar ]);
+      return container
     }
   },
+  render: function(h) {
+    navbar = this.getNavbar(h);
+    return navbar
+  }
+})
+var coreos_download_app = new Vue({
+  el: '#coreos-download-app',
   created: function() { this.refreshStream() },
-  watch: { stream: 'refreshStream' },
+  data: data,
+  watch: { 'data.stream': 'refreshStream' },
   methods: {
     getObjectUrl: function(path) {
-      return getArtifactUrl(this.streamUrl, path);
+      return getArtifactUrl(data.streamUrl, path);
     },
     isAws: function(platform) {
       return platform == "aws";
@@ -115,20 +159,20 @@ var coreos_download_app = new Vue({
     // not deep-copy information from `streamData` or elsewhere into
     // `streamDisplay`.
     loadStreamDisplay: function() {
-      this.streamDisplay = {
+      data.streamDisplay = {
         cloudLaunchable: {},
         bareMetal: {},
         virtualized: {},
         cloud: {}
       };
-      if (this.streamData == null) {
+      if (data.streamData == null) {
         return;
       }
-      const architectures = getMember(this.streamData, "architectures");
+      const architectures = getMember(data.streamData, "architectures");
       if (architectures == null) {
         return;
       }
-      const architectureData = getMember(architectures, this.architecture);
+      const architectureData = getMember(architectures, data.architecture);
       if (architectureData == null) {
         return;
       }
@@ -148,11 +192,11 @@ var coreos_download_app = new Vue({
               displayEntries.push({platform: prettyPlatform, region: region, release: release, image: image});
             }
           }
-          Vue.set(this.streamDisplay.cloudLaunchable, platform, {list: displayEntries});
+          Vue.set(data.streamDisplay.cloudLaunchable, platform, {list: displayEntries});
         }
         else {
           const image = getMember(images[platform], "image");
-          Vue.set(this.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});
+          Vue.set(data.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});
         }
       }
       const artifacts = getMember(architectureData, "artifacts");
@@ -177,32 +221,32 @@ var coreos_download_app = new Vue({
               Vue.set(display, platform + "-" + format, displayEntry);
             }
             if (this.isCloudImage(platform)) {
-              addDisplayEntry(this.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(data.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);
             }
             if (this.isVirtualizedImage(platform)) {
-              addDisplayEntry(this.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(data.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);
             }
             if (this.isBareMetalImage(platform)) {
-              addDisplayEntry(this.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(data.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);
             }
           }
         }
       }
     },
     refreshStream: function() {
-      this.loading = true
-      this.streamUrl = baseUrl
-      fetchStreamData(this.streamUrl, this.stream).then(streamData => {
-        this.loading = false;
-        this.streamData = streamData;
+      data.loading = true
+      data.streamUrl = baseUrl
+      fetchStreamData(data.streamUrl, data.stream).then(streamData => {
+        data.loading = false;
+        data.streamData = streamData;
         this.loadStreamDisplay();
       });
     },
     toggleShowSignatureAndSha: function(imageType, platformFormat, contentType) {
-      if (!(platformFormat in this.streamDisplay[imageType])) {
+      if (!(platformFormat in data.streamDisplay[imageType])) {
         return;
       }
-      const artifact = this.streamDisplay[imageType][platformFormat];
+      const artifact = data.streamDisplay[imageType][platformFormat];
       if (!(contentType in artifact.downloads)) {
         return;
       }
@@ -210,10 +254,10 @@ var coreos_download_app = new Vue({
       artifact.downloads[contentType].showSignatureAndSha = !prev;
     },
     showSignatureAndSha: function(imageType, platformFormat, contentType) {
-      if (!(platformFormat in this.streamDisplay[imageType])) {
+      if (!(platformFormat in data.streamDisplay[imageType])) {
         return false;
       }
-      const artifact = this.streamDisplay[imageType][platformFormat];
+      const artifact = data.streamDisplay[imageType][platformFormat];
       if (!(contentType in artifact.downloads)) {
         return false;
       }
@@ -245,56 +289,23 @@ var coreos_download_app = new Vue({
       } else {
         return stringize(Math.floor(elapsed/msPerYear), "year");
       }
-    },
-    toggleHidden: function(e) {
-      const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];
-      switch(e.target.innerText) {
-        case this.tabInnerText.cloud_launchable:
-          show_id = 'cloud-launchable';
-          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
-          break;
-        case this.tabInnerText.metal_virt:
-          show_id = 'metal-virt';
-          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
-          break;
-        case this.tabInnerText.cloud_operator:
-          show_id = 'cloud-operator';
-          id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
-          break;
-      }
-    },
-    getNavbar: function(h) {
-      cloud_icon = h('i', { class: "fas fa-cloud mr-2" })
-      nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, this.tabInnerText.cloud_launchable ]);
-      nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]);
-
-      server_icon = h('i', { class: "fas fa-server mr-2" })
-      nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, this.tabInnerText.metal_virt ]);
-      nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]);
-
-      cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })
-      nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, this.tabInnerText.cloud_operator ]);
-      nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);
-
-      navbar = h('ul', { class: "nav nav-tabs col-12 p-0" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
-      return navbar
     }
   },
   render: function(h) {
-    if (this.loading) {
+    if (data.loading) {
       return h('div', {}, "Loading...");
     }
-    else if (this.streamData) {
+    else if (data.streamData) {
       streamName = h('p', {}, [
         "Stream: ",
-        h('span', { "class":"font-weight-bold" }, this.streamData.stream),
+        h('span', { "class":"font-weight-bold" }, data.streamData.stream),
         " (",
         h('span', {}, [
-          h('a', { attrs: { href: this.getObjectUrl(this.streamData.stream + '.json') } }, "JSON")
+          h('a', { attrs: { href: this.getObjectUrl(data.streamData.stream + '.json') } }, "JSON")
         ]),
         ")",
         "—",
-        h('span', {}, this.timeSince(this.streamData.metadata['last-modified']))
+        h('span', {}, this.timeSince(data.streamData.metadata['last-modified']))
       ]);
 
       cloudLaunchableTitle = h('h3', { class:"font-weight-light" }, "Cloud Launchable");
@@ -310,19 +321,19 @@ var coreos_download_app = new Vue({
       cloudSection = {};
       cloud = {};
 
-      if (this.streamDisplay.cloudLaunchable) {
-        cloudLaunchableSection = h('div', {}, Object.entries(this.streamDisplay.cloudLaunchable).map(function(entry) {
+      if (data.streamDisplay.cloudLaunchable) {
+        cloudLaunchableSection = h('div', {}, Object.entries(data.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', {}, [
+                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)
+                    h('span', { class: "text-secondary" }, data.streamData.stream)
                   ]) : null,
                   amiInfo.image ? h('div', { class: "ml-2" }, [
                     h('a', {
@@ -375,7 +386,7 @@ var coreos_download_app = new Vue({
             }, "Verify signature & SHA256")
           ]),
           coreos_download_app.showSignatureAndSha(imageType, platformFormat, contentType) ? h('div', { class: "bg-gray-100 p-2 my-2" }, [ h('p', {}, [
-              displayDownloads.sha256 ? h('div', {}, [
+              displayDownloads.sha256 ? h('div', { class: "overflow-auto" }, [
                 "SHA256: ",
                 displayDownloads.sha256
               ]) : null,
@@ -419,12 +430,12 @@ var coreos_download_app = new Vue({
         return h('div', {}, Object.entries(displayArtifacts).map(function(entry) {
           platformFormat = entry[0];
           displayInfo = entry[1];
-          return h('div', { class: "my-2" }, [
+          return h('div', { class: "p-2 m-2" }, [
             displayInfo.platform ? h('div', { class: "font-weight-bold" }, displayInfo.platform) : null,
             displayInfo.extension ? h('div', {}, [ "(", displayInfo.extension, ")" ]) : null,
             displayInfo.release ? h('div', { class: "ml-2" }, [
               h('span', {}, [ displayInfo.release, " " ]),
-              h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)
+              h('span', { class: "text-secondary" }, data.streamData.stream)
             ]) : null,
             displayInfo.downloads ? h('div', { class: "ml-2" }, [
               createDownloadsSubSection(displayInfo.downloads.disk, 'disk', false, imageType),
@@ -434,24 +445,24 @@ var coreos_download_app = new Vue({
           ]);
         }));
       }
-      if (this.streamDisplay.bareMetal) {
-        bareMetalSection = createArtifactsSection(this.streamDisplay.bareMetal, 'bareMetal');
+      if (data.streamDisplay.bareMetal) {
+        bareMetalSection = createArtifactsSection(data.streamDisplay.bareMetal, 'bareMetal');
       }
       else {
         bareMetalSection = h('div', {}, "No bare metal images found.");
       }
       bareMetal = h('div', { class: "col-12 py-2 my-2" }, [ bareMetalSection ]);
 
-      if (this.streamDisplay.virtualized) {
-        virtualizedSection = createArtifactsSection(this.streamDisplay.virtualized, 'virtualized');
+      if (data.streamDisplay.virtualized) {
+        virtualizedSection = createArtifactsSection(data.streamDisplay.virtualized, 'virtualized');
       }
       else {
         virtualizedSection = h('div', {}, "No virtualized images found.");
       }
       virtualized = h('div', { class: "col-12 py-2 my-2" }, [ virtualizedSection ]);
-      
-      if (this.streamDisplay.cloud) {
-        cloudSection = createArtifactsSection(this.streamDisplay.cloud, 'cloud');
+
+      if (data.streamDisplay.cloud) {
+        cloudSection = createArtifactsSection(data.streamDisplay.cloud, 'cloud');
       }
       else {
         cloudSection = h('div', {}, "No cloud images found.");
@@ -466,17 +477,14 @@ var coreos_download_app = new Vue({
         ])
       ]);
 
-      let navbar = this.getNavbar(h);
-
-      let bare_metal_container = h('div', { class: "col-12 p-0" }, [ bareMetalTitle, verifyBlurb, bareMetal ]);
-      let virtualized_container = h('div', { class: "col-12 p-0" }, [ virtualizedTitle, verifyBlurb, virtualized ]);
+      let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, verifyBlurb, bareMetal ]);
+      let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, verifyBlurb, virtualized ]);
 
       let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-launchable", hidden: false} }, [ cloudLaunchableTitle, streamName, cloudLaunchable ]);
-      let metal_virt_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "metal-virt", hidden: true } }, [ bare_metal_container, virtualized_container ]);
+      let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: "metal-virt", hidden: true } }, [ bare_metal_container, virtualized_container ]);
       let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-operator", hidden: true } }, [ cloudTitle, verifyBlurb, cloud ]);
 
       return h('div', {}, [
-        navbar,
         cloud_launchable_container,
         metal_virt_container,
         cloud_operators_container

From 474e90df47541a2cddc52826779d440af72d388b Mon Sep 17 00:00:00 2001
From: Allen Bai <abai@redhat.com>
Date: Jan 13 2020 16:37:24 +0000
Subject: [PATCH 3/7] coreos-download: add dropdown list for streams


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

---

diff --git a/sites/getfedora.org/site/coreos/download/index.html b/sites/getfedora.org/site/coreos/download/index.html
index e286c82..3c6e12d 100644
--- a/sites/getfedora.org/site/coreos/download/index.html
+++ b/sites/getfedora.org/site/coreos/download/index.html
@@ -23,10 +23,7 @@
     </div>
     <br>
   </div>
-  <div class="jumbotron py-0">
-    <div class="text-center py-3">
-      <h5 class="font-weight-light">{% trans trimmed %}Fedora CoreOS is available on multiple platforms in 3 main categories:{% endtrans %}</h5>
-    </div>
+  <div class="jumbotron pb-0 pt-3">
     <div id="jumbotron-buttons"></div>
   </div>
   <div class="container">
diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index b2ca7a9..a2d2515 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -94,6 +94,36 @@ var jumbotron_buttons = new Vue ({
   el: '#jumbotron-buttons',
   data: data,
   methods: {
+    getObjectUrl: function(path) {
+      return getArtifactUrl(data.streamUrl, path);
+    },
+    // Adapted from https://stackoverflow.com/a/6109105
+    timeSince: function(rfc3339_timestamp) {
+      var current = Date.now();
+      var timestamp = Date.parse(rfc3339_timestamp);
+      var elapsed = current - timestamp;
+      var msPerMinute = 60 * 1000;
+      var msPerHour = msPerMinute * 60;
+      var msPerDay = msPerHour * 24;
+      var msPerMonth = msPerDay * 30;
+      var msPerYear = msPerDay * 365;
+      function stringize(n, s) {
+        return n + ` ${s}` + (n == 1 ? "" : "s") + ' ago';
+      };
+      if (elapsed < msPerMinute) {
+        return stringize(Math.floor(elapsed/1000), "second");
+      } else if (elapsed < msPerHour) {
+        return stringize(Math.floor(elapsed/msPerMinute), "minute");
+      } else if (elapsed < msPerDay) {
+        return stringize(Math.floor(elapsed/msPerHour), "hour");
+      } else if (elapsed < msPerMonth) {
+        return stringize(Math.floor(elapsed/msPerDay), "day");
+      } else if (elapsed < msPerYear) {
+        return stringize(Math.floor(elapsed/msPerMonth), "month");
+      } else {
+        return stringize(Math.floor(elapsed/msPerYear), "year");
+      }
+    },
     toggleHidden: function(e) {
       const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];
       switch(e.target.innerText) {
@@ -125,13 +155,32 @@ var jumbotron_buttons = new Vue ({
       nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);
 
       navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
-      container = h('div', { class: "container" }, [ navbar ]);
-      return container
+      return navbar;
+    },
+    // Add dropdown options of streams
+    getStreamName: function(h) {
+      if (data.streamData === null) return;
+      option_default = h('option', { attrs: { value: "testing", selected: "selected" }}, "testing" );
+      selectOptions = h('select', { class: "mx-1" }, [ option_default ]);
+      streamName = h('p', {}, [
+        "Stream: ",
+        selectOptions,
+        " (",
+        h('span', {}, [
+          h('a', { attrs: { href: this.getObjectUrl(data.streamData.stream + '.json') } }, "JSON")
+        ]),
+        ")",
+        "—",
+        h('span', {}, this.timeSince(data.streamData.metadata['last-modified']))
+      ]);
+      return streamName;
     }
   },
   render: function(h) {
     navbar = this.getNavbar(h);
-    return navbar
+    streamName = this.getStreamName(h);
+    container = h('div', { class: "container" }, [ streamName, navbar ]);
+    return container
   }
 })
 var coreos_download_app = new Vue({
@@ -140,9 +189,6 @@ var coreos_download_app = new Vue({
   data: data,
   watch: { 'data.stream': 'refreshStream' },
   methods: {
-    getObjectUrl: function(path) {
-      return getArtifactUrl(data.streamUrl, path);
-    },
     isAws: function(platform) {
       return platform == "aws";
     },
@@ -262,33 +308,6 @@ var coreos_download_app = new Vue({
         return false;
       }
       return artifact.downloads[contentType].showSignatureAndSha;
-    },
-    // Adapted from https://stackoverflow.com/a/6109105
-    timeSince: function(rfc3339_timestamp) {
-      var current = Date.now();
-      var timestamp = Date.parse(rfc3339_timestamp);
-      var elapsed = current - timestamp;
-      var msPerMinute = 60 * 1000;
-      var msPerHour = msPerMinute * 60;
-      var msPerDay = msPerHour * 24;
-      var msPerMonth = msPerDay * 30;
-      var msPerYear = msPerDay * 365;
-      function stringize(n, s) {
-        return n + ` ${s}` + (n == 1 ? "" : "s") + ' ago';
-      };
-      if (elapsed < msPerMinute) {
-        return stringize(Math.floor(elapsed/1000), "second");
-      } else if (elapsed < msPerHour) {
-        return stringize(Math.floor(elapsed/msPerMinute), "minute");
-      } else if (elapsed < msPerDay) {
-        return stringize(Math.floor(elapsed/msPerHour), "hour");
-      } else if (elapsed < msPerMonth) {
-        return stringize(Math.floor(elapsed/msPerDay), "day");
-      } else if (elapsed < msPerYear) {
-        return stringize(Math.floor(elapsed/msPerMonth), "month");
-      } else {
-        return stringize(Math.floor(elapsed/msPerYear), "year");
-      }
     }
   },
   render: function(h) {
@@ -296,18 +315,6 @@ var coreos_download_app = new Vue({
       return h('div', {}, "Loading...");
     }
     else if (data.streamData) {
-      streamName = h('p', {}, [
-        "Stream: ",
-        h('span', { "class":"font-weight-bold" }, data.streamData.stream),
-        " (",
-        h('span', {}, [
-          h('a', { attrs: { href: this.getObjectUrl(data.streamData.stream + '.json') } }, "JSON")
-        ]),
-        ")",
-        "—",
-        h('span', {}, this.timeSince(data.streamData.metadata['last-modified']))
-      ]);
-
       cloudLaunchableTitle = h('h3', { class:"font-weight-light" }, "Cloud Launchable");
       cloudLaunchableSection = {};
       cloudLaunchable = {};
@@ -480,7 +487,7 @@ var coreos_download_app = new Vue({
       let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, verifyBlurb, bareMetal ]);
       let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, verifyBlurb, virtualized ]);
 
-      let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-launchable", hidden: false} }, [ cloudLaunchableTitle, streamName, cloudLaunchable ]);
+      let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-launchable", hidden: false} }, [ cloudLaunchableTitle, cloudLaunchable ]);
       let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: "metal-virt", hidden: true } }, [ bare_metal_container, virtualized_container ]);
       let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-operator", hidden: true } }, [ cloudTitle, verifyBlurb, cloud ]);
 

From 74e59d03e97a74767ff22a5848ddfd6b4cbe326f Mon Sep 17 00:00:00 2001
From: Robert Fairley <rfairley@redhat.com>
Date: Jan 13 2020 19:20:48 +0000
Subject: [PATCH 4/7] coreos: add 'stable' option in streams dropdown


Allow switching between the 'stable' and 'testing' streams by
making a selection in the dropdown menu. Upon selection, a
refresh of the loaded `streamData` is triggered by modifying
the watched `stream` data member, and new metadata is fetched.

In order to use the Vue watch between the jumbotron buttons and
the rest of the app, state data (`data`) is moved back into a
single Vue instance, along with methods associated with the
jumbotron buttons.

The width of the jumbotron buttons menu is slightly altered in
this change by extending to the same width as the main view app,
rather than the full page.

This is a quick change in order to have the switching between
streams working - further improvements to the visual aspects of
the page may be made afterward.

Signed-off-by: Robert Fairley <rfairley@redhat.com>

---

diff --git a/sites/getfedora.org/site/coreos/download/index.html b/sites/getfedora.org/site/coreos/download/index.html
index 3c6e12d..01746da 100644
--- a/sites/getfedora.org/site/coreos/download/index.html
+++ b/sites/getfedora.org/site/coreos/download/index.html
@@ -23,9 +23,6 @@
     </div>
     <br>
   </div>
-  <div class="jumbotron pb-0 pt-3">
-    <div id="jumbotron-buttons"></div>
-  </div>
   <div class="container">
     <div id="coreos-download-app"></div>
   </div>
diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index a2d2515..48dc568 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -29,29 +29,11 @@ const prettyPlatforms = {
   "vmware": "VMware",
   "openstack": "OpenStack"
 }
-var data = {
-  // currently selected stream
-  stream: 'testing',
-  // currently selected architecture
-  architecture: 'x86_64',
-  // current url to dir for stream
-  streamUrl: "",
-  // fetched {stream, metadata, architectures, updates} object from stream.json
-  streamData: null,
-  loading: false,
-  // loaded stream data to render
-  streamDisplay: {
-    cloudLaunchable: {},
-    bareMetal: {},
-    virtualized: {},
-    cloud: {}
-  },
-  // innerText of tab button
-  tabInnerText: {
-    cloud_launchable: "Cloud Launchable",
-    metal_virt: "Bare Metal & Virtualized",
-    cloud_operator: "For Cloud Operators"
-  }
+// innerText of tab button
+const tabInnerText = {
+  "cloud_launchable": "Cloud Launchable",
+  "metal_virt": "Bare Metal & Virtualized",
+  "cloud_operator": "For Cloud Operators"
 }
 function getMember(obj, member) {
   return (member in obj) ? obj[member] : null;
@@ -90,12 +72,33 @@ function getDownloadsFromFormat(formatData, downloads) {
     downloads[download] = entry;
   }
 }
-var jumbotron_buttons = new Vue ({
-  el: '#jumbotron-buttons',
-  data: data,
+var coreos_download_app = new Vue({
+  el: '#coreos-download-app',
+  created: function() { this.refreshStream() },
+  data: {
+    // currently selected stream
+    stream: 'testing',
+    // currently selected architecture
+    architecture: 'x86_64',
+    // current url to dir for stream
+    streamUrl: "",
+    // fetched {stream, metadata, architectures, updates} object from stream.json
+    streamData: null,
+    loading: false,
+    // loaded stream data to render
+    streamDisplay: {
+      cloudLaunchable: {},
+      bareMetal: {},
+      virtualized: {},
+      cloud: {}
+    },
+  },
+  watch: { stream: function() {
+    this.refreshStream();
+  } },
   methods: {
     getObjectUrl: function(path) {
-      return getArtifactUrl(data.streamUrl, path);
+      return getArtifactUrl(this.streamUrl, path);
     },
     // Adapted from https://stackoverflow.com/a/6109105
     timeSince: function(rfc3339_timestamp) {
@@ -127,15 +130,15 @@ var jumbotron_buttons = new Vue ({
     toggleHidden: function(e) {
       const id_list = ['cloud-launchable', 'metal-virt', 'cloud-operator'];
       switch(e.target.innerText) {
-        case data.tabInnerText.cloud_launchable:
+        case tabInnerText.cloud_launchable:
           show_id = 'cloud-launchable';
           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
           break;
-        case data.tabInnerText.metal_virt:
+        case tabInnerText.metal_virt:
           show_id = 'metal-virt';
           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
           break;
-        case data.tabInnerText.cloud_operator:
+        case tabInnerText.cloud_operator:
           show_id = 'cloud-operator';
           id_list.map(id => document.getElementById(id).hidden = (id !== show_id));
           break;
@@ -143,15 +146,15 @@ var jumbotron_buttons = new Vue ({
     },
     getNavbar: function(h) {
       cloud_icon = h('i', { class: "fas fa-cloud mr-2" })
-      nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, data.tabInnerText.cloud_launchable ]);
+      nav_cloud_launchable_btn = h('button', { class: "nav-link active col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_icon, tabInnerText.cloud_launchable ]);
       nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]);
 
       server_icon = h('i', { class: "fas fa-server mr-2" })
-      nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, data.tabInnerText.metal_virt ]);
+      nav_metal_virt_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, tabInnerText.metal_virt ]);
       nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]);
 
       cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" })
-      nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, data.tabInnerText.cloud_operator ]);
+      nav_cloud_operator_btn = h('button', { class: "nav-link col-12 h-100 overflow-hidden", attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ cloud_upload_icon, tabInnerText.cloud_operator ]);
       nav_cloud_operator = h('li', { class: "nav-item col-4" }, [ nav_cloud_operator_btn ]);
 
       navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operator ]);
@@ -159,36 +162,33 @@ var jumbotron_buttons = new Vue ({
     },
     // Add dropdown options of streams
     getStreamName: function(h) {
-      if (data.streamData === null) return;
-      option_default = h('option', { attrs: { value: "testing", selected: "selected" }}, "testing" );
-      selectOptions = h('select', { class: "mx-1" }, [ option_default ]);
+      if (this.streamData === null) return;
+      option_default = h('option', { attrs: { value: "testing", selected: "selected" }}, "testing");
+      option_stable = h('option', { attrs: { value: "stable" }}, "stable");
+      selectOptions = h('select', {
+        class: "mx-1",
+        on: {
+          change: function(e) {
+            coreos_download_app.stream = e.target.value;
+          }
+        }
+      }, [
+        option_default,
+        option_stable
+      ]);
       streamName = h('p', {}, [
         "Stream: ",
         selectOptions,
         " (",
         h('span', {}, [
-          h('a', { attrs: { href: this.getObjectUrl(data.streamData.stream + '.json') } }, "JSON")
+          h('a', { attrs: { href: this.getObjectUrl(this.streamData.stream + '.json') } }, "JSON")
         ]),
         ")",
         "—",
-        h('span', {}, this.timeSince(data.streamData.metadata['last-modified']))
+        h('span', {}, this.timeSince(this.streamData.metadata['last-modified']))
       ]);
       return streamName;
-    }
-  },
-  render: function(h) {
-    navbar = this.getNavbar(h);
-    streamName = this.getStreamName(h);
-    container = h('div', { class: "container" }, [ streamName, navbar ]);
-    return container
-  }
-})
-var coreos_download_app = new Vue({
-  el: '#coreos-download-app',
-  created: function() { this.refreshStream() },
-  data: data,
-  watch: { 'data.stream': 'refreshStream' },
-  methods: {
+    },
     isAws: function(platform) {
       return platform == "aws";
     },
@@ -205,20 +205,20 @@ var coreos_download_app = new Vue({
     // not deep-copy information from `streamData` or elsewhere into
     // `streamDisplay`.
     loadStreamDisplay: function() {
-      data.streamDisplay = {
+      this.streamDisplay = {
         cloudLaunchable: {},
         bareMetal: {},
         virtualized: {},
         cloud: {}
       };
-      if (data.streamData == null) {
+      if (this.streamData == null) {
         return;
       }
-      const architectures = getMember(data.streamData, "architectures");
+      const architectures = getMember(this.streamData, "architectures");
       if (architectures == null) {
         return;
       }
-      const architectureData = getMember(architectures, data.architecture);
+      const architectureData = getMember(architectures, this.architecture);
       if (architectureData == null) {
         return;
       }
@@ -238,11 +238,11 @@ var coreos_download_app = new Vue({
               displayEntries.push({platform: prettyPlatform, region: region, release: release, image: image});
             }
           }
-          Vue.set(data.streamDisplay.cloudLaunchable, platform, {list: displayEntries});
+          Vue.set(this.streamDisplay.cloudLaunchable, platform, {list: displayEntries});
         }
         else {
           const image = getMember(images[platform], "image");
-          Vue.set(data.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});
+          Vue.set(this.streamDisplay.cloudLaunchable, platform, {platform: prettyPlatform, image: image});
         }
       }
       const artifacts = getMember(architectureData, "artifacts");
@@ -267,32 +267,32 @@ var coreos_download_app = new Vue({
               Vue.set(display, platform + "-" + format, displayEntry);
             }
             if (this.isCloudImage(platform)) {
-              addDisplayEntry(data.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(this.streamDisplay.cloud, platform, format, formats, release, prettyPlatform, extension);
             }
             if (this.isVirtualizedImage(platform)) {
-              addDisplayEntry(data.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(this.streamDisplay.virtualized, platform, format, formats, release, prettyPlatform, extension);
             }
             if (this.isBareMetalImage(platform)) {
-              addDisplayEntry(data.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);
+              addDisplayEntry(this.streamDisplay.bareMetal, platform, format, formats, release, prettyPlatform, extension);
             }
           }
         }
       }
     },
     refreshStream: function() {
-      data.loading = true
-      data.streamUrl = baseUrl
-      fetchStreamData(data.streamUrl, data.stream).then(streamData => {
-        data.loading = false;
-        data.streamData = streamData;
+      this.loading = true
+      this.streamUrl = baseUrl
+      fetchStreamData(this.streamUrl, this.stream).then(streamData => {
+        this.loading = false;
+        this.streamData = streamData;
         this.loadStreamDisplay();
       });
     },
     toggleShowSignatureAndSha: function(imageType, platformFormat, contentType) {
-      if (!(platformFormat in data.streamDisplay[imageType])) {
+      if (!(platformFormat in this.streamDisplay[imageType])) {
         return;
       }
-      const artifact = data.streamDisplay[imageType][platformFormat];
+      const artifact = this.streamDisplay[imageType][platformFormat];
       if (!(contentType in artifact.downloads)) {
         return;
       }
@@ -300,10 +300,10 @@ var coreos_download_app = new Vue({
       artifact.downloads[contentType].showSignatureAndSha = !prev;
     },
     showSignatureAndSha: function(imageType, platformFormat, contentType) {
-      if (!(platformFormat in data.streamDisplay[imageType])) {
+      if (!(platformFormat in this.streamDisplay[imageType])) {
         return false;
       }
-      const artifact = data.streamDisplay[imageType][platformFormat];
+      const artifact = this.streamDisplay[imageType][platformFormat];
       if (!(contentType in artifact.downloads)) {
         return false;
       }
@@ -311,10 +311,11 @@ var coreos_download_app = new Vue({
     }
   },
   render: function(h) {
-    if (data.loading) {
-      return h('div', {}, "Loading...");
+    var stream_select_container = h('div', { class: "jumbotron pb-0 pt-3" }, [ h('div', { class: "container" }, [ this.getStreamName(h), this.getNavbar(h) ]) ]);
+    if (this.loading) {
+      return h('div', {}, [ stream_select_container, "Loading..."] );
     }
-    else if (data.streamData) {
+    else if (this.streamData) {
       cloudLaunchableTitle = h('h3', { class:"font-weight-light" }, "Cloud Launchable");
       cloudLaunchableSection = {};
       cloudLaunchable = {};
@@ -328,8 +329,8 @@ var coreos_download_app = new Vue({
       cloudSection = {};
       cloud = {};
 
-      if (data.streamDisplay.cloudLaunchable) {
-        cloudLaunchableSection = h('div', {}, Object.entries(data.streamDisplay.cloudLaunchable).map(function(entry) {
+      if (this.streamDisplay.cloudLaunchable) {
+        cloudLaunchableSection = h('div', {}, Object.entries(this.streamDisplay.cloudLaunchable).map(function(entry) {
           platform = entry[0];
           displayInfo = entry[1];
           if (coreos_download_app.isAws(platform)) {
@@ -340,7 +341,7 @@ var coreos_download_app = new Vue({
                   amiInfo.region ? h('div', {}, [ "(", amiInfo.region, ")" ]) : null,
                   amiInfo.release ? h('div', { class: "ml-2" }, [
                     h('span', {}, [ amiInfo.release, " " ]),
-                    h('span', { class: "text-secondary" }, data.streamData.stream)
+                    h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)
                   ]) : null,
                   amiInfo.image ? h('div', { class: "ml-2" }, [
                     h('a', {
@@ -442,7 +443,7 @@ var coreos_download_app = new Vue({
             displayInfo.extension ? h('div', {}, [ "(", displayInfo.extension, ")" ]) : null,
             displayInfo.release ? h('div', { class: "ml-2" }, [
               h('span', {}, [ displayInfo.release, " " ]),
-              h('span', { class: "text-secondary" }, data.streamData.stream)
+              h('span', { class: "text-secondary" }, coreos_download_app.streamData.stream)
             ]) : null,
             displayInfo.downloads ? h('div', { class: "ml-2" }, [
               createDownloadsSubSection(displayInfo.downloads.disk, 'disk', false, imageType),
@@ -452,24 +453,24 @@ var coreos_download_app = new Vue({
           ]);
         }));
       }
-      if (data.streamDisplay.bareMetal) {
-        bareMetalSection = createArtifactsSection(data.streamDisplay.bareMetal, 'bareMetal');
+      if (this.streamDisplay.bareMetal) {
+        bareMetalSection = createArtifactsSection(this.streamDisplay.bareMetal, 'bareMetal');
       }
       else {
         bareMetalSection = h('div', {}, "No bare metal images found.");
       }
       bareMetal = h('div', { class: "col-12 py-2 my-2" }, [ bareMetalSection ]);
 
-      if (data.streamDisplay.virtualized) {
-        virtualizedSection = createArtifactsSection(data.streamDisplay.virtualized, 'virtualized');
+      if (this.streamDisplay.virtualized) {
+        virtualizedSection = createArtifactsSection(this.streamDisplay.virtualized, 'virtualized');
       }
       else {
         virtualizedSection = h('div', {}, "No virtualized images found.");
       }
       virtualized = h('div', { class: "col-12 py-2 my-2" }, [ virtualizedSection ]);
 
-      if (data.streamDisplay.cloud) {
-        cloudSection = createArtifactsSection(data.streamDisplay.cloud, 'cloud');
+      if (this.streamDisplay.cloud) {
+        cloudSection = createArtifactsSection(this.streamDisplay.cloud, 'cloud');
       }
       else {
         cloudSection = h('div', {}, "No cloud images found.");
@@ -492,6 +493,7 @@ var coreos_download_app = new Vue({
       let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: "cloud-operator", hidden: true } }, [ cloudTitle, verifyBlurb, cloud ]);
 
       return h('div', {}, [
+        stream_select_container,
         cloud_launchable_container,
         metal_virt_container,
         cloud_operators_container

From 28cd6adef88b05c3cfd402d5af6b2e85fb7dc21a Mon Sep 17 00:00:00 2001
From: Robert Fairley <rfairley@redhat.com>
Date: Jan 13 2020 19:21:06 +0000
Subject: [PATCH 5/7] coreos: show last modified only if metadata loaded


Show the last modified timestamp of the metadata only if
metadata loaded successfully (i.e. `streamData.metadata` is
defined).

Signed-off-by: Robert Fairley <rfairley@redhat.com>

---

diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index 48dc568..7f3345c 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -181,11 +181,11 @@ var coreos_download_app = new Vue({
         selectOptions,
         " (",
         h('span', {}, [
-          h('a', { attrs: { href: this.getObjectUrl(this.streamData.stream + '.json') } }, "JSON")
+          h('a', { attrs: { href: this.getObjectUrl(this.stream + '.json') } }, "JSON")
         ]),
         ")",
-        "—",
-        h('span', {}, this.timeSince(this.streamData.metadata['last-modified']))
+        (this.streamData.metadata) ? "—" : null,
+        (this.streamData.metadata) ? h('span', {}, this.timeSince(this.streamData.metadata['last-modified'])) : null
       ]);
       return streamName;
     },

From 18104c2308184a92703dd43b2f9aba8d97b4c4d8 Mon Sep 17 00:00:00 2001
From: Robert Fairley <rfairley@redhat.com>
Date: Jan 13 2020 19:21:19 +0000
Subject: [PATCH 6/7] coreos: fix returned streamData during refreshStream


If streamData is an empty object, set it to `null` so that
`Loading...` text will be displayed in the case of an
unsuccessful fetch, rather than render empty fields for
artifacts.

Co-authored-by: Allen Bai <abai@redhat.com>
Signed-off-by: Robert Fairley <rfairley@redhat.com>

---

diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index 7f3345c..cb96558 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -284,7 +284,7 @@ var coreos_download_app = new Vue({
       this.streamUrl = baseUrl
       fetchStreamData(this.streamUrl, this.stream).then(streamData => {
         this.loading = false;
-        this.streamData = streamData;
+        this.streamData = Object.entries(streamData).length === 0 && streamData.constructor === Object ? null : streamData;
         this.loadStreamDisplay();
       });
     },

From 511036e09d2a6c3496f7da029512718daf3e5e82 Mon Sep 17 00:00:00 2001
From: Robert Fairley <rfairley@redhat.com>
Date: Jan 13 2020 19:21:22 +0000
Subject: [PATCH 7/7] coreos: remove jumbotron class from stream select area


This avoids rendering the buttons around the
stream/artifacts selection as grey, leaving it to
match with the color of the theme.

Signed-off-by: Robert Fairley <rfairley@redhat.com>

---

diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js
index cb96558..36ffedd 100644
--- a/sites/static/js/coreos-download.js
+++ b/sites/static/js/coreos-download.js
@@ -311,7 +311,7 @@ var coreos_download_app = new Vue({
     }
   },
   render: function(h) {
-    var stream_select_container = h('div', { class: "jumbotron pb-0 pt-3" }, [ h('div', { class: "container" }, [ this.getStreamName(h), this.getNavbar(h) ]) ]);
+    var stream_select_container = h('div', { class: "pb-0 pt-3" }, [ h('div', { class: "container" }, [ this.getStreamName(h), this.getNavbar(h) ]) ]);
     if (this.loading) {
       return h('div', {}, [ stream_select_container, "Loading..."] );
     }

