Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #36872 - Banner for different foreman instances #9872

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"nonpersistent",
"noopener",
"noreferrer",
"nowrap",
"num",
"numpad",
"operatingsystem",
Expand Down
17 changes: 14 additions & 3 deletions app/assets/stylesheets/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,7 +36,6 @@ html {
grid-template-columns: unset;
grid-template-areas: unset;
}

}

body {
Expand Down
3 changes: 2 additions & 1 deletion app/helpers/layout_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion app/registries/foreman/settings/general.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.'),
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/base.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -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) %>
Expand Down
59 changes: 38 additions & 21 deletions webpack/assets/javascripts/react_app/components/Layout/Layout.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -29,28 +30,44 @@ const Layout = ({
};
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>
<InstanceBanner data={data} />
</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>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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 && (
<Banner
isSticky
style={{ '--pf-c-banner--BackgroundColor': instanceColor }}
className="instance-banner"
>
<div
style={{
color: getContrastColor(instanceColor),
}}
>
{instance}
</div>
</Banner>
)
);
};

InstanceBanner.propTypes = {
data: PropTypes.shape({
instance_title: PropTypes.string,
instance_color: PropTypes.string,
}),
};

InstanceBanner.defaultProps = {
data: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
organizationPropType,
userPropType,
} from '../../LayoutHelper';
import InstanceTitleViewer from './InstanceTitleViewer';
import './HeaderToolbar.scss';

const HeaderToolbar = ({
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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,
Expand All @@ -69,7 +63,6 @@ HeaderToolbar.propTypes = {
};

HeaderToolbar.defaultProps = {
instance_title: null,
user: {},
isLoading: layoutDefaultProps.isLoading,
};
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ exports[`HeaderToolbar rendering render HeaderToolbar 1`] = `
}
}
>
<ToolbarItem>
<InstanceTitleViewer
title="Production"
/>
</ToolbarItem>
<ToolbarItem
className="notifications_container"
>
Expand Down