diff --git a/.eslintrc b/.eslintrc index 206609861e01..992dfe343fa3 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 c660b8d6605a..6f4678baf96e 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 8ca268df7ce9..bdef520fc678 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 a30a0b7eb30d..f15afb5a7bf7 100644 --- a/app/registries/foreman/settings/general.rb +++ b/app/registries/foreman/settings/general.rb @@ -81,9 +81,15 @@ 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 under the header in a banner (requires a page reload)."), default: nil, full_name: N_('Instance title')) + setting('instance_color', + type: :string, + description: N_("Color for the instance title banner. Will only be used if an instance title is defined. (requires a page reload)."), + default: 'default', + full_name: N_('Instance color'), + collection: proc { {'default' => _('Grey'), 'blue' => _('Blue'), 'red' => _('Red'), 'green' => _('Green'), 'gold' => _('Gold')} }) 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 c1f0f205bd02..51f555a29469 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -59,7 +59,7 @@ <% end %> <div id="rails-app-content" - class="pf-c-page" + class="pf-c-page <%= Foreman.settings.find('instance_title') ? 'user-banner-present' : '' %>" > <%= 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 cfa0c73d7691..836310e77532 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/Layout.js +++ b/webpack/assets/javascripts/react_app/components/Layout/Layout.js @@ -1,6 +1,12 @@ import React from 'react'; -import { Page, PageSidebar } from '@patternfly/react-core'; +import { + Page, + PageSidebar, + Banner, + Flex, + FlexItem, +} from '@patternfly/react-core'; import { layoutPropTypes, layoutDefaultProps } from './LayoutHelper'; import Header from './components/Toolbar/Header'; import Navigation from './Navigation'; @@ -27,30 +33,64 @@ const Layout = ({ document.body.classList.add('collapsed-nav'); } }; + const instance = data.instance_title; + const instanceColors = { + grey: 'default', + blue: 'info', + red: 'danger', + green: 'success', + gold: 'warning', + }; + const instanceColor = instanceColors[data.instance_color]; return ( <> - <Page - mainContainerId="foreman-main-container" - header={ - <Header data={data} onNavToggle={onNavToggle} isLoading={isLoading} /> - } - id="foreman-page" - sidebar={ - <PageSidebar - isNavOpen={!isCollapsed} - nav={ - <Navigation - items={items} - navigate={navigate} - navigationActiveItem={navigationActiveItem} - setNavigationActiveItem={setNavigationActiveItem} + <Flex + direction={{ default: 'column' }} + flexWrap={{ default: 'nowrap' }} + spaceItems={{ default: 'spaceItemsNone' }} + style={{ height: '100%' }} + > + <FlexItem> + {instance && ( + <Banner + isSticky + variant={instanceColor} + // style={{}} + className="instance-banner" + > + <div>{instance}</div> + </Banner> + )} + </FlexItem> + <FlexItem grow={{ default: 'grow' }} style={{ minHeight: 0 }}> + <Page + mainContainerId="foreman-main-container" + header={ + <Header + data={data} + onNavToggle={onNavToggle} + isLoading={isLoading} /> } - /> - } - > - {children} - </Page> + id="foreman-page" + sidebar={ + <PageSidebar + isNavOpen={!isCollapsed} + nav={ + <Navigation + items={items} + navigate={navigate} + navigationActiveItem={navigationActiveItem} + setNavigationActiveItem={setNavigationActiveItem} + /> + } + /> + } + > + {children} + </Page> + </FlexItem> + </Flex> </> ); }; 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 e0346ddc87b1..0f1674cf025f 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, }) => ( <Toolbar ouiaId="data-toolbar" id="data-toolbar" isFullHeight isStatic> @@ -39,9 +37,6 @@ const HeaderToolbar = ({ /> </ToolbarGroup> <ToolbarGroup alignment={{ default: 'alignRight' }}> - <ToolbarItem> - <InstanceTitleViewer title={instanceTitle} /> - </ToolbarItem> <ToolbarItem className="notifications_container"> <NotificationContainer data={{ url: notificationUrl }} /> </ToolbarItem> @@ -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 cd6e89539b4e..000000000000 --- 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 ( - <Tooltip - position={TooltipPosition.bottom} - id="instance-toggle-icon" - content={title} - > - <Icon type="fa" name="server small" /> - </Tooltip> - ); -}; - -InstanceTitleViewer.propTypes = { - /** Title to display */ - title: PropTypes.string, -}; -InstanceTitleViewer.defaultProps = { - title: '', -}; -export default InstanceTitleViewer;