Skip to content

Commit e926990

Browse files
authored
basic landing page (#3470)
* basic landing page Issue: AAH-2172
1 parent d57ad05 commit e926990

File tree

14 files changed

+291
-10
lines changed

14 files changed

+291
-10
lines changed

CHANGES/2172.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a landing page to our UI.

developer_guidelines.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Example:
8282

8383
```
8484
// Static route
85-
<Link to={formatPath(Paths.search)} />
85+
<Link to={formatPath(Paths.collections)} />
8686
8787
// Dynamic route
8888
<Link to={formatPath(Paths.editNamespace, { namespace: "NSname" })} />
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Card, CardBody, CardHeader, Title } from '@patternfly/react-core';
2+
import * as React from 'react';
3+
4+
interface IProps {
5+
title: string;
6+
body: React.ReactNode;
7+
}
8+
9+
export const LandingPageCard = ({ title, body }: IProps) => {
10+
return (
11+
<Card
12+
className='landing-page-card'
13+
style={{
14+
margin: '0 0 24px 24px',
15+
flex: '30%',
16+
borderTop: '3px solid #39a5dc',
17+
}}
18+
>
19+
{' '}
20+
<div
21+
style={{
22+
border: 0,
23+
borderBottom: '1px solid #d1d1d1',
24+
}}
25+
>
26+
<CardHeader>
27+
<Title headingLevel='h1' size='2xl'>
28+
{title}
29+
</Title>
30+
</CardHeader>
31+
</div>
32+
<CardBody style={{ marginTop: '24px' }}>{body}</CardBody>
33+
</Card>
34+
);
35+
};

src/components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export { LoadingPageSpinner } from './loading/loading-page-spinner';
6161
export { LoadingPageWithHeader } from './loading/loading-with-header';
6262
export { LoginLink } from './shared/login-link';
6363
export { Logo } from './logo/logo';
64+
export { LandingPageCard } from './cards/landing-page-card';
6465
export { Main } from './patternfly-wrappers/main';
6566
export { MarkdownEditor } from './markdown-editor/markdown-editor';
6667
export { NamespaceCard } from './cards/namespace-card';

src/containers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { default as CollectionDocs } from './collection-detail/collection-docs';
1111
export { default as CollectionImportLog } from './collection-detail/collection-import-log';
1212
export { default as CollectionDependencies } from './collection-detail/collection-dependencies';
1313
export { default as CollectionDistributions } from './collection-detail/collection-distributions';
14+
export { default as LandingPage } from './landing/landing-page';
1415
export { default as EditNamespace } from './edit-namespace/edit-namespace';
1516
export { default as LoginPage } from './login/login';
1617
export { default as MyImports } from './my-imports/my-imports';
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.footer-link {
2+
padding-right: 16px;
3+
}
+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { Trans, t } from '@lingui/macro';
2+
import * as React from 'react';
3+
import { Link } from 'react-router-dom';
4+
import {
5+
AlertList,
6+
AlertType,
7+
BaseHeader,
8+
LandingPageCard,
9+
Main,
10+
closeAlertMixin,
11+
} from 'src/components';
12+
import { AppContext } from 'src/loaders/app-context';
13+
import { Paths, formatPath } from 'src/paths';
14+
import { RouteProps, withRouter } from 'src/utilities';
15+
import './landing-page.scss';
16+
17+
interface IState {
18+
alerts: AlertType[];
19+
redirect: boolean;
20+
}
21+
22+
export class LandingPage extends React.Component<RouteProps, IState> {
23+
constructor(props) {
24+
super(props);
25+
26+
this.state = {
27+
alerts: [],
28+
redirect: false,
29+
};
30+
}
31+
32+
componentDidMount() {
33+
const { ai_deny_index } = this.context.featureFlags;
34+
if (!ai_deny_index) {
35+
this.setState({ redirect: true });
36+
}
37+
}
38+
39+
render() {
40+
const { alerts, redirect } = this.state;
41+
42+
if (redirect) {
43+
setTimeout(() => this.props.navigate(formatPath(Paths.collections)));
44+
return null;
45+
}
46+
47+
return (
48+
<React.Fragment>
49+
<AlertList
50+
alerts={alerts}
51+
closeAlert={(i) => this.closeAlert(i)}
52+
></AlertList>
53+
<BaseHeader title={t`Welcome to Beta Galaxy`} />
54+
<Main>
55+
<div
56+
style={{
57+
display: 'flex',
58+
flexWrap: 'wrap',
59+
alignContent: 'flex-start',
60+
marginLeft: '-24px',
61+
}}
62+
>
63+
<LandingPageCard
64+
title={t`Download`}
65+
body={
66+
<React.Fragment>
67+
<p>{t`Jump-start your automation project with great content from the Ansible community. Galaxy provides pre-packaged units of work known to Ansible as roles and collections.`}</p>
68+
<br />
69+
70+
<p>
71+
{t`Content from roles and collections can be referenced in Ansible PlayBooks and immediately put to work. You'll find content for provisioning infrastructure, deploying applications, and all of the tasks you do everyday.`}{' '}
72+
</p>
73+
<br />
74+
75+
<p>
76+
<Trans>
77+
Use the{' '}
78+
<Link to={formatPath(Paths.collections)}>
79+
Search page{' '}
80+
</Link>
81+
to find content for your project, then download them onto
82+
your Ansible host using{' '}
83+
<a
84+
href='https://docs.ansible.com/ansible/latest/reference_appendices/galaxy.html#the-command-line-tool'
85+
target='_blank'
86+
rel='noreferrer'
87+
>
88+
ansible-galaxy
89+
</a>
90+
, the command line tool that comes bundled with Ansible.
91+
</Trans>
92+
</p>
93+
</React.Fragment>
94+
}
95+
/>
96+
<LandingPageCard
97+
title={t`Share`}
98+
body={
99+
<React.Fragment>
100+
<p>{t`Help other Ansible users by sharing the awesome roles and collections you create.`}</p>
101+
<br />
102+
<p>{t`Maybe you have automation for installing and configuring a popular software package, or for deploying software built by your company. Whatever it is, use Galaxy to share it with the community.`}</p>
103+
<br />
104+
105+
<p>
106+
<Trans>
107+
Red Hat is working on exciting new Ansible content
108+
development capabilities within the context of{' '}
109+
<a
110+
href='https://www.redhat.com/en/engage/project-wisdom?extIdCarryOver=true&sc_cid=701f2000001OH6uAAG'
111+
target='_blank'
112+
rel='noopener noreferrer'
113+
>
114+
Ansible Lightspeed
115+
</a>{' '}
116+
to help other automators build Ansible content. Your roles
117+
and collections may be used as training data for a machine
118+
learning model that provides Ansible automation content
119+
recommendations. If you have concerns, please contact the
120+
Ansible team at{' '}
121+
<a href='mailto:ansible-content-ai@redhat.com'>
122+
ansible-content-ai@redhat.com
123+
</a>
124+
</Trans>
125+
</p>
126+
</React.Fragment>
127+
}
128+
/>
129+
<LandingPageCard
130+
title={t`Featured`}
131+
body={
132+
<React.Fragment>
133+
<b>
134+
<p>{t`AnsibleFest`}</p>
135+
</b>
136+
<br />
137+
<p>
138+
<a
139+
href='https://www.redhat.com/en/summit/ansiblefest?intcmp=7013a0000034lvmAAA'
140+
target='_blank'
141+
rel='noreferrer'
142+
>
143+
<img
144+
width='100%'
145+
alt='Ansible Fest at Red Hat Summit May 23rd to 25th 2023'
146+
src='https://www.ansible.com/hubfs/rh-2023-summit-ansiblefest-ansible-galaxy-site-200x200.png'
147+
/>
148+
</a>
149+
</p>
150+
<hr
151+
style={{
152+
boxSizing: 'content-box',
153+
height: 0,
154+
marginTop: 20,
155+
marginBottom: 20,
156+
border: 0,
157+
borderTop: '1px solid #f1f1f1',
158+
}}
159+
/>
160+
<p>
161+
<b>
162+
{t`Extend the power of Ansible to your entire team.`}{' '}
163+
</b>
164+
</p>
165+
<br />
166+
<p>{t`Try Red Hat Ansible Automation Platform`}</p>
167+
<br />
168+
<p>
169+
<a
170+
href='https://www.redhat.com/en/technologies/management/ansible/try-it?sc_cid=7013a0000030vCCAAY'
171+
target='_blank'
172+
rel='noreferrer'
173+
>{t`Get the trial`}</a>
174+
</p>
175+
</React.Fragment>
176+
}
177+
/>
178+
<LandingPageCard
179+
title={t`Terms of Use`}
180+
body={
181+
<React.Fragment>
182+
<div className='footer-parent-links'>
183+
<span>
184+
<a
185+
className='footer-link'
186+
href='https://www.redhat.com/en/about/privacy-policy'
187+
target='_blank'
188+
rel='noreferrer'
189+
>{t`Privacy statement`}</a>
190+
</span>
191+
<span>
192+
<a
193+
className='footer-link'
194+
href='https://www.redhat.com/en/about/terms-use'
195+
target='_blank'
196+
rel='noreferrer'
197+
>{t`Terms of use`}</a>
198+
</span>
199+
<span>
200+
<a
201+
className='footer-link'
202+
href='https://www.redhat.com/en/about/all-policies-guidelines'
203+
target='_blank'
204+
rel='noreferrer'
205+
>{t`All policies and guidelines`}</a>
206+
</span>
207+
<span>
208+
<a
209+
className='footer-link'
210+
href='https://www.redhat.com/en/about/digital-accessibility'
211+
target='_blank'
212+
rel='noreferrer'
213+
>{t`Digital accessibility`}</a>
214+
</span>
215+
</div>
216+
</React.Fragment>
217+
}
218+
/>
219+
</div>
220+
</Main>
221+
</React.Fragment>
222+
);
223+
}
224+
225+
private get closeAlert() {
226+
return closeAlertMixin('alerts');
227+
}
228+
229+
private addAlert(alert: AlertType) {
230+
this.setState({
231+
alerts: [...this.state.alerts, alert],
232+
});
233+
}
234+
}
235+
236+
export default withRouter(LandingPage);
237+
238+
LandingPage.contextType = AppContext;

src/containers/login/login.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class LoginPage extends React.Component<RouteProps, IState> {
2929
};
3030

3131
const params = ParamHelper.parseParamString(this.props.location.search);
32-
this.redirectPage = params['next'] || formatPath(Paths.search);
32+
this.redirectPage = params['next'] || formatPath(Paths.landingPage);
3333
}
3434

3535
render() {

src/containers/token/token-insights.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ class TokenInsights extends React.Component<RouteProps, IState> {
103103
page to sync collections curated by your organization to the Red
104104
Hat Certified repository in your private Automation Hub. Users
105105
with the correct permissions can use the sync toggles on the{' '}
106-
<Link to={formatPath(Paths.search)}>Collections</Link> page to
107-
control which collections are added to their organization&apos;s
108-
sync repository.
106+
<Link to={formatPath(Paths.collections)}>Collections</Link> page
107+
to control which collections are added to their
108+
organization&apos;s sync repository.
109109
</Trans>
110110
</p>
111111
</section>

src/loaders/insights/routes.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const routes = [
154154
{ path: Paths.myImports, component: MyImports },
155155
{ path: Paths.namespace, component: NamespaceDetail },
156156
{ path: Paths.collections, component: Search },
157-
{ path: Paths.search, component: Search },
157+
{ path: '/', component: Search },
158158
];
159159

160160
/**

src/loaders/standalone/layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export const StandaloneLayout = ({
127127
<PageHeader
128128
logo={<SmallLogo alt={APPLICATION_NAME}></SmallLogo>}
129129
logoComponent={({ children }) => (
130-
<Link to={formatPath(Paths.collections)}>{children}</Link>
130+
<Link to={formatPath(Paths.landingPage)}>{children}</Link>
131131
)}
132132
headerTools={
133133
<PageHeaderTools>

src/loaders/standalone/routes.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ExecutionEnvironmentRegistryList,
2929
GroupDetail,
3030
GroupList,
31+
LandingPage,
3132
LegacyNamespace,
3233
LegacyNamespaces,
3334
LegacyRole,
@@ -287,7 +288,7 @@ export class StandaloneRoutes extends React.Component<IRoutesProps> {
287288
{ component: MyImports, path: Paths.myImports },
288289
{ component: NamespaceDetail, path: Paths.namespace },
289290
{ component: Search, path: Paths.collections },
290-
{ component: Search, path: Paths.search },
291+
{ component: LandingPage, path: Paths.landingPage },
291292
];
292293
}
293294

src/paths.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function formatPath(path: Paths, data = {}, params?: ParamType) {
1111
.replace(/\/$/, '')
1212
: '';
1313
url += (path as string) + '/';
14+
url = url.replaceAll('//', '/');
1415

1516
for (const k of Object.keys(data)) {
1617
url = url.replace(':' + k, encodeURIComponent(data[k]));
@@ -86,7 +87,7 @@ export enum Paths {
8687
myImports = '/my-imports',
8788
login = '/login',
8889
logout = '/logout',
89-
search = '/',
90+
landingPage = '/',
9091
legacyRole = '/legacy/roles/:username/:name',
9192
legacyRoles = '/legacy/roles/',
9293
legacyNamespace = '/legacy/namespaces/:namespaceid',

test/cypress/e2e/execution_environments/execution_environments_edit.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ describe('execution environments', () => {
33

44
before(() => {
55
cy.login();
6-
76
cy.deleteRegistriesManual();
87
cy.deleteContainersManual();
98

@@ -23,6 +22,7 @@ describe('execution environments', () => {
2322

2423
beforeEach(() => {
2524
cy.login();
25+
cy.wait(10000);
2626
cy.menuGo('Execution Environments > Execution Environments');
2727
});
2828

0 commit comments

Comments
 (0)