[KEYCLOAK-10962] Application screen for the new account console
This commit is contained in:
parent
c3d80651bf
commit
caf08da2af
3 changed files with 205 additions and 19 deletions
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in a new issue