[KEYCLOAK-10962] Application screen for the new account console

This commit is contained in:
Bruno Oliveira da Silva 2019-10-25 20:34:36 +01:00
parent c3d80651bf
commit caf08da2af
3 changed files with 205 additions and 19 deletions

View file

@ -69,3 +69,21 @@ setUp=Set Up
passwordless=Passwordless passwordless=Passwordless
lastUpdate=Last Update lastUpdate=Last Update
unknown=Unknown unknown=Unknown
# Applications page
applicationsPageTitle=Applications
internalApp=Internal
thirdPartyApp=Third-party
offlineAccess=Offline Access
inUse=In use
notInUse=Not in use
applicationDetails=Application Details
client=Client
description=Description
baseUrl=URL
accessGrantedOn=Access granted on
removeButton=Remove access
removeModalTitle=Remove Access
removeModalMessage=This will remove the currently granted access permission for {0}. You will need to grant access again if you want to use this app.
confirmButton=Confirm
infoMessage=By clicking 'Remove Access', you will remove granted permissions of this application. This application will no longer use your information.

View file

@ -15,21 +15,189 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { AxiosResponse } from 'axios';
import {
DataList,
DataListItem,
DataListItemRow,
DataListCell,
DataListToggle,
DataListContent,
DataListItemCells,
Grid,
GridItem,
} from '@patternfly/react-core';
import { InfoAltIcon, CheckIcon, LinkIcon, BuilderImageIcon } from '@patternfly/react-icons';
import { ContentPage } from '../ContentPage';
import { ContinueCancelModal } from '../../widgets/ContinueCancelModal';
import { AccountServiceClient } from '../../account-service/account.service';
import {Msg} from '../../widgets/Msg';
declare const locale: string;
export interface ApplicationsPageProps { export interface ApplicationsPageProps {
} }
export class ApplicationsPage extends React.Component<ApplicationsPageProps> { export interface ApplicationsPageState {
isRowOpen: boolean[];
applications: Application[];
}
export interface GrantedScope {
displayTest: string;
id: string;
name: string;
}
export interface Consent {
createDate: number;
grantedScopes: GrantedScope[];
lastUpdatedDate: number;
}
interface Application {
baseUrl: string;
clientId: string;
clientName: string;
consent: Consent;
description: string;
inUse: boolean;
offlineAccess: boolean;
userConsentRequired: boolean;
scope: string[];
}
export class ApplicationsPage extends React.Component<ApplicationsPageProps, ApplicationsPageState> {
public constructor(props: ApplicationsPageProps) { public constructor(props: ApplicationsPageProps) {
super(props); super(props);
this.state = {
isRowOpen: [],
applications: []
};
this.fetchApplications();
}
private removeConsent = (clientId: string) => {
AccountServiceClient.Instance.doDelete("/applications/" + clientId + "/consent")
.then(() => {
this.fetchApplications();
});
}
private onToggle = (row: number): void => {
const newIsRowOpen: boolean[] = this.state.isRowOpen;
newIsRowOpen[row] = !newIsRowOpen[row];
this.setState({ isRowOpen: newIsRowOpen });
};
private fetchApplications(): void {
AccountServiceClient.Instance.doGet("/applications")
.then((response: AxiosResponse<Application[]>) => {
const applications = response.data;
this.setState({
isRowOpen: new Array(applications.length).fill(false),
applications: applications
});
});
} }
public render(): React.ReactNode { public render(): React.ReactNode {
return ( return (
<div> <ContentPage title={Msg.localize('applicationsPageTitle')}>
<h2>Hello Applications Page</h2> <DataList id="applications-list" aria-label={Msg.localize('applicationsPageTitle')}>
{this.state.applications.map((application: Application, appIndex: number) => {
return (
<DataListItem key={'application-' + appIndex} aria-labelledby="applications-list" isExpanded={this.state.isRowOpen[appIndex]}>
<DataListItemRow>
<DataListToggle
onClick={() => this.onToggle(appIndex)}
isExpanded={this.state.isRowOpen[appIndex]}
id={'applicationToggle' + appIndex}
aria-controls="expandable"
/>
<DataListItemCells
dataListCells={[
<DataListCell width={2} key={'app-' + appIndex}>
<BuilderImageIcon size='sm'/> {application.clientName ? application.clientName : application.clientId}
</DataListCell>,
<DataListCell width={2} key={'internal-' + appIndex}>
{application.userConsentRequired ? Msg.localize('thirdPartyApp') : Msg.localize('internalApp')}
{application.offlineAccess ? ', ' + Msg.localize('offlineAccess') : ''}
</DataListCell>,
<DataListCell width={2} key={'status-' + appIndex}>
{application.inUse ? Msg.localize('inUse') : Msg.localize('notInUse')}
</DataListCell>,
<DataListCell width={4} key={'baseUrl-' + appIndex}>
<a href={application.userConsentRequired ? application.baseUrl : '/auth' + application.baseUrl} target="_blank"><LinkIcon /> {application.baseUrl}</a>
</DataListCell>,
]}
/>
</DataListItemRow>
<DataListContent
noPadding={false}
aria-label={Msg.localize('applicationDetails')}
id="expandable"
isHidden={!this.state.isRowOpen[appIndex]}
>
<Grid sm={12} md={12} lg={12}>
<div className='pf-c-content'>
<GridItem><strong>{Msg.localize('client') + ': '}</strong> {application.clientId}</GridItem>
{application.description &&
<GridItem><strong>{Msg.localize('description') + ': '}</strong> {application.description}</GridItem>
}
<GridItem><strong>{Msg.localize('baseUrl') + ': '}</strong> {application.baseUrl}</GridItem>
{application.consent &&
<React.Fragment>
<GridItem span={12}>
<strong>Has access to:</strong>
</GridItem>
{application.consent.grantedScopes.map((scope: GrantedScope, scopeIndex: number) => {
return (
<React.Fragment key={'scope-' + scopeIndex} >
<GridItem offset={1}><CheckIcon /> {scope.name}</GridItem>
</React.Fragment>
)
})}
<GridItem><strong>{Msg.localize('accessGrantedOn') + ': '}</strong>
{new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
}).format(application.consent.createDate)}
</GridItem>
</React.Fragment>
}
</div> </div>
</Grid>
<Grid gutter='sm'>
<hr />
<GridItem>
<React.Fragment>
<ContinueCancelModal
buttonTitle={Msg.localize('removeButton')} // required
buttonVariant='secondary' // defaults to 'primary'
modalTitle={Msg.localize('removeModalTitle')} // required
modalMessage={Msg.localize('removeModalMessage', [application.clientId])}
modalContinueButtonLabel={Msg.localize('confirmButton')} // defaults to 'Continue'
onContinue={() => this.removeConsent(application.clientId)} // required
/>
</React.Fragment>
</GridItem>
<GridItem><InfoAltIcon /> {Msg.localize('infoMessage')}</GridItem>
</Grid>
</DataListContent>
</DataListItem>
)
})}
</DataList>
</ContentPage>
); );
} }
}; };

View file

@ -122,7 +122,7 @@
'./SkipToContent': '@empty', //'./SkipToContent/index.js', './SkipToContent': '@empty', //'./SkipToContent/index.js',
'./Switch': './Switch/index.js', './Switch': './Switch/index.js',
'./Tabs': './Tabs/index.js', //'./Tabs/index.js', './Tabs': './Tabs/index.js', //'./Tabs/index.js',
'./Text': '@empty', //'./Text/index.js', './Text': './Text/index.js', //'./Text/index.js',
'./TextArea': '@empty', //'./TextArea/index.js', './TextArea': '@empty', //'./TextArea/index.js',
'./TextInput': './TextInput/index.js', './TextInput': './TextInput/index.js',
'./Title': './Title/index.js', './Title': './Title/index.js',
@ -360,7 +360,7 @@
'./icons/buffer-icon.js': '@empty', './icons/buffer-icon.js': '@empty',
'./icons/bug-icon.js': '@empty', './icons/bug-icon.js': '@empty',
'./icons/build-icon.js': '@empty', './icons/build-icon.js': '@empty',
'./icons/builder-image-icon.js': '@empty', //'./icons/builder-image-icon.js': '@empty',
'./icons/building-icon.js': '@empty', './icons/building-icon.js': '@empty',
'./icons/bullhorn-icon.js': '@empty', './icons/bullhorn-icon.js': '@empty',
'./icons/bullseye-icon.js': '@empty', './icons/bullseye-icon.js': '@empty',
@ -431,7 +431,7 @@
'./icons/chat-icon.js': '@empty', './icons/chat-icon.js': '@empty',
//'./icons/check-circle-icon.js': '@empty', //'./icons/check-circle-icon.js': '@empty',
'./icons/check-double-icon.js': '@empty', './icons/check-double-icon.js': '@empty',
'./icons/check-icon.js': '@empty', //'./icons/check-icon.js': '@empty',
'./icons/check-square-icon.js': '@empty', './icons/check-square-icon.js': '@empty',
'./icons/cheese-icon.js': '@empty', './icons/cheese-icon.js': '@empty',
'./icons/chess-bishop-icon.js': '@empty', './icons/chess-bishop-icon.js': '@empty',
@ -906,7 +906,7 @@
'./icons/indent-icon.js': '@empty', './icons/indent-icon.js': '@empty',
'./icons/industry-icon.js': '@empty', './icons/industry-icon.js': '@empty',
'./icons/infinity-icon.js': '@empty', './icons/infinity-icon.js': '@empty',
'./icons/info-alt-icon.js': '@empty', //'./icons/info-alt-icon.js': '@empty',
//'./icons/info-circle-icon.js': '@empty', //'./icons/info-circle-icon.js': '@empty',
'./icons/info-icon.js': '@empty', './icons/info-icon.js': '@empty',
'./icons/infrastructure-icon.js': '@empty', './icons/infrastructure-icon.js': '@empty',