diff --git a/content/patterns/ansible-edge-gitops-kasten/_index.md b/content/patterns/ansible-edge-gitops-kasten/_index.md index a87081b7f..e4983212b 100644 --- a/content/patterns/ansible-edge-gitops-kasten/_index.md +++ b/content/patterns/ansible-edge-gitops-kasten/_index.md @@ -1,5 +1,6 @@ --- title: OpenShift Virtualization Data Protection with Veeam Kasten +date: 2024-11-06 tier: sandbox summary: This pattern uses OpenShift Virtualization to simulate an edge environment for VMs, protected by Veeam Kasten. rh_products: diff --git a/content/patterns/cockroachdb/_index.md b/content/patterns/cockroachdb/_index.md index 64acd5c29..a8c26d508 100644 --- a/content/patterns/cockroachdb/_index.md +++ b/content/patterns/cockroachdb/_index.md @@ -1,5 +1,6 @@ --- title: Cockroach +date: 2022-12-14 tier: sandbox Summary: A multicloud pattern using cockroachdb and submariner, deployed via RHACM. rh_products: diff --git a/content/patterns/coco-pattern/_index.adoc b/content/patterns/coco-pattern/_index.adoc index c593ee0b4..9dc143c08 100644 --- a/content/patterns/coco-pattern/_index.adoc +++ b/content/patterns/coco-pattern/_index.adoc @@ -31,7 +31,7 @@ A core goal of confidential computing is to use this technology to isolate the w image::coco-pattern/isolation.png[Schematic describing the isolation of confidential contains from the hosting system] -This pattern uses https://docs.redhat.com/en/documentation/openshift_sandboxed_containers/1.7/html/user_guide/deploying-on-azure#deploying-cc_azure-cc[Red Hat OpenShift sandbox containers] to deploy and configure confidential containers on Microsoft Azure. +This pattern uses https://docs.redhat.com/en/documentation/openshift_sandboxed_containers/1.7/html/user_guide/deploying-on-azure#deploying-cc_azure-cc[Red Hat OpenShift sandbox containers] to deploy and configure confidential containers on Microsoft Azure. It deploys three copies of 'Hello OpenShift' to demonstrate some of the security boundaries that enforced with confidential containers. @@ -46,7 +46,7 @@ It deploys three copies of 'Hello OpenShift' to demonstrate some of the security **This pattern is a demonstration only and contains configuration that is not best practice** - The default configuration deploys everything in a single cluster for testing purposes. The https://www.ietf.org/archive/id/draft-ietf-rats-architecture-22.html[RATS] architecture mandates that the Key Broker Service (e.g. https://github.com/confidential-containers/trustee[Trustee]) is in a trusted security zone. -- The https://github.com/confidential-containers/trustee/tree/main/attestation-service[Attestation Service] has wide open security policies. +- The https://github.com/confidential-containers/trustee/tree/main/attestation-service[Attestation Service] has wide open security policies. == Future work diff --git a/content/patterns/connected-vehicle-architecture/_index.md b/content/patterns/connected-vehicle-architecture/_index.md index e0ef43792..18ad2d37a 100644 --- a/content/patterns/connected-vehicle-architecture/_index.md +++ b/content/patterns/connected-vehicle-architecture/_index.md @@ -1,5 +1,6 @@ --- title: Connected Vehicle Architecture +date: 2022-12-14 tier: sandbox Summary: A distributed cloud-native application that implements key aspects of a modern IoT architecture. rh_products: diff --git a/content/patterns/kong-gateway/_index.md b/content/patterns/kong-gateway/_index.md index bffd3b980..4c9bc4ac9 100644 --- a/content/patterns/kong-gateway/_index.md +++ b/content/patterns/kong-gateway/_index.md @@ -1,5 +1,6 @@ --- title: Kong +date: 2022-12-14 tier: sandbox Summary: A pattern for Kong Gateway Control Plane and Data Plane demo. rh_products: diff --git a/content/patterns/multicloud-gitops-Portworx/_index.md b/content/patterns/multicloud-gitops-Portworx/_index.md index 1a9adab6b..d138f59d8 100644 --- a/content/patterns/multicloud-gitops-Portworx/_index.md +++ b/content/patterns/multicloud-gitops-Portworx/_index.md @@ -1,6 +1,6 @@ --- title: Multicloud GitOps with Portworx Enterprise -date: 2023-18-05 +date: 2023-05-18 tier: sandbox summary: This pattern helps you develop and deploy applications on an open hybrid cloud in a stable, simple, and secure way. It includes persistent storage for stateful applications. rh_products: diff --git a/layouts/partials/menu-patterns-browser.html b/layouts/partials/menu-patterns-browser.html index 50e89818f..2874d0576 100644 --- a/layouts/partials/menu-patterns-browser.html +++ b/layouts/partials/menu-patterns-browser.html @@ -20,19 +20,7 @@

What do these tiers mean?
-
- - - +
@@ -56,13 +44,7 @@

-
- {{ range $name, $taxonomy := .Site.Taxonomies.industries }} - - {{ end }} +
@@ -86,13 +68,7 @@

-
- {{ range $name, $taxonomy := .Site.Taxonomies.rh_products }} - - {{ end }} +
@@ -116,13 +92,7 @@

-
- {{ range $name, $taxonomy := .Site.Taxonomies.other_products }} - - {{ end }} +
diff --git a/layouts/partials/patterns-browser.html b/layouts/partials/patterns-browser.html index 7086203b3..3eaa124c7 100644 --- a/layouts/partials/patterns-browser.html +++ b/layouts/partials/patterns-browser.html @@ -25,45 +25,35 @@

{{ $patterns := where $.Pages "Section" "patterns"}}
- {{- len $patterns -}} of {{ len $patterns }} patterns displayed + + +
+
+
+
-

- +
+ {{ partial "footer.html" . }} diff --git a/layouts/patterns/list.json.json b/layouts/patterns/list.json.json index eb458b605..a78835d9d 100644 --- a/layouts/patterns/list.json.json +++ b/layouts/patterns/list.json.json @@ -1,9 +1,48 @@ {{- if (eq .RelPermalink "/patterns/") }} + + {{/* Patterns */}} {{- $patterns := where $.Pages "Section" "patterns"}} - {{- $patterns_len := $patterns | len }} - {{- $pattern_list := slice }} - {{- range $index, $pattern := $patterns.ByTitle }} - {{- $pattern_list = $pattern_list | append (dict $pattern.Title $pattern.Params) }} - {{- end }} - {{- $pattern_list | jsonify }} + {{- $pattern_list := slice }} + {{- range $index, $pattern := $patterns.ByTitle }} + {{- $object := dict "Name" $pattern.LinkTitle "Link" $pattern.RelPermalink "Params" $pattern.Params -}} + {{- $pattern_list = $pattern_list | append $object -}} + {{- end }} + + {{/* Red Hat Products */}} + {{- $rh_products := .Site.Taxonomies.rh_products }} + {{- $rh_products_list := slice }} + {{- range $index, $rh_product := $rh_products.Alphabetical }} + {{- $rh_products_list = $rh_products_list | append (dict "Name" $rh_product.Name "LinkTitle" $rh_product.Page.LinkTitle) }} + {{- end }} + {{- $rh_products_filter_types := slice "AND" "OR" }} + {{- $rh_products_dict := dict "filter_list" $rh_products_list "filter_types" $rh_products_filter_types }} + + {{/* Other Products */}} + {{- $other_products := .Site.Taxonomies.other_products }} + {{- $other_products_list := slice }} + {{- range $index, $other_product := $other_products.Alphabetical }} + {{- $other_products_list = $other_products_list | append (dict "Name" $other_product.Name "LinkTitle" $other_product.Page.LinkTitle) }} + {{- end }} + {{- $other_products_filter_types := slice "AND" "OR" }} + {{- $other_products_dict := dict "filter_list" $other_products_list "filter_types" $other_products_filter_types }} + + {{/* Industries */}} + {{- $industries := .Site.Taxonomies.industries }} + {{- $industries_list := slice }} + {{- range $index, $industry := $industries.Alphabetical }} + {{- $industries_list = $industries_list | append (dict "Name" $industry.Name "LinkTitle" $industry.Page.LinkTitle) }} + {{- end }} + {{- $industries_filter_types := slice "AND" "OR" }} + {{- $industries_dict := dict "filter_list" $industries_list "filter_types" $industries_filter_types }} + + {{/* Tiers */}} + {{- $tiers_list := slice -}} + {{- $tiers_list = $tiers_list | append (dict "Name" "maintained" "LinkTitle" "Maintained" "color" "green") }} + {{- $tiers_list = $tiers_list | append (dict "Name" "tested" "LinkTitle" "Tested" "color" "blue") }} + {{- $tiers_list = $tiers_list | append (dict "Name" "sandbox" "LinkTitle" "Sandbox" "color" "oragne") }} + {{- $tiers_filter_types := slice "OR" }} + {{- $tiers_dict := dict "filter_list" $tiers_list "filter_types" $tiers_filter_types }} + + {{- $filter_categories := dict "rh_products" $rh_products_dict "other_products" $other_products_dict "industries" $industries_dict "tier" $tiers_dict -}} + {{- dict "patterns" $pattern_list "filter_categories" $filter_categories | jsonify }} {{- end }} diff --git a/static/js/patterns-browser-v2.js b/static/js/patterns-browser-v2.js new file mode 100644 index 000000000..0b1cd3c77 --- /dev/null +++ b/static/js/patterns-browser-v2.js @@ -0,0 +1,337 @@ +class Filter { + // Class for constructing the filter, which is based on the provided + // filter_categories dictionary checked against options from the filters + // that the user selects. + + constructor(filter_categories) { + this.filter_categories = filter_categories; + this.filter_values = this.get_filter_values(); + this.filter_types = this.get_filter_types(); + } + + get_filter_values() { + // Return the options from the filters that have been filtered + var filterValuesResult = new Object(); + for (const [category, terms] of Object.entries(this.filter_categories)) { + for (item = 0; item < terms.filter_list.length; item++) { + var checkboxId = category + ":" + cleanString(terms.filter_list[item].Name); + var checkbox = document.getElementById(checkboxId); + if (checkbox.checked) { + if (filterValuesResult[category] == undefined) { filterValuesResult[category] = [] }; + filterValuesResult[category].push(terms.filter_list[item].LinkTitle); + } + } + } + return filterValuesResult; + } + + get_filter_types() { + // Return the operator selected from the filter + var filterTypesResult = new Object(); + for (const [category, terms] of Object.entries(this.filter_categories)) { + for (item = 0; item < terms.filter_types.length; item++) { + var filterTypeID = category + "_button:" + terms.filter_types[item].toLowerCase(); + var filterTypeItem = document.getElementById(filterTypeID); + if (filterTypeItem != null && filterTypeItem.classList.contains("pf-m-selected")) { + filterTypesResult[category] = terms.filter_types[item].toLowerCase(); + } + } + } + return filterTypesResult; + } +} + +class FilteredPatterns { + // Class for filtered the patterns based on a Filter object. Also sorts the + // patterns based on sort_value. + + constructor(patterns_filter, patterns, sort_value) { + this.patterns_filter = patterns_filter; + this.patterns = patterns; + this.sort_value = sort_value; + this.filter_patterns = this.get_filter_patterns(); + } + + get_filter_patterns() { + // Create a filtered list of patterns + var filteredPatterns = []; + for (item = 0; item < this.patterns.length; item++) { + var checksPassed = new Object(); + for (const [category, terms] of Object.entries(this.patterns_filter.filter_values)) { + if (typeof(this.patterns[item].Params[category]) == "string") { + checksPassed[category] = checkCategoryString(terms, this.patterns[item].Params[category]) + } else if (typeof(this.patterns[item].Params[category]) == "object" && this.patterns[item].Params[category] != null) { + var patternTerms = this.patterns[item].Params[category].map(v => v.toLowerCase()); + var filterTerms = terms.map(v => v.toLowerCase()); + checksPassed[category] = checkCategoryObject(patternTerms, filterTerms, this.patterns_filter.filter_types[category]); + }; + }; + var patternPassed = true; + for (const [category, terms] of Object.entries(this.patterns_filter.filter_values)) { + if (checksPassed[category] != true) { + patternPassed = false; + }; + }; + if (patternPassed == true) { filteredPatterns.push(this.patterns[item]); }; + }; + this.sort_filtered_patterns(filteredPatterns) + return filteredPatterns + } + + sort_filtered_patterns(filteredPatterns) { + // Sort the filtered list of patterns + if (this.sort_value.value == "atoz") { + filteredPatterns.sort(sortAtoZ); + } else if (this.sort_value.value == "ztoa") { + filteredPatterns.sort(sortAtoZ); + filteredPatterns.reverse(); + } else if (this.sort_value.value == "oldest") { + filteredPatterns.sort(sortDate); + } else if (this.sort_value.value == "newest") { + filteredPatterns.sort(sortDate); + filteredPatterns.reverse(); + }; + } +} + +async function getData() { + // Load the JSON file containing all patterns data + const url = "/patterns/index.json"; + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } + const json = await response.json(); + return json; + } catch (error) { + console.error(error.message); + } +} + +function cleanString(string) { + // Provide a string that can be used as a HTML id i.e. no spaces. + return string.replace(/ /g, "-") +} + +function capitalizeFirstLetter(string) { + // Capitalize the first letter of a string + return string[0].toUpperCase() + string.slice(1); +} + +function sortAtoZ(a, b){ + // Sort alphabetically + const nameA = a.Name.toUpperCase() + const nameB = b.Name.toUpperCase() + if (nameA < nameB) { + return -1; + } + if (nameA > nameB) { + return 1; + } + return 0; +} + +function sortDate(a, b){ + // Sort by date + if (a.Params.date != undefined) { + var dateA = new Date(a.Params.date) + } else { + // Set a default date if no date exists + var dateA = new Date("2000-01-01T00:00:00Z"); + }; + if (b.Params.date != undefined) { + var dateB = new Date(b.Params.date) + } else { + // Set a default date if no date exists + var dateB = new Date("2000-01-01T00:00:00Z"); + }; + if (dateA < dateB) { + return -1; + } + if (dateA > dateB) { + return 1; + } + return 0; +} + +function checkCategoryString(terms, category) { + // If the category we're checking is a string type, that means it can have + // only one value. Therefore you can only use an OR operator on the filter. + var checksPassed = false; + for (termId = 0; termId < terms.length; termId++) { + if (terms[termId].toLowerCase() == category.toLowerCase()) { + checksPassed = true; + } + } + return checksPassed; +} + +function checkCategoryObject(patternTerms, filterTerms, filter_type) { + // If the category we're checking is a object type, that means it can have + // more than one value. Therefore you can use either AND or OR on the filter. + var checksPassed = false; + if(filter_type == "and" && filterTerms.every(r => patternTerms.includes(r))) { + checksPassed = true; + } else if (filter_type == "or" && filterTerms.some(r => patternTerms.includes(r))) { + checksPassed = true; + }; + return checksPassed; +} + +function renderSpinner() { + // HTML for the loading spinner + return '
' + + '
' + + '' + + '' + + '' + + '
' + + '
'; +} + +function renderFilterItem(type, name, linkTitle) { + // HTML for each checkbox item in the filters + var filterItemHtml = ''; + return filterItemHtml; +} + +function renderFilterButtons(filterButtonTypes, name) { + // HTML for the AND / OR operator buttons + if (filterButtonTypes.length > 1) { + var filterButtonsHtml = '
'; + var firstSelected = ""; + for (item = 0; item < filterButtonTypes.length; item++) { + firstSelected = ""; + if (item == 0) { firstSelected = " pf-m-selected"; } + filterButtonsHtml += '
' + + '' + + '
' + } + filterButtonsHtml += '
' + return filterButtonsHtml; + } + return ""; +} + +function renderFilter(elementId, filterType, filterData) { + // HTML to render all filters + const element = document.getElementById(elementId); + for (item = 0; item < filterData.filter_list.length; item++) { + element.innerHTML += renderFilterItem(filterType, filterData.filter_list[item].Name, filterData.filter_list[item].LinkTitle); + }; + element.innerHTML += renderFilterButtons(filterData.filter_types, filterType); +} + +function renderLabel(tier, tier_categories) { + // HTML to render the pattern tier label + if (tier != undefined) { + var color= tier_categories.filter_list.find(item => item.Name === tier); + var renderedLabelHtml = '' + + '' + + '' + capitalizeFirstLetter(tier) + '' + + capitalizeFirstLetter(tier) + + '' + + ''; + return renderedLabelHtml; + } else { + return ""; + }; +} + +function renderCard(pattern, tier_categories) { + // HTML for each pattern card + var renderCardHtml = ''; + return renderCardHtml; +} + +function renderFilteredCards(patterns, filter_categories) { + // HTML to render a gallery of cards for filtered patterns + + // Remove the current pattern cards + const element = document.getElementById("patternCards"); + element.innerHTML = ""; + + // Display the spinner while we load the pattern cards + const patternLoaderSpinner = document.getElementById("patternLoaderSpinner"); + patternLoaderSpinner.innerHTML = renderSpinner(); + + // Initialize the filter + const patternsFilter = new Filter(filter_categories); + + // Filter the patterns + const sortValue = document.getElementById("select-pattern-sort"); + var filteredPatterns = new FilteredPatterns(patternsFilter, patterns, sortValue).filter_patterns; + + // Remove the spinner + patternLoaderSpinner.innerHTML = ""; + + // Render the filtered cards + for (item = 0; item < filteredPatterns.length; item++) { + const element = document.getElementById("patternCards"); + element.innerHTML += renderCard(filteredPatterns[item], filter_categories.tier); + }; + + // Display the number of patterns + var totalPatternsCount = patterns.length + var filteredPatternsCount = filteredPatterns.length + const counter = document.getElementById("pattern-counter"); + counter.innerHTML = filteredPatternsCount + " of " + totalPatternsCount + " patterns displayed"; +} + +function filterSelection(filter) { + // Filter the patterns + const patternsData = getData() + patternsData.then(output => { + renderFilteredCards(output.patterns, output.filter_categories) + }); +} + +function changeFilterType(id) { + // Change Function to change the filter type when the user clicks an + // AND / OR operator button + + var filterType = id.split(":"); + var filter_category = filterType[0].replace('_button',''); + const patternsData = getData(); + patternsData.then(output => { + for (item = 0; item < output.filter_categories[filter_category].filter_types.length; item++) { + var unselectId = filter_category + "_button:" + output.filter_categories[filter_category].filter_types[item].toLowerCase() + const unselectButton = document.getElementById(unselectId); + unselectButton.classList.remove("pf-m-selected"); + } + const selectButton = document.getElementById(id); + selectButton.classList.add("pf-m-selected"); + renderFilteredCards(output.patterns, output.filter_categories) + }); +} + + +// Initialize the filters and pattern cards when the page loads +const patternsData = getData() +patternsData.then(output => { + renderFilter("TiersItems", "tier", output.filter_categories.tier); + renderFilter("IndustriesItems", "industries", output.filter_categories.industries); + renderFilter("RhProductsItems", "rh_products", output.filter_categories.rh_products); + renderFilter("OtherProductsItems", "other_products", output.filter_categories.other_products); + renderFilteredCards(output.patterns, output.filter_categories) +});