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

Add ability to open cluster web console from Dashboard #378

Merged
merged 8 commits into from
Oct 25, 2021
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 .deps/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@
| [`jsdom@16.7.0`](https://github.com/jsdom/jsdom.git) | MIT | |
| [`json-parse-better-errors@1.0.2`](https://github.com/zkat/json-parse-better-errors) | MIT | clearlydefined |
| [`json-parse-even-better-errors@2.3.1`](https://github.com/npm/json-parse-even-better-errors) | MIT | clearlydefined |
| [`json-schema@0.3.0`](http://github.com/kriszyp/json-schema) | (AFL-2.1 OR BSD-3-Clause) | clearlydefined |
| [`json-stable-stringify-without-jsonify@1.0.1`](git://github.com/samn/json-stable-stringify.git) | MIT | clearlydefined |
| [`json5@2.2.0`](git+https://github.com/json5/json5.git) | MIT | clearlydefined |
| [`jsonparse@1.3.1`](http://github.com/creationix/jsonparse.git) | MIT | clearlydefined |
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,45 @@ Field `"links"` allows you to configure links in the masthead, like
]
```

#### External Applications Menu (experimental)

The Dashboard has the ability to provide the way to navigate to the OpenShift cluster console via the `Applications` menu that is shown in the Dashboard masthead.
This ability can be tunned by setting the environment variables:
| Env var | Description | Default value |
| ------- | ----------- | ------------- |
| OPENSHIFT_CONSOLE_GROUP | The group title where Console link is shown | Applications |
| OPENSHIFT_CONSOLE_TITLE | The title that is displayed near icon. Set to "" to hide that Console Link at all. | OpenShift Console |
| OPENSHIFT_CONSOLE_ICON | The icon that is used for the link | ${CONSOLE_URL}/static/assets/redhat.png |

The following example shows how to provision that env vars with Che Operator:
```sh
CHE_NAMESPACE="eclipse-che"
cat <<EOF | kubectl apply -f -
kind: ConfigMap
apiVersion: v1
metadata:
name: che-dashboard-custom-config
namespace: eclipse-che
labels:
app.kubernetes.io/component: che-dashboard-configmap
app.kubernetes.io/part-of: che.eclipse.org
annotations:
che.eclipse.org/OPENSHIFT_CONSOLE_GROUP_env-name: OPENSHIFT_CONSOLE_GROUP
che.eclipse.org/OPENSHIFT_CONSOLE_TITLE_env-name: OPENSHIFT_CONSOLE_TITLE
che.eclipse.org/OPENSHIFT_CONSOLE_ICON_env-name: OPENSHIFT_CONSOLE_ICON
che.eclipse.org/mount-as: env
data:
OPENSHIFT_CONSOLE_GROUP: Apps
OPENSHIFT_CONSOLE_TITLE: OpenShift Container Platform
OPENSHIFT_CONSOLE_ICON: https://example.com/icon.png
EOF

# Due temporary limitation we need to rollout che operator to apply changes
kubectl rollout restart deployment/che-operator -n $CHE_NAMESPACE
```

**Note**: This way to configure dashboard is experimental and may be changed.

## License

Che is open sourced under the Eclipse Public License 2.0.
18 changes: 18 additions & 0 deletions packages/common/src/dto/application-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

export interface ApplicationInfo {
url: string;
title: string;
icon: string;
group?: string;
}
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import helpers from './helpers';

export * from './dto/application-info';

export {
helpers,
};
Expand Down
1 change: 1 addition & 0 deletions packages/dashboard-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"eslint-plugin-notice": "^0.9.10",
"file-loader": "^6.2.0",
"jest": "^26.6.3",
"json-schema": "^0.3.0",
"nodemon": "^2.0.12",
"source-map-loader": "^1.0.0",
"ts-jest": "^26.5.1",
Expand Down
34 changes: 34 additions & 0 deletions packages/dashboard-backend/src/api/clusterInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import { ApplicationInfo } from '@eclipse-che/common';
import { FastifyInstance } from 'fastify';
import { baseApiPath } from '../constants/config';
import { clusterConsoleGroup, clusterConsoleIcon, clusterConsoleTitle, clusterConsoleUrl } from '../devworkspace-client/services/cluster-info';
import { getSchema } from '../services/helpers';

const tags = ['clusterInfo'];

export function registerClusterInfo(server: FastifyInstance) {

server.get(
`${baseApiPath}/cluster-info`,
getSchema({ tags }),
async () => ({
icon: clusterConsoleIcon,
title: clusterConsoleTitle,
url: clusterConsoleUrl,
group: clusterConsoleGroup,
} as ApplicationInfo)
);

}
47 changes: 33 additions & 14 deletions packages/dashboard-backend/src/constants/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
*/

import { template } from './examples';
import { JSONSchema7 } from 'json-schema';

export const authenticationHeaderSchema = {
export const authenticationHeaderSchema: JSONSchema7 = {
type: 'object',
properties: {
'authorization': {
Expand All @@ -21,7 +22,7 @@ export const authenticationHeaderSchema = {
}
};

export const namespacedWorkspaceSchema = {
export const namespacedWorkspaceSchema: JSONSchema7 = {
type: 'object',
properties: {
namespace: {
Expand All @@ -34,7 +35,7 @@ export const namespacedWorkspaceSchema = {
required: ['namespace', 'workspaceName']
};

export const namespacedSchema = {
export const namespacedSchema: JSONSchema7 = {
type: 'object',
properties: {
namespace: {
Expand All @@ -44,9 +45,17 @@ export const namespacedSchema = {
required: ['namespace']
};

export const patchSchema = {
export const patchSchema: JSONSchema7 = {
type: 'array',
example: [{
items: {
type: 'object',
properties: {
op: { type: 'string' },
path: { type: 'string' },
value: {} // matches any value
},
},
examples: [{
op: 'replace',
path: '/spec/started',
value: true
Expand All @@ -63,7 +72,7 @@ export const devworkspaceSchema = {
required: ['devworkspace']
};

export const templateStartedSchema = {
export const templateStartedSchema: JSONSchema7 = {
type: 'object',
properties: {
template: {
Expand All @@ -73,10 +82,15 @@ export const templateStartedSchema = {
kind: { type: 'string' },
metadata: {
type: 'object',
template: {
name: { type: 'string' },
namespace: { type: 'string' },
ownerReferences: { type: 'array' },
properties: {
template: {
type: 'object',
properties: {
name: { type: 'string' },
namespace: { type: 'string' },
ownerReferences: { type: 'array' },
}
}
}
},
spec: {
Expand All @@ -86,16 +100,21 @@ export const templateStartedSchema = {
components: { type: 'array' },
events: {
type: 'object',
template: {
preStart: {
type: 'array'
properties: {
template: {
type: 'object',
properties: {
preStart: {
type: 'array'
}
}
}
}
},
},
},
},
example: template
examples: [template]
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-2021 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

export const clusterConsoleUrl = process.env['OPENSHIFT_CONSOLE_URL'] || '';
export const clusterConsoleTitle = process.env['OPENSHIFT_CONSOLE_TITLE']
|| 'Openshift Console';
export const clusterConsoleIcon = process.env['OPENSHIFT_CONSOLE_ICON']
|| (clusterConsoleUrl ? clusterConsoleUrl + '/static/assets/redhat.svg' : '');
export const clusterConsoleGroup = process.env['OPENSHIFT_CONSOLE_GROUP'];
6 changes: 6 additions & 0 deletions packages/dashboard-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { registerCors } from './cors';
import { registerSwagger } from './swagger';
import { getMessage } from '@eclipse-che/common/lib/helpers/errors';
import { isLocalRun } from './local-run';
import { registerClusterInfo } from './api/clusterInfo';
import { clusterConsoleIcon, clusterConsoleTitle, clusterConsoleUrl } from './devworkspace-client/services/cluster-info';

const CHE_HOST = process.env.CHE_HOST as string;

Expand Down Expand Up @@ -63,6 +65,10 @@ registerDevworkspaceWebsocketWatcher(server);

registerTemplateApi(server);

if (clusterConsoleUrl) {
registerClusterInfo(server);
}

registerCors(isLocalRun, server);
if (isLocalRun) {
registerLocalServers(server, CHE_HOST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Red Hat, Inc. - initial API and implementation
*/

import * as React from 'react';
import React from 'react';
import {
AboutModal as PatternflyAboutModal,
TextContent,
Expand All @@ -19,34 +19,38 @@ import {
} from '@patternfly/react-core';
import { detect } from 'detect-browser';

type AboutModalProps = {
type Props = {
productName: string | undefined;
serverVersion: string | undefined;
logo: string;
isOpen: boolean;
closeAboutModal: () => void;
closeModal: () => void;
username: string | undefined;
};

type AboutModalItemsProps = {
username: string | undefined;
serverVersion: string | undefined;
browserVersion: string | null | undefined;
browserOS: string | null | undefined;
browserName: string | null | undefined;
};
export class AboutModal extends React.PureComponent<Props> {
private browserVersion: string | undefined | null;
private browserOS: string | undefined | null;
private browserName: string | undefined;

constructor(props: Props) {
super(props);

const AboutModalItems: React.FC<AboutModalItemsProps> = (
props: AboutModalItemsProps
) => {
const dashboardVersion = process.env.DASHBOARD_VERSION;
const serverVersion = props.serverVersion;
const username = props.username;
const browserVersion = props.browserVersion;
const browserOS = props.browserOS;
const browserName = props.browserName;
return (
<>
const browser = detect();
this.browserVersion = browser?.version;
this.browserOS = browser?.os;
this.browserName = browser?.name;
}

private buildContent(): React.ReactElement {
const dashboardVersion = process.env.DASHBOARD_VERSION;
const serverVersion = this.props.serverVersion;
const username = this.props.username;
const browserVersion = this.browserVersion;
const browserOS = this.browserOS;
const browserName = this.browserName;

return (
<TextContent>
<TextList component="dl">
{dashboardVersion && (
Expand Down Expand Up @@ -123,38 +127,27 @@ const AboutModalItems: React.FC<AboutModalItemsProps> = (
)}
</TextList>
</TextContent>
</>
);
};
);
}

export class AboutModal extends React.PureComponent<AboutModalProps> {
public render(): React.ReactElement {
const { isOpen, closeAboutModal } = this.props;
const productName = this.props.productName;
const logo = this.props.logo;
const serverVersion = this.props.serverVersion;
const userName = this.props.username;
const {
isOpen,
logo,
productName,
} = this.props;

const browser = detect();
const browserVersion = browser?.version;
const browserOS = browser?.os;
const browserName = browser?.name;
const modalContent = this.buildContent();

return (
<PatternflyAboutModal
isOpen={isOpen}
onClose={closeAboutModal}
onClose={() => this.props.closeModal()}
brandImageSrc={logo}
brandImageAlt={`${productName} logo`}
noAboutModalBoxContentContainer={true}
>
<AboutModalItems
serverVersion={serverVersion}
username={userName}
browserOS={browserOS}
browserVersion={browserVersion}
browserName={browserName}
/>
{modalContent}
</PatternflyAboutModal>
);
}
Expand Down
Loading