From 4682c25b46996313b2c1fc858d9d4c2a29ed98e4 Mon Sep 17 00:00:00 2001 From: Allen Bai Date: May 15 2020 21:30:22 +0000 Subject: [PATCH 1/5] coreos-download: restyling coreos download page Restyles the Fedora CoreOS Download page based on the initial proposed design https://pagure.io/fedora-websites/issue/964#comment-576026. Moreover, dustymabe and abai thought it could be a good idea to add release stream info and release notes to the top level coreos page: https://getfedora.org/en/coreos/. Signed-off-by: Allen Bai --- diff --git a/sites/getfedora.org/site/coreos/download/index.html b/sites/getfedora.org/site/coreos/download/index.html index 878cca7..fbe5872 100644 --- a/sites/getfedora.org/site/coreos/download/index.html +++ b/sites/getfedora.org/site/coreos/download/index.html @@ -12,7 +12,7 @@

{% trans trimmed %}Download Fedora CoreOS.{% endtrans %}

{% trans trimmed %}Run your containers today in the latest release of Fedora CoreOS.{% endtrans %}
-

{% trans trimmed mailing_link_start=''|safe, tracker_link_start=''|safe, link_end=''|safe %}Subscribe to the {{ mailing_link_start}}coreos-status mailing list{{ link_end }} to receive important operational notices from the Fedora CoreOS team. Give feedback and follow Fedora CoreOS development in the {{ tracker_link_start }}Fedora CoreOS issue tracker{{ link_end }}.{% endtrans %}

+

{% trans trimmed mailing_link_start=''|safe, tracker_link_start=''|safe, link_end=''|safe %}Subscribe to the {{ mailing_link_start}}coreos-status mailing list{{ link_end }} to receive important operational notices from the Fedora CoreOS team. Give feedback and follow Fedora CoreOS development in the {{ tracker_link_start }}Fedora CoreOS issue tracker{{ link_end }}.{% endtrans %}

{% trans trimmed docs_link_start=''|safe, link_end=''|safe %} Start running Fedora CoreOS with the {{ docs_link_start }}Getting Started{{ link_end }} guide. diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js index 339fddc..0795cf1 100644 --- a/sites/static/js/coreos-download.js +++ b/sites/static/js/coreos-download.js @@ -43,6 +43,9 @@ const IdPool = { "metal_virtualized": "metal-virtualized", "cloud_operators": "cloud-operators" } +function isEmptyObj(obj) { + return Object.entries(obj).length === 0 && obj.constructor === Object; +} function getMember(obj, member) { return (member in obj) ? obj[member] : null; } @@ -94,6 +97,8 @@ var coreos_download_app = new Vue({ streamUrl: "", // fetched {stream, metadata, architectures, updates} object from stream.json streamData: null, + // fetched {stream, metadata, architectures, updates} object from stream.json for all streams + streamDataAll: { stable: {}, testing: {}, next: {} }, loading: false, // loaded stream data to render streamDisplay: { @@ -103,9 +108,48 @@ var coreos_download_app = new Vue({ cloud: {} }, }, - watch: { stream: function() { - this.refreshStream(); - } }, + watch: { + stream: function() { + this.refreshStream(); + }, + // watching nested data: https://stackoverflow.com/a/46331968 + "streamDataAll.stable": function(newVal, oldVal) { + if (isEmptyObj(this.streamDataAll.stable)) { + return + } + + stableReleaseVersion = "v " + this.streamDataAll.stable.architectures.x86_64.artifacts.metal.release; + $("#stable-version").text(stableReleaseVersion); + + $("#stable-json").empty(); + $("#stable-json").append(`JSON`); + $("#stable-json").append(` — ${this.timeSince(this.streamDataAll.stable.metadata['last-modified'])}`); + }, + "streamDataAll.testing": function(newVal, oldVal) { + if (isEmptyObj(this.streamDataAll.testing)) { + return + } + + testingReleaseVersion = isEmptyObj(this.streamDataAll.testing) ? "" : "v " + this.streamDataAll.testing.architectures.x86_64.artifacts.metal.release; + $("#testing-version").text(testingReleaseVersion); + + $("#testing-json").empty(); + $("#testing-json").append(`JSON`); + $("#testing-json").append(` — ${this.timeSince(this.streamDataAll.testing.metadata['last-modified'])}`); + }, + "streamDataAll.next": function(newVal, oldVal) { + if (isEmptyObj(this.streamDataAll.next)) { + return + } + + nextReleaseVersion = isEmptyObj(this.streamDataAll.next) ? "" : "v " + this.streamDataAll.next.architectures.x86_64.artifacts.metal.release; + $("#next-version").text(nextReleaseVersion); + + $("#next-json").empty(); + $("#next-json").append(`JSON`); + $("#next-json").append(` — ${this.timeSince(this.streamDataAll.next.metadata['last-modified'])}`); + } + }, methods: { getObjectUrl: function(path) { return getArtifactUrl(this.streamUrl, path); @@ -154,15 +198,15 @@ var coreos_download_app = new Vue({ getNavbar: function(h) { cloud_icon = h('i', { class: "fas fa-cloud mr-2" }) nav_cloud_launchable_btn = 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 } }, [ cloud_icon, tabInnerText.cloud_launchable ]); - nav_cloud_launchable = h('li', { class: "nav-item col-4" }, [ nav_cloud_launchable_btn ]); + nav_cloud_launchable = h('li', { class: "nav-item col-12 col-sm-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".concat(this.shownId === IdPool.metal_virtualized ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, tabInnerText.metal_virtualized ]); - nav_metal_virt = h('li', { class: "nav-item col-4" }, [ nav_metal_virt_btn ]); + nav_metal_virt = h('li', { class: "nav-item col-12 col-sm-4" }, [ nav_metal_virt_btn ]); cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" }) nav_cloud_operators_btn = 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 } }, [ cloud_upload_icon, tabInnerText.cloud_operators ]); - nav_cloud_operators = h('li', { class: "nav-item col-4" }, [ nav_cloud_operators_btn ]); + nav_cloud_operators = h('li', { class: "nav-item col-12 col-sm-4" }, [ nav_cloud_operators_btn ]); navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operators ]); return navbar; @@ -171,36 +215,156 @@ var coreos_download_app = new Vue({ getStreamName: function(h) { const self = this; if (this.streamData === null) return; - option_stable = h('option', { attrs: { value: "stable", selected: this.stream === "stable" ? "selected" : null }}, "stable"); - option_testing = h('option', { attrs: { value: "testing", selected: this.stream === "testing" ? "selected" : null }}, "testing"); - option_next = h('option', { attrs: { value: "next", selected: this.stream === "next" ? "selected" : null }}, "next"); - selectOptions = h('select', { - class: "mx-1", + + function onClick(e) { + const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; + const currentShownKey = Object.keys(IdPool).find(key => IdPool[key] === self.shownId); + coreos_download_app.stream = e.target.value; + history.pushState(null, null, `${downloadPageUrl}?tab=${currentShownKey}&stream=${coreos_download_app.stream}`); + } + + btn_dropdown_toggle = h('button', { class: "btn btn-sm bg-gray-200 dropdown-toggle py-0", attrs: { type: "button", id: "dropdownMenuStreams", "data-toggle": "dropdown", "aria-haspopup": true, "aria-expanded": false } }, self.stream ); + btn_dropdown_stable = h('button', { class: "dropdown-item", attrs: { type: "button", value: "stable" }, on: { click: onClick } }, "stable"); + btn_dropdown_testing = h('button', { class: "dropdown-item", attrs: { type: "button", value: "testing" }, on: { click: onClick } }, "testing"); + btn_dropdown_next = h('button', { class: "dropdown-item", attrs: { type: "button", value: "next" }, on: { click: onClick } }, "next"); + div_dropdown_menu = h('div', { class: "dropdown-menu", attrs: { "aria-labelledby": "dropdownMenuStreams" } }, [ btn_dropdown_stable, btn_dropdown_testing, btn_dropdown_next ]); + div_dropdown = h('div', { class: "d-inline dropdown ml-2" }, [ btn_dropdown_toggle, div_dropdown_menu ]) + + streamName = h('p', { class: "mt-5 mb-2 ml-3" }, [ + "Currently Selected Stream:", + div_dropdown ]); + return streamName; + }, + // Introduction section for streams + getStreamIntro: function(h) { + title = h('h2', { class: "font-weight-light text-center pb-3" }, "Fedora CoreOS is available across 3 different release streams:"); + + if (this.loading) { + return title; + } + + viewAllStreamsBtn = h('button', + { + class: "d-block mx-auto mb-5 py-1 btn btn-sm btn-fedora-purple", + on: { + click: function(e) { + e.preventDefault(); + window.open("https://builds.coreos.fedoraproject.org/browser"); + } + } + }, "View All Streams"); + + // Release info section with three tabs: stable, testing, next + // NOTE: in order for the button line up at the same horizontal level, add different mb for

elements. + stableIcon = h("i", { + 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); + 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") + ]), + (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 ]) + + 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 btn btn-sm btn-fedora-blue", on: { - change: function(e) { - const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; - const currentShownKey = Object.keys(IdPool).find(key => IdPool[key] === self.shownId); - coreos_download_app.stream = e.target.value; - history.pushState(null, null, `${downloadPageUrl}?tab=${currentShownKey}&stream=${coreos_download_app.stream}`); + click: function(e) { + e.preventDefault(); + window.open("https://builds.coreos.fedoraproject.org/browser?stream=stable"); } } - }, [ - option_stable, - option_testing, - option_next + }, "View Stable Releases"); + + // 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); + 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") + ]), + (isEmptyObj(this.streamDataAll.testing)) ? null : " — ", + (isEmptyObj(this.streamDataAll.testing)) ? null : h('span', { class: "font-weight-normal" }, this.timeSince(this.streamDataAll.testing.metadata['last-modified'])) ]); - streamName = h('p', {}, [ - "Stream: ", - selectOptions, - " (", + 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 btn btn-sm btn-fedora-green", + on: { + click: function(e) { + e.preventDefault(); + window.open("https://builds.coreos.fedoraproject.org/browser?stream=testing"); + } + } + }, "View Testing Releases"); + + // 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); + nextJSON = h('p', { class: "text-gray-500", attrs: { id: "next-json" } }, [ h('span', {}, [ - h('a', { attrs: { href: this.getObjectUrl(this.stream + '.json') } }, "JSON") + h('a', { class: "font-weight-bold text-gray-500", attrs: { href: `${baseUrl}/next.json` } }, "JSON") ]), - ")", - (this.streamData.metadata) ? "—" : null, - (this.streamData.metadata) ? h('span', {}, this.timeSince(this.streamData.metadata['last-modified'])) : null + (isEmptyObj(this.streamDataAll.next)) ? null : " — ", + (isEmptyObj(this.streamDataAll.next)) ? null : h('span', { class: "font-weight-normal" }, this.timeSince(this.streamDataAll.next.metadata['last-modified'])) ]); - return streamName; + 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 btn btn-sm btn-fedora-orange", + on: { + click: function(e) { + e.preventDefault(); + window.open("https://builds.coreos.fedoraproject.org/browser?stream=next"); + } + } + }, "View Next Releases"); + + stableDiv = h('div', { + class: "col-12 col-lg-4 border-left border-fedora-blue pt-3", + style: { + "border-width": "10px !important", + } + }, [stableHeadingContainer, stableIntroText, stableReleaseLink]) + testingDiv = h('div', { + class: "col-12 col-lg-4 border-left border-fedora-green pt-3", + style: { + "border-width": "10px !important", + } + }, [testingHeadingContainer, testingIntroText, testingReleaseLink]) + nextDiv = h('div', { + class: "col-12 col-lg-4 border-left border-fedora-orange pt-3", + style: { + "border-width": "10px !important", + } + }, [nextHeadingContainer, nextIntroText, nextReleaseLink]) + + streamsIntroDiv = h('div', { class: "row my-3" }, [stableDiv, testingDiv, nextDiv]); + wrapper_div = h('div', {}, [title, viewAllStreamsBtn, streamsIntroDiv]); + return wrapper_div; }, isAws: function(platform) { return platform == "aws"; @@ -319,13 +483,28 @@ var coreos_download_app = new Vue({ } }, refreshStream: function() { - this.loading = true - this.streamUrl = baseUrl - fetchStreamData(this.streamUrl, this.stream).then(streamData => { - this.loading = false; - this.streamData = Object.entries(streamData).length === 0 && streamData.constructor === Object ? null : streamData; - this.loadStreamDisplay(); - }); + 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(); + }) }, getSignatureAndShaModal: function(h) { return h('div', { class: "modal", attrs: { id: "signatureAndShaModal", tabindex: "-1", role: "dialog", "aria-labelledby": "signatureAndShaModalLabel", "aria-hidden": "true" }}, [ @@ -393,12 +572,14 @@ var coreos_download_app = new Vue({ history.pushState(null, null, `${downloadPageUrl}?${searchParams.toString()}`); var signature_sha256_verification_modal = this.getSignatureAndShaModal(h); - var stream_select_container = h('div', { class: "pb-0 pt-3 mb-3" }, [ h('div', { class: "container" }, [ this.getStreamName(h), this.getNavbar(h) ]) ]); + h1_title = h('h1', { class: "font-weight-light text-center my-5" }, "Download Fedora CoreOS"); + stream_select_container = h('div', { class: "pb-0 pt-3 mb-3" }, [ this.getStreamIntro(h), this.getStreamName(h), this.getNavbar(h) ]); if (this.loading) { - return h('div', {}, [ stream_select_container, "Loading..."] ); + stream_info_div = h('div', { class: "bg-light pt-3" }, [ h('div', { class: "container font-weight-light" }, [ stream_select_container ]) ]); + download_div = h('div', { class: "bg-white pb-5" }, [ h('div', { class: "container font-weight-light" }, "Loading...") ]); + return h('div', {}, [ h1_title, stream_info_div, download_div ]); } else if (this.streamData) { - cloudLaunchableTitle = h('h3', { class:"font-weight-light" }, "Cloud Launchable"); cloudLaunchableSection = {}; cloudLaunchable = {}; virtualizedTitle = h('h3', { class:"font-weight-light" }, "Virtualized"); @@ -407,7 +588,6 @@ var coreos_download_app = new Vue({ bareMetalTitle = h('h3', { class:"font-weight-light" }, "Bare Metal"); bareMetalSection = {}; bareMetal = {}; - cloudTitle = h('h3', { class:"font-weight-light" }, "For Cloud Operators"); cloudSection = {}; cloud = {}; @@ -450,6 +630,11 @@ var coreos_download_app = new Vue({ cloudLaunchable = h('div', { class: "col-12 py-2 my-2" }, [ cloudLaunchableSection ]); function createDownloadsSubSection(displayDownloads, contentType, showTitle, imageType) { + verifyBlurb = + `

+ Verify your download using the detached signature after importing Fedora's GPG signing keys. + 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. +
` return displayDownloads ? h('div', { class: "pb-2" }, [ showTitle ? h('span', {}, contentType + ": ") : null, displayDownloads.location ? h('span', {}, [ @@ -486,6 +671,7 @@ var coreos_download_app = new Vue({ .html("signature"); } $(p).appendTo("#modal-body"); + $(verifyBlurb).appendTo("#modal-body"); // Download the Checksum file and Signature let ol = document.createElement('ol'); @@ -594,31 +780,28 @@ var coreos_download_app = new Vue({ } cloud = h('div', { class: "col-12 py-2 my-2" }, [ cloudSection ]); - verifyBlurb = h('div', {}, [ - h('div', { class:"font-weight-light" }, [ - "Verify your download using the detached signature after importing ", - h('a', { attrs: { href: "https://getfedora.org/security/" } }, "Fedora's GPG signing keys"), - ". 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." - ]) - ]); - - let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, verifyBlurb, bareMetal ]); - let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, verifyBlurb, virtualized ]); + let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, bareMetal ]); + let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, virtualized ]); - let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [ cloudLaunchableTitle, cloudLaunchable ]); + let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [ cloudLaunchable ]); let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: IdPool.metal_virtualized, hidden: this.shownId !== IdPool.metal_virtualized } }, [ bare_metal_container, virtualized_container ]); - let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_operators, hidden: this.shownId !== IdPool.cloud_operators } }, [ cloudTitle, verifyBlurb, cloud ]); - - return h('div', {}, [ - signature_sha256_verification_modal, - stream_select_container, - cloud_launchable_container, - metal_virt_container, - cloud_operators_container - ]); + let cloud_operators_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_operators, hidden: this.shownId !== IdPool.cloud_operators } }, [ cloud ]); + + stream_info_div = h('div', { class: "bg-light pt-3" }, [ h('div', { class: "container font-weight-light" }, [ stream_select_container ]) ]); + download_div = h('div', { class: "bg-white pb-5" }, [ + h('div', { class: "container font-weight-light" }, [ + signature_sha256_verification_modal, + cloud_launchable_container, + metal_virt_container, + cloud_operators_container + ]) + ]) + + return h('div', {}, [h1_title, stream_info_div, download_div]); } else { - return h('div', {}, "No stream data found!"); + error_div = h('div', { class: "bg-transparent py-5" }, [ h('div', { class: "container font-weight-light" }, "No stream data found!") ]); + return h('div', {}, [ error_div ]); } } }) From e65b16a8e7d85490a33a20c2140d0411ddfaf83a Mon Sep 17 00:00:00 2001 From: Allen Bai Date: May 15 2020 21:30:22 +0000 Subject: [PATCH 2/5] coreos-download: add release note section Adds a release note section on the overview page of Fedora CoreOS Download. This gives users general information about the last 5 releases for each of stable, testing and next stream. For each release, list the versions of important packages and package information about what was added, removed, upgraded and downgraded. In addition, for future improvements, adding notes on bugfix information might also be helpful. Signed-off-by: Allen Bai --- diff --git a/sites/getfedora.org/site/coreos/download/index.html b/sites/getfedora.org/site/coreos/download/index.html index fbe5872..f4accf9 100644 --- a/sites/getfedora.org/site/coreos/download/index.html +++ b/sites/getfedora.org/site/coreos/download/index.html @@ -6,26 +6,7 @@ {% block content %} {{submenu(edition="coreos", active="download")}} -
-
-
-
-

{% trans trimmed %}Download Fedora CoreOS.{% endtrans %}

-
{% trans trimmed %}Run your containers today in the latest release of Fedora CoreOS.{% endtrans %}
-

{% trans trimmed mailing_link_start=''|safe, tracker_link_start=''|safe, link_end=''|safe %}Subscribe to the {{ mailing_link_start}}coreos-status mailing list{{ link_end }} to receive important operational notices from the Fedora CoreOS team. Give feedback and follow Fedora CoreOS development in the {{ tracker_link_start }}Fedora CoreOS issue tracker{{ link_end }}.{% endtrans %}

-

- {% trans trimmed docs_link_start=''|safe, link_end=''|safe %} - Start running Fedora CoreOS with the {{ docs_link_start }}Getting Started{{ link_end }} guide. - {% endtrans %} -

-
-
-
-
-
-
-
-
+
{% endblock %} {% block js %} diff --git a/sites/getfedora.org/site/coreos/index.html b/sites/getfedora.org/site/coreos/index.html index b7562c9..af9e373 100644 --- a/sites/getfedora.org/site/coreos/index.html +++ b/sites/getfedora.org/site/coreos/index.html @@ -5,38 +5,44 @@ {% block content %} {{submenu(edition="coreos", active="overview")}} -
+
-
-
-

Fedora CoreOS.

-
{% trans trimmed %}Automatically updating Linux OS for containerized workloads.{% endtrans %}
- {% trans trimmed %}Download Now{% endtrans %} +
+
+

Getting Started with Fedora CoreOS

+
+ {% trans trimmed %} + Fedora CoreOS is an automatically-updating, minimal operating system for running containerized workloads securely and at scale. It is currently available on multiple platforms, with more coming soon. + {% endtrans %} +
-
-
-
-
-
-
-
-
-
- {% trans trimmed %} - Fedora CoreOS is an automatically-updating, minimal operating system for running containerized workloads securely and at scale. It is currently available on multiple platforms, with more coming soon. + +
+

+ {% trans trimmed docs_link_start=''|safe, mailing_link_start=''|safe, tracker_link_start=''|safe, link_end=''|safe %} + Subscribe to the {{ mailing_link_start}}coreos-status mailing list{{ link_end }} to receive important operational notices from the Fedora CoreOS team. + Give feedback and follow Fedora CoreOS development in the {{ tracker_link_start }}Fedora CoreOS issue tracker{{ link_end }}. + To get started with running Fedora CoreOS, see the {{ docs_link_start }}Getting Started{{ link_end }} guide. {% endtrans %} +

- -
-
-

Getting Started with Fedora CoreOS

- {% trans trimmed docs_link_start=''|safe, link_end=''|safe %} - To get started with running Fedora CoreOS, see the {{ docs_link_start }}Getting Started{{ link_end }} guide. - {% endtrans %} +
+
+

Release Notes

+
+{% endblock %} +{% block js %} + + + +{{ super() }} {% endblock %} diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js index 0795cf1..ebaed32 100644 --- a/sites/static/js/coreos-download.js +++ b/sites/static/js/coreos-download.js @@ -188,7 +188,7 @@ var coreos_download_app = new Vue({ const val = pair[1]; if (val === e.target.innerText) { const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; - history.pushState(null, null, `${downloadPageUrl}?tab=${key}&stream=${coreos_download_app.stream}`); + history.replaceState(null, null, `${downloadPageUrl}?tab=${key}&stream=${coreos_download_app.stream}`); const show_id = IdPool[key]; id_list.map(id => document.getElementById(id).hidden = (id !== show_id)); this.shownId = show_id; @@ -220,7 +220,7 @@ var coreos_download_app = new Vue({ const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; const currentShownKey = Object.keys(IdPool).find(key => IdPool[key] === self.shownId); coreos_download_app.stream = e.target.value; - history.pushState(null, null, `${downloadPageUrl}?tab=${currentShownKey}&stream=${coreos_download_app.stream}`); + history.replaceState(null, null, `${downloadPageUrl}?tab=${currentShownKey}&stream=${coreos_download_app.stream}`); } btn_dropdown_toggle = h('button', { class: "btn btn-sm bg-gray-200 dropdown-toggle py-0", attrs: { type: "button", id: "dropdownMenuStreams", "data-toggle": "dropdown", "aria-haspopup": true, "aria-expanded": false } }, self.stream ); @@ -530,6 +530,9 @@ var coreos_download_app = new Vue({ } }, render: function(h) { + if(window.location.href.match(/^.*\/coreos\/download/) == null) { + return + } const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; searchParams = new URLSearchParams(window.location.search); // switch to specified tab if `tab` parameter is set @@ -569,7 +572,7 @@ var coreos_download_app = new Vue({ searchParams.set('stream', 'stable'); } // Update the url with the parameters - history.pushState(null, null, `${downloadPageUrl}?${searchParams.toString()}`); + history.replaceState(null, null, `${downloadPageUrl}?${searchParams.toString()}`); var signature_sha256_verification_modal = this.getSignatureAndShaModal(h); h1_title = h('h1', { class: "font-weight-light text-center my-5" }, "Download Fedora CoreOS"); @@ -780,8 +783,8 @@ var coreos_download_app = new Vue({ } cloud = h('div', { class: "col-12 py-2 my-2" }, [ cloudSection ]); - let bare_metal_container = h('div', { class: "col-6" }, [ bareMetalTitle, bareMetal ]); - let virtualized_container = h('div', { class: "col-6" }, [ virtualizedTitle, virtualized ]); + let bare_metal_container = h('div', { class: "col-lg-6" }, [ bareMetalTitle, bareMetal ]); + let virtualized_container = h('div', { class: "col-lg-6" }, [ virtualizedTitle, virtualized ]); let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [ cloudLaunchable ]); let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: IdPool.metal_virtualized, hidden: this.shownId !== IdPool.metal_virtualized } }, [ bare_metal_container, virtualized_container ]); diff --git a/sites/static/js/coreos-release-notes.js b/sites/static/js/coreos-release-notes.js new file mode 100644 index 0000000..5a04011 --- /dev/null +++ b/sites/static/js/coreos-release-notes.js @@ -0,0 +1,316 @@ +// There are three layers of async fetch calls +// - builds.json +// - meta.json +// - commitmeta.json + +// Originally the `loading` variable is set to false after builds.json +// has been fetched, but since we are using runtime only build and not using +// Vue templates, watching the builds list and re-render each time a metadata +// has been fetched seems to be a large overhead. +// Therefore, in this implementation, only render the page after fetching all +// of the three json files in the first place, and re-render if the user changes +// stream. + +// NOTE: set the `initialBuildsShown` to 5 means only 5 builds will be rendered, +// which means the overhead for waiting for fetching 5 builds might not be large +// (~40ms for fetching all jsons). + +const baseUrl = 'https://builds.coreos.fedoraproject.org/streams' +const baseProdUrl = 'https://builds.coreos.fedoraproject.org/prod/streams' +const baseDevelUrl = 'https://builds.coreos.fedoraproject.org/devel/streams' + +const initialBuildsShown = 5; + +// pkgdiff enum to str +const diffType = ["added", "removed", "upgraded", "downgraded"]; +const importantPkgs = ["kernel", "systemd", "rpm-ostree", "ignition", "podman"]; + +function timestampToPrettyString(date) { + date = new Date(date); + const year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date); + const month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date); + const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date); + + return `${month} ${day}, ${year}`; +} + +function getBaseUrl(stream, developer) { + return stream != "developer" + ? `${baseProdUrl}/${stream}` + : `${baseDevelUrl}/${developer}`; +} + +// function fetchStreamData(base, stream) { +// return fetch(`${base}/${stream}.json`) +// .then(response => response.ok ? response.json() : {}); +// } + +// function isEmptyObj(obj) { +// return Object.entries(obj).length === 0 && obj.constructor === Object; +// } + +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; + } +} + +function findImportantPkgs(commitmeta) { + var r = []; + commitmeta["rpmostree.rpmdb.pkglist"].forEach(pkg => { + if (importantPkgs.includes(pkg[0])) { + r.push(pkg); + } + }); + return r; +} + +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}))]; + } + }); +} + +function fetchBuild(base, build, legacy) { + return fetchBuildMeta(base, build, legacy).then(result => { + [basearch, meta] = result; + sortPkgDiff(meta); + // show the build metadata + build.meta = meta; + // and fetch extra commit metadata in async + return fetchBuildCommitMeta(base, build, basearch, legacy).then(commitmeta => { + commitmeta["importantPkgs"] = findImportantPkgs(commitmeta); + commitmeta["showImportantPkgsOnly"] = true; + build.commitmeta = commitmeta; + }); + }); +} + +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() : {}])); + } + // 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() : {}])); + + // return Promise.all(build.arches.map(arch => { + // fetch(`${base}/${build.id}/${arch}/meta.json`) + // .then(response => Promise.all([arch, response.ok ? response.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}/${basearch}/commitmeta.json`) + .then(response => response.ok ? response.json() : {}); +} + +var coreos_release_notes = new Vue({ + el: '#coreos-release-notes', + created: function() { this.refreshBuilds() }, + data: { + // source of truth for streams + streamList: ['stable', 'testing', 'next'], + // currently selected stream + stream: 'stable', + // if current stream is "developer", currently entered developer + developer: "", + // current url to builds/ dir for stream + buildsUrl: "", + // whether the currently selected stream has a legacy layout + // https://github.com/coreos/coreos-assembler/pull/580 + legacy: false, + // list of {id, arches, meta, commitmeta} build objects + // XXX: in non-legacy mode, meta and commitmeta are those + // of the first arch, but in the future these would be e.g. + // meta[arch] and commitmeta[arch] + builds: [], + // list of unshown {id, arches, meta, commitmeta} build objects + unshown_builds: [], + // toggles "Loading..." + loading: true + }, + watch: { + stream: function() { + this.refreshBuilds(); + } + }, + methods: { + 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]}`; + }, + getPkgEvra: function(tuple) { + return `${tuple[1]}.${tuple[2]}`; + }, + getNavbar: function(h) { + const self = this; + const changeStream = e => { + if (e.target.innerText === "Stable Stream") { + self.stream = "stable" + } + if (e.target.innerText === "Testing Stream") { + self.stream = "testing" + } + if (e.target.innerText === "Next Stream") { + self.stream = "next" + } + } + 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 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 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 navbar = h('ul', { class: "nav nav-tabs" }, [ navStable, navTesting, navNext ]); + return navbar; + }, + getReleaseNoteCards: function(h) { + const self = this; + // check if all build metadata has been fetched + if (self.loading) { + return + } + + rows = []; + self.builds.forEach((build, idx) => { + // Left pane consists of Build ID and Arch info + let headingListArches = []; + let headingBuildId = h('h5', { class: "font-weight-bold" }, build.id); + build.arches.forEach((arch, _) => { + headingListArches.push(h('h6', {}, arch)); + }); + 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'])}`); + // List of important packages and versions + let importantPkgsElements = []; + build.commitmeta.importantPkgs.forEach((pkg, _) => { + importantPkgsElements.push(pkg[0]); + importantPkgsElements.push(h('span', { class: "mr-2 badge badge-pill badge-light" }, pkg[2])); + }); + + // Added package list + let addedPkgsElementsList = []; + let addedPkgsHeading = []; + if (build.meta.pkgdiff != null && build.meta.pkgdiff.added.length > 0) { + build.meta.pkgdiff.added.forEach((pkg, _) => { + addedPkgsElementsList.push(h('li', {}, self.getPkgNevra(pkg[2]["NewPackage"]))); + }); + addedPkgsHeading = h('p', { class: "mt-3" }, "Added:") + } + let addedPkgsElements = h('div', {}, [ addedPkgsHeading, h('ul', {}, addedPkgsElementsList) ]); + + // Removed package list + let removedPkgsElementsList = []; + let removedPkgsHeading = []; + if (build.meta.pkgdiff != null && build.meta.pkgdiff.removed.length > 0) { + build.meta.pkgdiff.removed.forEach((pkg, _) => { + removedPkgsElementsList.push(h('li', {}, self.getPkgNevra(pkg[2]["PreviousPackage"]))); + }); + removedPkgsHeading = h('p', {}, "Removed:"); + } + let removedPkgsElements = h('div', {}, [ removedPkgsHeading, h('ul', {}, removedPkgsElementsList) ]); + + // Upgraded package list + let upgradedPkgsElementsList = []; + let upgradedPkgsHeading = []; + if (build.meta.pkgdiff != null && build.meta.pkgdiff.upgraded.length > 0) { + build.meta.pkgdiff.upgraded.forEach((pkg, _) => { + upgradedPkgsElementsList.push(h('li', {}, `${pkg[2]["PreviousPackage"][0]} ${self.getPkgEvra(pkg[2]["PreviousPackage"])} → ${self.getPkgEvra(pkg[2]["NewPackage"])}`)); + }); + upgradedPkgsHeading = h('p', {}, "Upgraded:"); + } + let upgradedPkgsElements = h('div', {}, [ upgradedPkgsHeading, h('ul', {}, upgradedPkgsElementsList) ]); + + // Downgraded package list + let downgradedPkgsElementsList = []; + let downgradedPkgsHeading = []; + if (build.meta.pkgdiff != null && build.meta.pkgdiff.downgraded.length > 0) { + build.meta.pkgdiff.downgraded.forEach((pkg, _) => { + downgradedPkgsElementsList.push(h('li', {}, `${pkg[2]["PreviousPackage"][0]} ${self.getPkgEvra(pkg[2]["PreviousPackage"])} → ${self.getPkgEvra(pkg[2]["NewPackage"])}`)); + }); + downgradedPkgsHeading = h('p', {}, "Downgraded:"); + } + let downgradedPkgsElements = h('div', {}, [ downgradedPkgsHeading, h('ul', {}, downgradedPkgsElementsList) ]); + + let rightPane = h('div', { class: "col-lg-10 border-bottom mb-5 pb-3" }, [ date, importantPkgsElements, 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.buildsUrl = getBaseUrl(this.stream, this.developer) + "/builds" + fetchBuilds(this.buildsUrl).then(result => { + [legacy, builds] = result; + // first populate and show the build list + this.legacy = legacy; + this.builds = []; + this.unshown_builds = []; + let counter = 0; + + // and now fetch each build info async + builds.forEach((build, idx) => { + if (idx < initialBuildsShown) { + this.builds.push(build); + fetchBuild(this.buildsUrl, build, this.legacy) + .then(() => { + counter++; + if (counter === builds.length) { + // fetched all metadata + this.loading = false; + } + }) + } else { + this.unshown_builds.push(build); + counter++; + if (counter === builds.length) { + // fetched all metadata + this.loading = false; + } + } + }) + }); + } + }, + render: function(h) { + 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 ]); + } else { + let releaseNoteCards = this.getReleaseNoteCards(h); + return h('div', {}, [ navBar, releaseNoteCards ]); + } + } +}); From 3bd77eaa7e26c95563397656b5bddd6408dd0a12 Mon Sep 17 00:00:00 2001 From: Allen Bai Date: May 15 2020 21:30:22 +0000 Subject: [PATCH 3/5] coreos-download: use releases instead of builds for release note Fixes the issue that releases are subset of builds and the displayed information should be between releases instead of builds. For example, if there are three builds between two releases, the pkgdiff between releases should be accumulated among all three builds in between plus the pkgdiff between last build and the current release. Also cleaned up naming inconsistencies in coreos-download.js, use camelCase naming convention if possible. Also adds comments on functions and hides buttons for links to fcos release browser for security and consistency concerns, since the release browser is subject to change without notice. Signed-off-by: Allen Bai --- diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js index ebaed32..89b18e8 100644 --- a/sites/static/js/coreos-download.js +++ b/sites/static/js/coreos-download.js @@ -181,37 +181,42 @@ var coreos_download_app = new Vue({ 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) { - const id_list = Object.values(IdPool); + const idList = Object.values(IdPool); Object.entries(tabInnerText).map(pair => { const key = pair[0]; const val = pair[1]; if (val === e.target.innerText) { const downloadPageUrl = window.location.href.match(/^.*\/coreos\/download/)[0]; history.replaceState(null, null, `${downloadPageUrl}?tab=${key}&stream=${coreos_download_app.stream}`); - const show_id = IdPool[key]; - id_list.map(id => document.getElementById(id).hidden = (id !== show_id)); - this.shownId = show_id; + const showId = IdPool[key]; + idList.map(id => document.getElementById(id).hidden = (id !== showId)); + this.shownId = showId; } }); }, + // Render a navbar section getNavbar: function(h) { - cloud_icon = h('i', { class: "fas fa-cloud mr-2" }) - nav_cloud_launchable_btn = 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 } }, [ cloud_icon, tabInnerText.cloud_launchable ]); - nav_cloud_launchable = h('li', { class: "nav-item col-12 col-sm-4" }, [ nav_cloud_launchable_btn ]); + 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 ]); - 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".concat(this.shownId === IdPool.metal_virtualized ? " active" : ""), attrs: { "data-toggle": "tab" }, on: { click: this.toggleHidden } }, [ server_icon, tabInnerText.metal_virtualized ]); - nav_metal_virt = h('li', { class: "nav-item col-12 col-sm-4" }, [ nav_metal_virt_btn ]); + 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 ]); - cloud_upload_icon = h('i', { class: "fas fa-cloud-upload-alt mr-2" }) - nav_cloud_operators_btn = 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 } }, [ cloud_upload_icon, tabInnerText.cloud_operators ]); - nav_cloud_operators = h('li', { class: "nav-item col-12 col-sm-4" }, [ nav_cloud_operators_btn ]); + 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 ]); - navbar = h('ul', { class: "nav nav-tabs" }, [ nav_cloud_launchable, nav_metal_virt, nav_cloud_operators ]); + navbar = h('ul', { class: "nav nav-tabs" }, [ navCloudLaunchable, navMetalVirt, navCloudOperators ]); return navbar; }, - // Add dropdown options of streams + // Render a dropdown list with options of streams getStreamName: function(h) { const self = this; if (this.streamData === null) return; @@ -223,19 +228,19 @@ var coreos_download_app = new Vue({ history.replaceState(null, null, `${downloadPageUrl}?tab=${currentShownKey}&stream=${coreos_download_app.stream}`); } - btn_dropdown_toggle = h('button', { class: "btn btn-sm bg-gray-200 dropdown-toggle py-0", attrs: { type: "button", id: "dropdownMenuStreams", "data-toggle": "dropdown", "aria-haspopup": true, "aria-expanded": false } }, self.stream ); - btn_dropdown_stable = h('button', { class: "dropdown-item", attrs: { type: "button", value: "stable" }, on: { click: onClick } }, "stable"); - btn_dropdown_testing = h('button', { class: "dropdown-item", attrs: { type: "button", value: "testing" }, on: { click: onClick } }, "testing"); - btn_dropdown_next = h('button', { class: "dropdown-item", attrs: { type: "button", value: "next" }, on: { click: onClick } }, "next"); - div_dropdown_menu = h('div', { class: "dropdown-menu", attrs: { "aria-labelledby": "dropdownMenuStreams" } }, [ btn_dropdown_stable, btn_dropdown_testing, btn_dropdown_next ]); - div_dropdown = h('div', { class: "d-inline dropdown ml-2" }, [ btn_dropdown_toggle, div_dropdown_menu ]) + btnDropdownToggle = h('button', { class: "btn btn-sm bg-gray-200 dropdown-toggle py-0", attrs: { type: "button", id: "dropdownMenuStreams", "data-toggle": "dropdown", "aria-haspopup": true, "aria-expanded": false } }, self.stream ); + btnDropdownStable = h('button', { class: "dropdown-item", attrs: { type: "button", value: "stable" }, on: { click: onClick } }, "stable"); + btnDropdownTesting = h('button', { class: "dropdown-item", attrs: { type: "button", value: "testing" }, on: { click: onClick } }, "testing"); + btnDropdownNext = h('button', { class: "dropdown-item", attrs: { type: "button", value: "next" }, on: { click: onClick } }, "next"); + divDropdownMenu = h('div', { class: "dropdown-menu", attrs: { "aria-labelledby": "dropdownMenuStreams" } }, [ btnDropdownStable, btnDropdownTesting, btnDropdownNext ]); + divDropdown = h('div', { class: "d-inline dropdown ml-2" }, [ btnDropdownToggle, divDropdownMenu ]) streamName = h('p', { class: "mt-5 mb-2 ml-3" }, [ "Currently Selected Stream:", - div_dropdown ]); + divDropdown ]); return streamName; }, - // Introduction section for streams + // Introduction section for streams, the section above the navigation bar for platforms getStreamIntro: function(h) { title = h('h2', { class: "font-weight-light text-center pb-3" }, "Fedora CoreOS is available across 3 different release streams:"); @@ -255,7 +260,7 @@ var coreos_download_app = new Vue({ }, "View All Streams"); // Release info section with three tabs: stable, testing, next - // NOTE: in order for the button line up at the same horizontal level, add different mb for

elements. + // NOTE: in order for the button line up at the same horizontal level, use a fixed height `9em` for

elements of stream summaries. stableIcon = h("i", { class: "fas fa-shield-alt fa-2x rounded-circle bg-fedora-blue text-white p-3 ml-4", }, ""); @@ -273,16 +278,17 @@ var coreos_download_app = new Vue({ 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 btn btn-sm btn-fedora-blue", - on: { - click: function(e) { - e.preventDefault(); - window.open("https://builds.coreos.fedoraproject.org/browser?stream=stable"); - } - } - }, "View Stable Releases"); + // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time + // stableReleaseLink = h('button', + // { + // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-blue", + // on: { + // click: function(e) { + // e.preventDefault(); + // window.open("https://builds.coreos.fedoraproject.org/browser?stream=stable"); + // } + // } + // }, "View Stable Releases"); // then Testing stream testingIcon = h("i", { @@ -302,16 +308,17 @@ var coreos_download_app = new Vue({ 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 btn btn-sm btn-fedora-green", - on: { - click: function(e) { - e.preventDefault(); - window.open("https://builds.coreos.fedoraproject.org/browser?stream=testing"); - } - } - }, "View Testing Releases"); + // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time + // testingReleaseLink = h('button', + // { + // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-green", + // on: { + // click: function(e) { + // e.preventDefault(); + // window.open("https://builds.coreos.fedoraproject.org/browser?stream=testing"); + // } + // } + // }, "View Testing Releases"); // then Next stream nextIcon = h("i", { @@ -332,39 +339,41 @@ var coreos_download_app = new Vue({ 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 btn btn-sm btn-fedora-orange", - on: { - click: function(e) { - e.preventDefault(); - window.open("https://builds.coreos.fedoraproject.org/browser?stream=next"); - } - } - }, "View Next Releases"); - + // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time + // nextReleaseLink = h('button', + // { + // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-orange", + // on: { + // click: function(e) { + // e.preventDefault(); + // window.open("https://builds.coreos.fedoraproject.org/browser?stream=next"); + // } + // } + // }, "View Next Releases"); + + // XXX: Add the buttons here if in the future buttons for release browser are needed stableDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-blue pt-3", style: { "border-width": "10px !important", } - }, [stableHeadingContainer, stableIntroText, stableReleaseLink]) + }, [stableHeadingContainer, stableIntroText]) testingDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-green pt-3", style: { "border-width": "10px !important", } - }, [testingHeadingContainer, testingIntroText, testingReleaseLink]) + }, [testingHeadingContainer, testingIntroText]) nextDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-orange pt-3", style: { "border-width": "10px !important", } - }, [nextHeadingContainer, nextIntroText, nextReleaseLink]) + }, [nextHeadingContainer, nextIntroText]) streamsIntroDiv = h('div', { class: "row my-3" }, [stableDiv, testingDiv, nextDiv]); - wrapper_div = h('div', {}, [title, viewAllStreamsBtn, streamsIntroDiv]); - return wrapper_div; + wrapperDiv = h('div', {}, [title, viewAllStreamsBtn, streamsIntroDiv]); + return wrapperDiv; }, isAws: function(platform) { return platform == "aws"; @@ -482,6 +491,7 @@ var coreos_download_app = new Vue({ } } }, + // Load all stream metadata for rendering refreshStream: function() { const self = this; self.loading = true @@ -506,6 +516,7 @@ var coreos_download_app = new Vue({ 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" }}, [ @@ -574,13 +585,13 @@ var coreos_download_app = new Vue({ // Update the url with the parameters history.replaceState(null, null, `${downloadPageUrl}?${searchParams.toString()}`); - var signature_sha256_verification_modal = this.getSignatureAndShaModal(h); - h1_title = h('h1', { class: "font-weight-light text-center my-5" }, "Download Fedora CoreOS"); - stream_select_container = h('div', { class: "pb-0 pt-3 mb-3" }, [ this.getStreamIntro(h), this.getStreamName(h), this.getNavbar(h) ]); + 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 pt-3 mb-3" }, [ this.getStreamIntro(h), this.getStreamName(h), this.getNavbar(h) ]); if (this.loading) { - stream_info_div = h('div', { class: "bg-light pt-3" }, [ h('div', { class: "container font-weight-light" }, [ stream_select_container ]) ]); - download_div = h('div', { class: "bg-white pb-5" }, [ h('div', { class: "container font-weight-light" }, "Loading...") ]); - return h('div', {}, [ h1_title, stream_info_div, download_div ]); + streamInfoDiv = h('div', { class: "bg-light pt-3" }, [ 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 = {}; @@ -654,8 +665,8 @@ var coreos_download_app = new Vue({ else { $("#modal-body").empty(); let p = document.createElement('p'); - let a_checksum = null; - let a_signature = null; + let aChecksum = null; + let aSignature = null; // Show SHA256 and initialize the tags if data is available if(displayDownloads.sha256) { let d = document.createElement('div'); @@ -663,14 +674,14 @@ var coreos_download_app = new Vue({ .html("SHA256: " + displayDownloads.sha256) .appendTo(p); - a_checksum = document.createElement('a'); - $(a_checksum).attr("href", "data:text/plain;charset=utf-8," + encodeURIComponent("SHA256 (" + getFilename(displayDownloads.location) + ") = " + displayDownloads.sha256)) + 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"); } if(displayDownloads.signature) { - a_signature = document.createElement('a'); - $(a_signature).attr("href", displayDownloads.signature) + aSignature = document.createElement('a'); + $(aSignature).attr("href", displayDownloads.signature) .html("signature"); } $(p).appendTo("#modal-body"); @@ -678,13 +689,13 @@ var coreos_download_app = new Vue({ // Download the Checksum file and Signature let ol = document.createElement('ol'); - if (a_checksum || a_signature) { + if (aChecksum || aSignature) { let li = document.createElement('li'); p = document.createElement('p'); $(p).append("Download the ") - .append(a_checksum) - .append(a_checksum && a_signature ? " and " : "") - .append(a_signature); + .append(aChecksum) + .append(aChecksum && aSignature ? " and " : "") + .append(aSignature); $(p).appendTo(li); $(li).appendTo(ol); } @@ -783,28 +794,28 @@ var coreos_download_app = new Vue({ } cloud = h('div', { class: "col-12 py-2 my-2" }, [ cloudSection ]); - let bare_metal_container = h('div', { class: "col-lg-6" }, [ bareMetalTitle, bareMetal ]); - let virtualized_container = h('div', { class: "col-lg-6" }, [ virtualizedTitle, virtualized ]); + let bareMetalContainer = h('div', { class: "col-lg-6" }, [ bareMetalTitle, bareMetal ]); + let virtualizedContainer = h('div', { class: "col-lg-6" }, [ virtualizedTitle, virtualized ]); - let cloud_launchable_container = h('div', { class: "col-12 py-2 my-2", attrs: { id: IdPool.cloud_launchable, hidden: this.shownId !== IdPool.cloud_launchable } }, [ cloudLaunchable ]); - let metal_virt_container = h('div', { class: "row col-12 py-2 my-2", attrs: { id: IdPool.metal_virtualized, hidden: this.shownId !== IdPool.metal_virtualized } }, [ bare_metal_container, virtualized_container ]); - let cloud_operators_container = 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 ]); - stream_info_div = h('div', { class: "bg-light pt-3" }, [ h('div', { class: "container font-weight-light" }, [ stream_select_container ]) ]); - download_div = h('div', { class: "bg-white pb-5" }, [ + streamInfoDiv = h('div', { class: "bg-light pt-3" }, [ h('div', { class: "container font-weight-light" }, [ streamSelectContainer ]) ]); + downloadDiv = h('div', { class: "bg-white pb-5" }, [ h('div', { class: "container font-weight-light" }, [ - signature_sha256_verification_modal, - cloud_launchable_container, - metal_virt_container, - cloud_operators_container + signatureSha256VerificationModal, + cloudLaunchableContainer, + metalVirtContainer, + cloudOperatorsContainer ]) ]) - return h('div', {}, [h1_title, stream_info_div, download_div]); + return h('div', {}, [h1Title, streamInfoDiv, downloadDiv]); } else { - error_div = h('div', { class: "bg-transparent py-5" }, [ h('div', { class: "container font-weight-light" }, "No stream data found!") ]); - return h('div', {}, [ error_div ]); + errorDiv = h('div', { class: "bg-transparent py-5" }, [ h('div', { class: "container font-weight-light" }, "No stream data found!") ]); + return h('div', {}, [ errorDiv ]); } } }) diff --git a/sites/static/js/coreos-release-notes.js b/sites/static/js/coreos-release-notes.js index 5a04011..4b932bd 100644 --- a/sites/static/js/coreos-release-notes.js +++ b/sites/static/js/coreos-release-notes.js @@ -1,21 +1,16 @@ -// There are three layers of async fetch calls +// There are four layers of async fetch calls +// - releases.json // - builds.json // - meta.json // - commitmeta.json -// Originally the `loading` variable is set to false after builds.json -// has been fetched, but since we are using runtime only build and not using -// Vue templates, watching the builds list and re-render each time a metadata -// has been fetched seems to be a large overhead. -// Therefore, in this implementation, only render the page after fetching all -// of the three json files in the first place, and re-render if the user changes +// In this implementation, only render the page after fetching all +// of the four json files in the first place, and re-render if the user changes // stream. +// NOTE: set the `initialBuildsShown` to 5 means only 5 releases will be rendered, +// which means the overhead for waiting for fetching 5 releases might not be large +// (~600ms for fetching all jsons and ~300ms for consecutive reloads). -// NOTE: set the `initialBuildsShown` to 5 means only 5 builds will be rendered, -// which means the overhead for waiting for fetching 5 builds might not be large -// (~40ms for fetching all jsons). - -const baseUrl = 'https://builds.coreos.fedoraproject.org/streams' const baseProdUrl = 'https://builds.coreos.fedoraproject.org/prod/streams' const baseDevelUrl = 'https://builds.coreos.fedoraproject.org/devel/streams' @@ -40,15 +35,6 @@ function getBaseUrl(stream, developer) { : `${baseDevelUrl}/${developer}`; } -// function fetchStreamData(base, stream) { -// return fetch(`${base}/${stream}.json`) -// .then(response => response.ok ? response.json() : {}); -// } - -// function isEmptyObj(obj) { -// return Object.entries(obj).length === 0 && obj.constructor === Object; -// } - function sortPkgDiff(meta) { if ("pkgdiff" in meta) { var newdiff = {}; @@ -68,6 +54,16 @@ function findImportantPkgs(commitmeta) { return r; } +// 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); + }); +} + +// The actual fetch function for `builds.json` function fetchBuilds(base) { return fetch(`${base}/builds.json`) .then(response => response.ok ? response.json() : {"builds": []}) @@ -81,21 +77,201 @@ function fetchBuilds(base) { }); } -function fetchBuild(base, build, legacy) { - return fetchBuildMeta(base, build, legacy).then(result => { - [basearch, meta] = result; - sortPkgDiff(meta); - // show the build metadata - build.meta = meta; - // and fetch extra commit metadata in async - return fetchBuildCommitMeta(base, build, basearch, legacy).then(commitmeta => { - commitmeta["importantPkgs"] = findImportantPkgs(commitmeta); - commitmeta["showImportantPkgsOnly"] = true; - build.commitmeta = commitmeta; - }); +// Gather a metadata list of builds between releases +// Pre-condition: currentReleaseIdx <= targetReleaseIdx +function gatherMetadataBtwReleases(currentReleaseIdx, targetReleaseIdx, config) { + ({ builds, base, legacy } = config); + let metaPromiseList = []; + + // no need to fetch the older release each time, since pkgdiff's are accumulated among + // changes after and not including older release. + // in the case of oldest release, however, we need to fetch the metadata for current / older / oldest + // release, since they are pointing to the same release. + if (currentReleaseIdx == targetReleaseIdx) { + let metaPromise = fetchBuildMeta(base, builds[currentReleaseIdx], legacy); + metaPromiseList.push(metaPromise); + return Promise.all(metaPromiseList); + } + + for (let i = currentReleaseIdx; i < targetReleaseIdx; i++) { + let metaPromise = fetchBuildMeta(base, builds[i], legacy); + metaPromiseList.push(metaPromise); + } + return Promise.all(metaPromiseList); +} + +// 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 getPkgDiffReducer(pkgDiffAcc, currentMeta) { + // NOTE: pkgDiffAcc is the most recent diff accumulated, and currentMeta has the older pkgdiff + if (! ("pkgdiff" in currentMeta[1])) { + return pkgDiffAcc; + } + currentMeta[1].pkgdiff.map(d => { + const pkgName = d[0]; + const diffType = d[1]; + const pkgInfo = d[2]; + let pkgNamesAcc = pkgDiffAcc.map(dAcc => dAcc[0]); + // if the pkgdiff is first encountered, add it to accumulator + if (pkgNamesAcc.indexOf(pkgName) < 0) { + pkgDiffAcc.push(d); + return; + } + + // added pkgdiff type + if (diffType == 0) { + let pkgDiffAccCpy = [...pkgDiffAcc]; + for (let i = 0; i < pkgDiffAccCpy.length; i++) { + const dAcc = pkgDiffAccCpy[i]; + // added first then later removed + if (dAcc[0] == pkgName && dAcc[1] == 1) { + // replace with empty list and remove later + pkgDiffAccCpy[i] = []; + break; + } + // added first then later upgraded + if (dAcc[0] == pkgName && dAcc[1] == 2) { + pkgDiffAccCpy[i] = d; + pkgDiffAccCpy[i][2].NewPackage[1] = dAcc[2].NewPackage[1]; + break; + } + // added first then later downgraded + if (dAcc[0] == pkgName && dAcc[1] == 3) { + pkgDiffAccCpy[i] = d; + pkgDiffAccCpy[i][2].NewPackage[1] = dAcc[2].NewPackage[1]; + break; + } + } + pkgDiffAccCpy = pkgDiffAccCpy.filter(dAcc => dAcc.length != 0); + pkgDiffAcc = [...pkgDiffAccCpy]; + } + + // removed pkgdiff type + if (diffType == 1) { + let pkgDiffAccCpy = [...pkgDiffAcc]; + for (let i = 0; i < pkgDiffAccCpy.length; i++) { + const dAcc = pkgDiffAccCpy[i]; + // removed first then later added + if (dAcc[0] == pkgName && dAcc[1] == 0) { + // replace with empty list and remove later + pkgDiffAccCpy[i] = []; + break; + } + } + pkgDiffAccCpy = pkgDiffAccCpy.filter(dAcc => dAcc.length != 0); + pkgDiffAcc = [...pkgDiffAccCpy]; + } + + // upgraded pkgdiff type + if (diffType == 2) { + let pkgDiffAccCpy = [...pkgDiffAcc]; + for (let i = 0; i < pkgDiffAccCpy.length; i++) { + const dAcc = pkgDiffAccCpy[i]; + // upgraded first then later removed + if (dAcc[0] == pkgName && dAcc[1] == 1) { + // should be removing the previous version + pkgDiffAccCpy[i][2].PreviousPackage[1] = pkgInfo.PreviousPackage[1]; + break; + } + // upgraded first then later upgraded again + if (dAcc[0] == pkgName && dAcc[1] == 2) { + pkgDiffAccCpy[i] = d; + pkgDiffAccCpy[i][2].NewPackage[1] = dAcc[2].NewPackage[1]; + break; + } + // upgraded first then later downgraded + if (dAcc[0] == pkgName && dAcc[1] == 3) { + // checks if versions have changed + let strcmp = (s1, s2) => s1.localeCompare(s2); + if (strcmp(pkgDiffAccCpy[i][2].NewPackage[1], pkgInfo.PreviousPackage[1]) == 0) { + pkgDiffAccCpy[i] = []; + } else if (strcmp(pkgDiffAccCpy[i][2].NewPackage[1], pkgInfo.PreviousPackage[1]) > 0) { + // overall, an upgrade + pkgDiffAccCpyp[i] = d; + pkgDiffAccCpy[i].NewPackage[1] = dAcc[2].NewPackage[1]; + } else { + // overall, a downgrade + pkgDiffAccCpy[i].PreviousPackage[1] = pkgInfo.PreviousPackage[1]; + } + break; + } + } + pkgDiffAccCpy = pkgDiffAccCpy.filter(dAcc => dAcc.length != 0); + pkgDiffAcc = [...pkgDiffAccCpy]; + } + + // downgraded pkgdiff type + if (diffType == 3) { + let pkgDiffAccCpy = [...pkgDiffAcc]; + for (let i = 0; i < pkgDiffAccCpy.length; i++) { + const dAcc = pkgDiffAccCpy[i]; + // downgraded first then later removed + if (dAcc[0] == pkgName && dAcc[1] == 1) { + // should be removing the previous version + pkgDiffAccCpy[i][2].PreviousPackage[1] = pkgInfo.PreviousPackage[1]; + break; + } + // downgraded first then later upgraded + if (dAcc[0] == pkgName && dAcc[1] == 2) { + // checks if versions have changed + let strcmp = (s1, s2) => s1.localeCompare(s2); + if (strcmp(pkgDiffAccCpy[i][2].NewPackage[1], pkgInfo.PreviousPackage[1]) == 0) { + pkgDiffAccCpy[i] = []; + } else if (strcmp(pkgDiffAccCpy[i][2].NewPackage[1], pkgInfo.PreviousPackage[1]) > 0) { + // overall, an upgrade + pkgDiffAccCpy[i].PreviousPackage[1] = pkgInfo.PreviousPackage[1]; + } else { + // overall, a downgrade + pkgDiffAccCpyp[i] = d; + pkgDiffAccCpy[i].NewPackage[1] = dAcc[2].NewPackage[1]; + } + break; + } + // upgraded first then later downgraded + if (dAcc[0] == pkgName && dAcc[1] == 3) { + pkgDiffAccCpy[i] = d; + pkgDiffAccCpy[i][2].NewPackage[1] = dAcc[2].NewPackage[1]; + break; + } + } + pkgDiffAccCpy = pkgDiffAccCpy.filter(dAcc => dAcc.length != 0); + pkgDiffAcc = [...pkgDiffAccCpy]; + } + + }) + + return pkgDiffAcc; + + } + + let pkgdiff = metaList.reduce(getPkgDiffReducer, []); + return pkgdiff; +} + +// Fetch the metadata of the release `builds[fromIdx]` +// and also calculate the accumulated pkgdiff between the last release `builds[toIdx]` +// Note: meta.json is used for pkgdiff and commitmeta.json contains pkglist +function fetchBuild(base, legacy, builds, fromIdx, toIdx) { + let config = { builds, base, legacy }; + return gatherMetadataBtwReleases(fromIdx, toIdx, config).then(metaList => { + let build = builds[fromIdx]; + let [basearch, meta] = metaList[0]; + meta.pkgdiff = getPkgDiffFromMetaList(metaList); + sortPkgDiff(meta); + build.meta = meta; + // and fetch extra commit metadata in async + return fetchBuildCommitMeta(base, build, basearch, legacy).then(commitmeta => { + commitmeta["importantPkgs"] = findImportantPkgs(commitmeta); + commitmeta["showImportantPkgsOnly"] = true; + build.commitmeta = commitmeta; + builds[fromIdx] = build; }); + }); } +// The actual fetch function for `meta.json` function fetchBuildMeta(base, build, legacy) { if (legacy) { return fetch(`${base}/${build.id}/meta.json`) @@ -111,6 +287,7 @@ function fetchBuildMeta(base, build, legacy) { // })); } +// The actual fetch function for `commitmeta.json` function fetchBuildCommitMeta(base, build, basearch, legacy) { if (legacy) { return fetch(`${base}/${build.id}/commitmeta.json`) @@ -139,7 +316,7 @@ var coreos_release_notes = new Vue({ // XXX: in non-legacy mode, meta and commitmeta are those // of the first arch, but in the future these would be e.g. // meta[arch] and commitmeta[arch] - builds: [], + releases: [], // list of unshown {id, arches, meta, commitmeta} build objects unshown_builds: [], // toggles "Loading..." @@ -193,13 +370,13 @@ var coreos_release_notes = new Vue({ }, getReleaseNoteCards: function(h) { const self = this; - // check if all build metadata has been fetched + // check if all release metadata has been fetched if (self.loading) { return } rows = []; - self.builds.forEach((build, idx) => { + self.releases.forEach((build, idx) => { // Left pane consists of Build ID and Arch info let headingListArches = []; let headingBuildId = h('h5', { class: "font-weight-bold" }, build.id); @@ -269,36 +446,51 @@ var coreos_release_notes = new Vue({ }, refreshBuilds: function() { this.loading = true - this.buildsUrl = getBaseUrl(this.stream, this.developer) + "/builds" - fetchBuilds(this.buildsUrl).then(result => { + 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.builds = []; + this.releases = []; this.unshown_builds = []; + // counter for the number of release metadata fetched since fetch is asnyc operation let counter = 0; - // and now fetch each build info async - builds.forEach((build, idx) => { - if (idx < initialBuildsShown) { - this.builds.push(build); - fetchBuild(this.buildsUrl, build, this.legacy) - .then(() => { - counter++; - if (counter === builds.length) { - // fetched all metadata - this.loading = false; - } - }) - } else { - this.unshown_builds.push(build); - counter++; - if (counter === builds.length) { - // fetched all metadata - this.loading = false; - } + // 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) { + // 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; } - }) + } + } + }); }); } }, From de225b5ac69bd29592ad88cabe6a1ee9730a4773 Mon Sep 17 00:00:00 2001 From: Allen Bai Date: May 15 2020 21:30:22 +0000 Subject: [PATCH 4/5] coreos-download: add summary for packages and pkgdiffs Adds a section at each release note that summarizes number of packages that ships in the release and number of pkgdiffs between previous release. Appends a button for each pkg summary that expands/collapses the corresponding pkglist/pkgdiff section on the release note. Signed-off-by: Allen Bai --- diff --git a/sites/getfedora.org/site/coreos/index.html b/sites/getfedora.org/site/coreos/index.html index af9e373..912351f 100644 --- a/sites/getfedora.org/site/coreos/index.html +++ b/sites/getfedora.org/site/coreos/index.html @@ -33,7 +33,7 @@

-

Release Notes

+

Release Notes

diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js index 89b18e8..d103924 100644 --- a/sites/static/js/coreos-download.js +++ b/sites/static/js/coreos-download.js @@ -242,22 +242,23 @@ var coreos_download_app = new Vue({ }, // Introduction section for streams, the section above the navigation bar for platforms getStreamIntro: function(h) { - title = h('h2', { class: "font-weight-light text-center pb-3" }, "Fedora CoreOS is available across 3 different release streams:"); + title = h('h2', { class: "font-weight-light text-center pb-3 mb-5" }, "Fedora CoreOS is available across 3 different release streams:"); if (this.loading) { return title; } - viewAllStreamsBtn = h('button', - { - class: "d-block mx-auto mb-5 py-1 btn btn-sm btn-fedora-purple", - on: { - click: function(e) { - e.preventDefault(); - window.open("https://builds.coreos.fedoraproject.org/browser"); - } - } - }, "View All Streams"); + // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time + // viewAllStreamsBtn = h('button', + // { + // class: "d-block mx-auto mb-5 py-1 btn btn-sm btn-fedora-purple", + // on: { + // click: function(e) { + // e.preventDefault(); + // window.open("https://builds.coreos.fedoraproject.org/browser"); + // } + // } + // }, "View All Streams"); // Release info section with three tabs: stable, testing, next // NOTE: in order for the button line up at the same horizontal level, use a fixed height `9em` for

elements of stream summaries. @@ -372,7 +373,8 @@ var coreos_download_app = new Vue({ }, [nextHeadingContainer, nextIntroText]) streamsIntroDiv = h('div', { class: "row my-3" }, [stableDiv, testingDiv, nextDiv]); - wrapperDiv = h('div', {}, [title, viewAllStreamsBtn, streamsIntroDiv]); + // XXX: Add the buttons here if in the future buttons for release browser are needed + wrapperDiv = h('div', {}, [title, streamsIntroDiv]); return wrapperDiv; }, isAws: function(platform) { diff --git a/sites/static/js/coreos-release-notes.js b/sites/static/js/coreos-release-notes.js index 4b932bd..0a67aa8 100644 --- a/sites/static/js/coreos-release-notes.js +++ b/sites/static/js/coreos-release-notes.js @@ -372,14 +372,19 @@ var coreos_release_notes = new Vue({ const self = this; // check if all release metadata has been fetched if (self.loading) { - return + return; } rows = []; self.releases.forEach((build, idx) => { + // checked if build metadata has been fetched + if (build.arches.length == 0 || build.meta == null || build.commitmeta == null) { + return; + } + // Left pane consists of Build ID and Arch info let headingListArches = []; - let headingBuildId = h('h5', { class: "font-weight-bold" }, build.id); + let headingBuildId = h('h5', { class: "font-weight-normal" }, build.id); build.arches.forEach((arch, _) => { headingListArches.push(h('h6', {}, arch)); }); @@ -394,51 +399,215 @@ var coreos_release_notes = new Vue({ importantPkgsElements.push(h('span', { class: "mr-2 badge badge-pill badge-light" }, pkg[2])); }); + // Summary of pkglist and pkgdiffs with expand buttons + let pkgSummaryElements = [] + .concat(`${build.commitmeta['rpmostree.rpmdb.pkglist'].length} packages (`) + .concat( + h('a', { + attrs: { + href: "#" + }, + on: { + click: function(e) { + e.preventDefault(); + let totalPkgListElement = e.target.parentElement.nextSibling; + if (totalPkgListElement.hidden == true) { + totalPkgListElement.hidden = false; + e.target.innerText = 'collapse'; + } else { + totalPkgListElement.hidden = true; + e.target.innerText = 'expand'; + } + } + } + }, 'expand') + ) + .concat('); ') + // Next, append the pkgdiffs summary + // `added` summary and expand button + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.added.length > 0) { + 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'; + } + } + } + }, 'expand') + ) + .concat('); '); + } + + // `removed` summary and expand button + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.removed.length > 0) { + 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'; + } + } + } + }, 'expand') + ) + .concat('); '); + } + + // `upgraded` summary and expand button + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.upgraded.length > 0) { + 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'; + } + } + } + }, 'expand') + ) + .concat('); '); + } + + // `downgraded` summary and expand button + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.downgraded.length > 0) { + 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'; + } + } + } + }, 'expand') + ) + .concat('); '); + } + + let pkgSummaryDiv = h('div', { class: "mt-3" }, pkgSummaryElements); + + // Package list + let totalPkgsElementsList = []; + let totalPkgsHeading = []; + if (build.commitmeta['rpmostree.rpmdb.pkglist'].length > 0) { + build.commitmeta['rpmostree.rpmdb.pkglist'].forEach((pkg, _) => { + totalPkgsElementsList.push(h('li', {}, self.getPkgNevraFull(pkg))); + }); + totalPkgsHeading = h('p', { class: "mt-3" }, "Package List:") + } + let totalPkgsElements = h('div', { attrs: { hidden: true } }, [ totalPkgsHeading, h('ul', {}, totalPkgsElementsList) ]); + // Added package list let addedPkgsElementsList = []; let addedPkgsHeading = []; - if (build.meta.pkgdiff != null && build.meta.pkgdiff.added.length > 0) { + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.added.length > 0) { build.meta.pkgdiff.added.forEach((pkg, _) => { addedPkgsElementsList.push(h('li', {}, self.getPkgNevra(pkg[2]["NewPackage"]))); }); addedPkgsHeading = h('p', { class: "mt-3" }, "Added:") } - let addedPkgsElements = h('div', {}, [ addedPkgsHeading, h('ul', {}, addedPkgsElementsList) ]); + let addedPkgsElements = h('div', { attrs: { hidden: true } }, [ addedPkgsHeading, h('ul', {}, addedPkgsElementsList) ]); // Removed package list let removedPkgsElementsList = []; let removedPkgsHeading = []; - if (build.meta.pkgdiff != null && build.meta.pkgdiff.removed.length > 0) { + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.removed.length > 0) { build.meta.pkgdiff.removed.forEach((pkg, _) => { removedPkgsElementsList.push(h('li', {}, self.getPkgNevra(pkg[2]["PreviousPackage"]))); }); - removedPkgsHeading = h('p', {}, "Removed:"); + removedPkgsHeading = h('p', { class: "mt-3" }, "Removed:"); } - let removedPkgsElements = h('div', {}, [ removedPkgsHeading, h('ul', {}, removedPkgsElementsList) ]); + let removedPkgsElements = h('div', { attrs: { hidden: true } }, [ removedPkgsHeading, h('ul', {}, removedPkgsElementsList) ]); // Upgraded package list let upgradedPkgsElementsList = []; let upgradedPkgsHeading = []; - if (build.meta.pkgdiff != null && build.meta.pkgdiff.upgraded.length > 0) { + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.upgraded.length > 0) { build.meta.pkgdiff.upgraded.forEach((pkg, _) => { upgradedPkgsElementsList.push(h('li', {}, `${pkg[2]["PreviousPackage"][0]} ${self.getPkgEvra(pkg[2]["PreviousPackage"])} → ${self.getPkgEvra(pkg[2]["NewPackage"])}`)); }); - upgradedPkgsHeading = h('p', {}, "Upgraded:"); + upgradedPkgsHeading = h('p', { class: "mt-3" }, "Upgraded:"); } - let upgradedPkgsElements = h('div', {}, [ upgradedPkgsHeading, h('ul', {}, upgradedPkgsElementsList) ]); + let upgradedPkgsElements = h('div', { attrs: { hidden: true } }, [ upgradedPkgsHeading, h('ul', {}, upgradedPkgsElementsList) ]); // Downgraded package list let downgradedPkgsElementsList = []; let downgradedPkgsHeading = []; - if (build.meta.pkgdiff != null && build.meta.pkgdiff.downgraded.length > 0) { + if (build.meta.pkgdiff != {} && build.meta.pkgdiff.downgraded.length > 0) { build.meta.pkgdiff.downgraded.forEach((pkg, _) => { downgradedPkgsElementsList.push(h('li', {}, `${pkg[2]["PreviousPackage"][0]} ${self.getPkgEvra(pkg[2]["PreviousPackage"])} → ${self.getPkgEvra(pkg[2]["NewPackage"])}`)); }); - downgradedPkgsHeading = h('p', {}, "Downgraded:"); + downgradedPkgsHeading = h('p', { class: "mt-3" }, "Downgraded:"); } - let downgradedPkgsElements = h('div', {}, [ 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-3" }, [ date, importantPkgsElements, addedPkgsElements, removedPkgsElements, upgradedPkgsElements, downgradedPkgsElements ]); + 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); }) From 43b3afbb86d092b661a39c43b7341d6c6e7fd14b Mon Sep 17 00:00:00 2001 From: Allen Bai Date: May 19 2020 19:27:29 +0000 Subject: [PATCH 5/5] coreos-download: add buttons for releases in download page Adds buttons that link to release notes from download page. This can be helpful when users enter the download page directly and want to view the release notes for different streams. Signed-off-by: Allen Bai --- diff --git a/sites/static/js/coreos-download.js b/sites/static/js/coreos-download.js index d103924..e158027 100644 --- a/sites/static/js/coreos-download.js +++ b/sites/static/js/coreos-download.js @@ -242,23 +242,23 @@ var coreos_download_app = new Vue({ }, // Introduction section for streams, the section above the navigation bar for platforms getStreamIntro: function(h) { - title = h('h2', { class: "font-weight-light text-center pb-3 mb-5" }, "Fedora CoreOS is available across 3 different release streams:"); + const overviewPageUrl = window.location.href.match(/^.*\/coreos/)[0]; + title = h('h2', { class: "font-weight-light text-center pb-3 mb-3" }, "Fedora CoreOS is available across 3 different release streams:"); if (this.loading) { return title; } - // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time - // viewAllStreamsBtn = h('button', - // { - // class: "d-block mx-auto mb-5 py-1 btn btn-sm btn-fedora-purple", - // on: { - // click: function(e) { - // e.preventDefault(); - // window.open("https://builds.coreos.fedoraproject.org/browser"); - // } - // } - // }, "View All Streams"); + viewAllStreamsBtn = h('button', + { + class: "d-block mx-auto mb-5 py-1 btn btn-sm btn-fedora-purple", + on: { + click: function(e) { + e.preventDefault(); + window.open(`${overviewPageUrl}`); + } + } + }, "View All Streams"); // Release info section with three tabs: stable, testing, next // NOTE: in order for the button line up at the same horizontal level, use a fixed height `9em` for

elements of stream summaries. @@ -279,17 +279,16 @@ var coreos_download_app = new Vue({ 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."); - // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time - // stableReleaseLink = h('button', - // { - // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-blue", - // on: { - // click: function(e) { - // e.preventDefault(); - // window.open("https://builds.coreos.fedoraproject.org/browser?stream=stable"); - // } - // } - // }, "View Stable Releases"); + stableReleaseLink = h('button', + { + class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-blue", + on: { + click: function(e) { + e.preventDefault(); + window.open(`${overviewPageUrl}?stream=stable`); + } + } + }, "View Stable Releases"); // then Testing stream testingIcon = h("i", { @@ -309,17 +308,16 @@ var coreos_download_app = new Vue({ 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."); - // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time - // testingReleaseLink = h('button', - // { - // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-green", - // on: { - // click: function(e) { - // e.preventDefault(); - // window.open("https://builds.coreos.fedoraproject.org/browser?stream=testing"); - // } - // } - // }, "View Testing Releases"); + testingReleaseLink = h('button', + { + class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-green", + on: { + click: function(e) { + e.preventDefault(); + window.open(`${overviewPageUrl}?stream=testing`); + } + } + }, "View Testing Releases"); // then Next stream nextIcon = h("i", { @@ -340,41 +338,38 @@ var coreos_download_app = new Vue({ 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."); - // XXX: the button is hided for now since we do not want to expose the release browser, which are subject to changes at any time - // nextReleaseLink = h('button', - // { - // class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-orange", - // on: { - // click: function(e) { - // e.preventDefault(); - // window.open("https://builds.coreos.fedoraproject.org/browser?stream=next"); - // } - // } - // }, "View Next Releases"); - - // XXX: Add the buttons here if in the future buttons for release browser are needed + nextReleaseLink = h('button', + { + class: "d-block mx-auto mb-4 py-1 btn btn-sm btn-fedora-orange", + on: { + click: function(e) { + e.preventDefault(); + window.open(`${overviewPageUrl}?stream=next`); + } + } + }, "View Next Releases"); + stableDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-blue pt-3", style: { "border-width": "10px !important", } - }, [stableHeadingContainer, stableIntroText]) + }, [stableHeadingContainer, stableIntroText, stableReleaseLink]) testingDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-green pt-3", style: { "border-width": "10px !important", } - }, [testingHeadingContainer, testingIntroText]) + }, [testingHeadingContainer, testingIntroText, testingReleaseLink]) nextDiv = h('div', { class: "col-12 col-lg-4 border-left border-fedora-orange pt-3", style: { "border-width": "10px !important", } - }, [nextHeadingContainer, nextIntroText]) + }, [nextHeadingContainer, nextIntroText, nextReleaseLink]) streamsIntroDiv = h('div', { class: "row my-3" }, [stableDiv, testingDiv, nextDiv]); - // XXX: Add the buttons here if in the future buttons for release browser are needed - wrapperDiv = h('div', {}, [title, streamsIntroDiv]); + wrapperDiv = h('div', {}, [title, viewAllStreamsBtn, streamsIntroDiv]); return wrapperDiv; }, isAws: function(platform) { diff --git a/sites/static/js/coreos-release-notes.js b/sites/static/js/coreos-release-notes.js index 0a67aa8..9fc280f 100644 --- a/sites/static/js/coreos-release-notes.js +++ b/sites/static/js/coreos-release-notes.js @@ -352,6 +352,8 @@ var coreos_release_notes = new Vue({ if (e.target.innerText === "Next Stream") { 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" ]); @@ -664,6 +666,34 @@ var coreos_release_notes = new Vue({ } }, render: function(h) { + // Duplicate logic from coreos-download.js + // URL paramters checking and setting default values + 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')) { + case 'stable': + this.stream = "stable"; + break; + case 'testing': + this.stream = "testing"; + break; + case 'next': + this.stream = "next"; + break; + default: + this.stream = "stable"; + } + } else { + searchParams.set('stream', 'stable'); + } + // Update the url with the parameters + history.replaceState(null, null, `${overviewPageUrl}?${searchParams.toString()}`); + let navBar = this.getNavbar(h); if (this.loading) {