From 89b5a1f1096d311460ea2de092f9d8acd49940e6 Mon Sep 17 00:00:00 2001 From: MariaAga Date: Mon, 30 Oct 2023 13:30:37 +0100 Subject: [PATCH] Fixes #36872 - Banner to show foreman instance --- .eslintrc | 1 + app/assets/stylesheets/base.scss | 17 ++++- app/helpers/layout_helper.rb | 3 +- app/registries/foreman/settings/general.rb | 7 +- app/views/layouts/base.html.erb | 2 +- .../react_app/components/Layout/Layout.js | 59 ++++++++++------ .../Layout/__tests__/Layout.test.js | 1 + .../Layout/components/InstanceBanner.js | 69 +++++++++++++++++++ .../components/Toolbar/HeaderToolbar.js | 7 -- .../components/Toolbar/InstanceTitleViewer.js | 29 -------- .../__snapshots__/HeaderToolbar.test.js.snap | 5 -- 11 files changed, 132 insertions(+), 68 deletions(-) create mode 100644 webpack/assets/javascripts/react_app/components/Layout/components/InstanceBanner.js delete mode 100644 webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/InstanceTitleViewer.js diff --git a/.eslintrc b/.eslintrc index 206609861e0..992dfe343fa 100644 --- a/.eslintrc +++ b/.eslintrc @@ -105,6 +105,7 @@ "nonpersistent", "noopener", "noreferrer", + "nowrap", "num", "numpad", "operatingsystem", diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 35834c1662c..4cd4a168e08 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -5,14 +5,26 @@ html { } #rails-app-content { + --header-height: 70px; position: absolute; - top: 70px; + top: var(--header-height); left: 0; right: 0; bottom: 0; overflow: auto; - height: calc(100% - 70px); + height: calc(100% - var(--header-height)); margin-left: 250px; + &.user-banner-present { + --banner-height: calc( + 2 * var(--pf-global--spacer--xs) + + (var(--pf-global--LineHeight--md) * var(--pf-global--FontSize--sm)) + ); // banner height is line height and a small padding + top: calc( + var(--header-height) + var(--banner-height) + ); + + height: calc(100% - var(--header-height) - var(--banner-height)); + } .rails-table-toolbar { padding-bottom: 0; display: flex; @@ -24,7 +36,6 @@ html { grid-template-columns: unset; grid-template-areas: unset; } - } body { diff --git a/app/helpers/layout_helper.rb b/app/helpers/layout_helper.rb index 8ca268df7ce..bdef520fc67 100644 --- a/app/helpers/layout_helper.rb +++ b/app/helpers/layout_helper.rb @@ -51,7 +51,8 @@ def layout_data user: fetch_user, brand: 'foreman', root: main_app.root_path, locations: fetch_locations, orgs: fetch_organizations, - instance_title: Setting[:instance_title] + instance_title: Setting[:instance_title], + instance_color: Setting[:instance_color] } end diff --git a/app/registries/foreman/settings/general.rb b/app/registries/foreman/settings/general.rb index bb2c86a2c40..bf62e7eed3e 100644 --- a/app/registries/foreman/settings/general.rb +++ b/app/registries/foreman/settings/general.rb @@ -86,9 +86,14 @@ collection: timezones) setting('instance_title', type: :string, - description: N_("The instance title is shown on the top navigation bar (requires a page reload)."), + description: N_("The instance title is shown above the header in a banner (requires a page reload)."), default: nil, full_name: N_('Instance title')) + setting('instance_color', + type: :string, + description: N_("Hex value. for color for the instance title banner. Will only be used if an instance title is defined. If no valid value is given, the default value is: #000000. (requires a page reload)."), + default: '#000000', + full_name: N_('Instance color')) setting('audits_period', type: :integer, description: N_('Duration in days to preserve audits for. Leave empty to disable the audits cleanup.'), diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index c1f0f205bd0..51f555a2946 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -59,7 +59,7 @@ <% end %>
<%= yield(:content) %> diff --git a/webpack/assets/javascripts/react_app/components/Layout/Layout.js b/webpack/assets/javascripts/react_app/components/Layout/Layout.js index cfa0c73d769..a740ad334a7 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/Layout.js +++ b/webpack/assets/javascripts/react_app/components/Layout/Layout.js @@ -1,7 +1,8 @@ import React from 'react'; -import { Page, PageSidebar } from '@patternfly/react-core'; +import { Page, PageSidebar, Flex, FlexItem } from '@patternfly/react-core'; import { layoutPropTypes, layoutDefaultProps } from './LayoutHelper'; +import { InstanceBanner } from './components/InstanceBanner'; import Header from './components/Toolbar/Header'; import Navigation from './Navigation'; import './layout.scss'; @@ -29,28 +30,44 @@ const Layout = ({ }; return ( <> - - } - id="foreman-page" - sidebar={ - + + + + + } - /> - } - > - {children} - + id="foreman-page" + sidebar={ + + } + /> + } + > + {children} + + + ); }; diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js index 780b2e6fd39..0f0ff7e619b 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js +++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js @@ -31,5 +31,6 @@ describe('Layout', () => { expect(screen.getByText('Monitor')).toBeVisible(); expect(screen.getByText('Dashboard')).not.toBeVisible(); expect(screen.getByText('All Hosts')).toBeVisible(); + expect(screen.getByText('Production')).toBeVisible(); }); }); diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/InstanceBanner.js b/webpack/assets/javascripts/react_app/components/Layout/components/InstanceBanner.js new file mode 100644 index 00000000000..6c759b0e1b0 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/Layout/components/InstanceBanner.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Banner } from '@patternfly/react-core'; + +const getContrastColor = backgroundColor => { + const hexToRgb = hex => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } + : null; + }; + backgroundColor = hexToRgb(backgroundColor); + // Calculate the relative luminance of the background color + const luminance = + (0.2126 * backgroundColor.r + + 0.7152 * backgroundColor.g + + 0.0722 * backgroundColor.b) / + 255; + + // Choose black or white text based on the relative luminance + return luminance > 0.5 ? 'black' : 'white'; +}; +const validateHexColor = instanceColor => { + // Check if the string is a valid hex color code + if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(instanceColor)) { + return instanceColor; + } + return '#000000'; +}; + +export const InstanceBanner = ({ data }) => { + if (!data || !data.instance_title) { + return null; + } + const instance = data.instance_title; + const instanceColor = validateHexColor(data.instance_color); + return ( + instance && ( + +
+ {instance} +
+
+ ) + ); +}; + +InstanceBanner.propTypes = { + data: PropTypes.shape({ + instance_title: PropTypes.string, + instance_color: PropTypes.string, + }), +}; + +InstanceBanner.defaultProps = { + data: {}, +}; diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js index e0346ddc87b..0f1674cf025 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js +++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js @@ -17,7 +17,6 @@ import { organizationPropType, userPropType, } from '../../LayoutHelper'; -import InstanceTitleViewer from './InstanceTitleViewer'; import './HeaderToolbar.scss'; const HeaderToolbar = ({ @@ -26,7 +25,6 @@ const HeaderToolbar = ({ notification_url: notificationUrl, user, stop_impersonation_url: stopImpersonationUrl, - instance_title: instanceTitle, isLoading, }) => ( @@ -39,9 +37,6 @@ const HeaderToolbar = ({ /> - - - @@ -60,7 +55,6 @@ const HeaderToolbar = ({ ); HeaderToolbar.propTypes = { stop_impersonation_url: PropTypes.string.isRequired, - instance_title: PropTypes.string, locations: locationPropType.isRequired, orgs: organizationPropType.isRequired, notification_url: PropTypes.string.isRequired, @@ -69,7 +63,6 @@ HeaderToolbar.propTypes = { }; HeaderToolbar.defaultProps = { - instance_title: null, user: {}, isLoading: layoutDefaultProps.isLoading, }; diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/InstanceTitleViewer.js b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/InstanceTitleViewer.js deleted file mode 100644 index cd6e89539b4..00000000000 --- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/InstanceTitleViewer.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { Icon } from 'patternfly-react'; -import { Tooltip, TooltipPosition } from '@patternfly/react-core'; -import PropTypes from 'prop-types'; - -const InstanceTitleViewer = ({ title }) => { - if (!title) { - return null; - } - - return ( - - - - ); -}; - -InstanceTitleViewer.propTypes = { - /** Title to display */ - title: PropTypes.string, -}; -InstanceTitleViewer.defaultProps = { - title: '', -}; -export default InstanceTitleViewer; diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap index 324a0762eaf..cba23cba620 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap +++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap @@ -53,11 +53,6 @@ exports[`HeaderToolbar rendering render HeaderToolbar 1`] = ` } } > - - -