Merge pull request #2085 from a1mery/app-secret-expiration-pagination
[react-graph-secret-expiration] - Add pagination
This commit is contained in:
commit
d16f505759
|
@ -10,7 +10,7 @@ I got the idea from this great article [Use Power Automate to Notify of Upcoming
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
|
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
|
||||||
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
|
![Node.js LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v12%20%7C%20LTS%20v10-green.svg)
|
||||||
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
|
||||||
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
![Teams N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
|
||||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||||
|
@ -32,6 +32,7 @@ react-graph-app-secret-expiration | [Aimery Thomas](https://github.com/a1mery) (
|
||||||
Version|Date|Comments
|
Version|Date|Comments
|
||||||
-------|----|--------
|
-------|----|--------
|
||||||
1.0|September 17, 2021|Initial release
|
1.0|September 17, 2021|Initial release
|
||||||
|
1.1|October 10, 2021|Add pagination
|
||||||
|
|
||||||
|
|
||||||
## Minimal Path to Awesome
|
## Minimal Path to Awesome
|
||||||
|
@ -39,14 +40,14 @@ Version|Date|Comments
|
||||||
- Clone this repository
|
- Clone this repository
|
||||||
- Ensure that you are at the solution folder
|
- Ensure that you are at the solution folder
|
||||||
- In the command-line run:
|
- In the command-line run:
|
||||||
- **npm install**
|
- `npm install`
|
||||||
- **gulp bundle**
|
- `gulp bundle`
|
||||||
- **gulp package-solution**
|
- `gulp package-solution`
|
||||||
- Deploy the package to your app catalog
|
- Deploy the package to your app catalog
|
||||||
- Approve the API permission request from the SharePoint admin
|
- Approve the API permission request from the SharePoint admin
|
||||||
- Add the web part to a page
|
- Add the web part to a page
|
||||||
- In the command-line run:
|
- In the command-line run:
|
||||||
- **gulp serve --nobrowser**
|
- `gulp serve --nobrowser`
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"This sample web part shows the list of your applications registered in Azure AD along with their associated client secret/certificate expiration date."
|
"This sample web part shows the list of your applications registered in Azure AD along with their associated client secret/certificate expiration date."
|
||||||
],
|
],
|
||||||
"creationDateTime": "2021-09-17",
|
"creationDateTime": "2021-09-17",
|
||||||
"updateDateTime": "2021-09-17",
|
"updateDateTime": "2021-10-10",
|
||||||
"products": [
|
"products": [
|
||||||
"SharePoint",
|
"SharePoint",
|
||||||
"Office"
|
"Office"
|
||||||
|
@ -22,6 +22,10 @@
|
||||||
{
|
{
|
||||||
"key": "SPFX-VERSION",
|
"key": "SPFX-VERSION",
|
||||||
"value": "1.12.1"
|
"value": "1.12.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "PNPCONTROLS",
|
||||||
|
"value": "Pagination"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"thumbnails": [
|
"thumbnails": [
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"solution": {
|
"solution": {
|
||||||
"name": "react-graph-app-secret-expiration-client-side-solution",
|
"name": "react-graph-app-secret-expiration-client-side-solution",
|
||||||
"id": "b25d85a4-7310-408a-a263-25959b0a5b1b",
|
"id": "b25d85a4-7310-408a-a263-25959b0a5b1b",
|
||||||
"version": "1.0.0.0",
|
"version": "1.1.0.0",
|
||||||
"includeClientSideAssets": true,
|
"includeClientSideAssets": true,
|
||||||
"isDomainIsolated": false,
|
"isDomainIsolated": false,
|
||||||
"skipFeatureDeployment": true,
|
"skipFeatureDeployment": true,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-graph-app-secret-expiration",
|
"name": "react-graph-app-secret-expiration",
|
||||||
"version": "0.0.1",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-graph-app-secret-expiration",
|
"name": "react-graph-app-secret-expiration",
|
||||||
"version": "0.0.1",
|
"version": "1.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp
|
||||||
import { IApplications, IApplication, IFormattedApplication } from '../../../models/IApplication';
|
import { IApplications, IApplication, IFormattedApplication } from '../../../models/IApplication';
|
||||||
import { IGraphAppSecretExpirationState } from './GraphAppSecretExpirationState';
|
import { IGraphAppSecretExpirationState } from './GraphAppSecretExpirationState';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { DefaultButton, Spinner, mergeStyles } from '@fluentui/react';
|
import { DefaultButton, Spinner, mergeStyles, SearchBox } from '@fluentui/react';
|
||||||
|
import { Pagination } from "@pnp/spfx-controls-react/lib/pagination";
|
||||||
|
import * as strings from 'GraphAppSecretExpirationWebPartStrings';
|
||||||
|
|
||||||
const stackItemHidden = mergeStyles({
|
const stackItemHidden = mergeStyles({
|
||||||
display: 'none',
|
display: 'none',
|
||||||
|
@ -57,9 +59,13 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
applications: [],
|
applications: [],
|
||||||
|
filteredApplications: [],
|
||||||
|
filterValue: "",
|
||||||
|
searchFilter: "",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loading: true,
|
loading: true,
|
||||||
groupByFields: []
|
groupByFields: [],
|
||||||
|
page: 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +119,6 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
|
||||||
var today = (moment(Date.now())).format('DD-MMM-YYYY');
|
var today = (moment(Date.now())).format('DD-MMM-YYYY');
|
||||||
try {
|
try {
|
||||||
applications.forEach(app => {
|
applications.forEach(app => {
|
||||||
|
|
||||||
if (app.passwordCredentials.length > 0) {
|
|
||||||
app.passwordCredentials.forEach(pswd => {
|
app.passwordCredentials.forEach(pswd => {
|
||||||
let daysBeforeExpiration = moment.duration((moment(pswd.endDateTime)).diff(today, 'days'), 'days').asDays();
|
let daysBeforeExpiration = moment.duration((moment(pswd.endDateTime)).diff(today, 'days'), 'days').asDays();
|
||||||
let formatedApp: IFormattedApplication = {
|
let formatedApp: IFormattedApplication = {
|
||||||
|
@ -126,8 +130,6 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
|
||||||
};
|
};
|
||||||
displayedApplication.push(formatedApp);
|
displayedApplication.push(formatedApp);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (app.keyCredentials.length > 0) {
|
|
||||||
app.keyCredentials.forEach(keyCred => {
|
app.keyCredentials.forEach(keyCred => {
|
||||||
let daysBeforeExpiration = moment.duration((moment(keyCred.endDateTime)).diff(today, 'days'), 'days').asDays();
|
let daysBeforeExpiration = moment.duration((moment(keyCred.endDateTime)).diff(today, 'days'), 'days').asDays();
|
||||||
let formatedApp: IFormattedApplication = {
|
let formatedApp: IFormattedApplication = {
|
||||||
|
@ -139,16 +141,58 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
|
||||||
};
|
};
|
||||||
displayedApplication.push(formatedApp);
|
displayedApplication.push(formatedApp);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
applications: displayedApplication
|
applications: displayedApplication,
|
||||||
|
filteredApplications: displayedApplication
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getPage(selectedPage: number) {
|
||||||
|
this.setState({
|
||||||
|
page: selectedPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterApplication = (value, clear: boolean) => {
|
||||||
|
let searchResult: IFormattedApplication[] = [];
|
||||||
|
if (clear) {
|
||||||
|
this.state.applications.forEach(app => {
|
||||||
|
if (this._filterByProperties(app, value)) {
|
||||||
|
searchResult.push(app);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
filteredApplications: searchResult,
|
||||||
|
filterValue: value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
filteredApplications: this.state.applications,
|
||||||
|
filterValue: value,
|
||||||
|
page: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterByProperties(application: IFormattedApplication, filterValue) {
|
||||||
|
if (application.applicationId.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
|
||||||
|
return true;
|
||||||
|
} else if (application.displayName.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
|
||||||
|
return true;
|
||||||
|
} else if (application.expirationDate.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
|
||||||
|
return true;
|
||||||
|
} else if (application.type.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _groupView = () => {
|
private _groupView = () => {
|
||||||
if (this.state.groupByFields.length === 0) {
|
if (this.state.groupByFields.length === 0) {
|
||||||
let groupByFields: IGrouping[] = [
|
let groupByFields: IGrouping[] = [
|
||||||
|
@ -177,21 +221,31 @@ export default class GraphAppSecretExpiration extends React.Component<IGraphAppS
|
||||||
<p className={styles.title}>Application list :</p>
|
<p className={styles.title}>Application list :</p>
|
||||||
<div className={this.state.loading ? "" : stackItemHidden}>
|
<div className={this.state.loading ? "" : stackItemHidden}>
|
||||||
<Spinner label="Loading..." ariaLive="assertive" labelPosition="left" />
|
<Spinner label="Loading..." ariaLive="assertive" labelPosition="left" />
|
||||||
<br/>
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div className={this.state.loading ? stackItemHidden : ""}>
|
<div className={this.state.loading ? stackItemHidden : ""}>
|
||||||
<DefaultButton text={this.state.groupByFields.length === 0 ? "Group by App ID" : "Ungroup"} onClick={this._groupView} />
|
<DefaultButton text={this.state.groupByFields.length === 0 ? "Group by App ID" : "Ungroup"} onClick={this._groupView} />
|
||||||
|
<SearchBox placeholder="Search" onChange={(e, text) => this._filterApplication(text, false)} onClear={() => this._filterApplication("", true)} value={this.state.filterValue} />
|
||||||
<ListView
|
<ListView
|
||||||
items={this.state.applications}
|
items={this.state.filteredApplications.slice(this.state.page === 1 || this.state.filterValue !== "" ? 0 : this.state.page * 10 - 10, this.state.page * 10)}
|
||||||
viewFields={_viewFields}
|
viewFields={_viewFields}
|
||||||
iconFieldName="ServerRelativeUrl"
|
iconFieldName="ServerRelativeUrl"
|
||||||
compact={true}
|
compact={true}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
selection={this._getSelection}
|
selection={this._getSelection}
|
||||||
groupByFields={this.state.groupByFields}
|
groupByFields={this.state.groupByFields}
|
||||||
showFilter={true}
|
showFilter={false}
|
||||||
filterPlaceHolder="Search..." />
|
filterPlaceHolder="Search..." />
|
||||||
</div>
|
</div>
|
||||||
|
<Pagination
|
||||||
|
currentPage={1}
|
||||||
|
totalPages={Math.floor(this.state.filteredApplications.length / 10) + 1}
|
||||||
|
onChange={(page) => this._getPage(page)}
|
||||||
|
limiter={3} // Optional - default value 3
|
||||||
|
hideFirstPageJump // Optional
|
||||||
|
hideLastPageJump // Optional
|
||||||
|
limiterIcon={"Emoji12"} // Optional
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,11 @@ import { IApplications, IApplication,IPasswordCredential,IKeyCredential,IFormatt
|
||||||
|
|
||||||
export interface IGraphAppSecretExpirationState {
|
export interface IGraphAppSecretExpirationState {
|
||||||
applications: IFormattedApplication[];
|
applications: IFormattedApplication[];
|
||||||
|
filteredApplications: IFormattedApplication[];
|
||||||
|
filterValue: string;
|
||||||
|
searchFilter: string;
|
||||||
groupByFields: IGrouping[];
|
groupByFields: IGrouping[];
|
||||||
|
page: number;
|
||||||
error: string;
|
error: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
}
|
}
|
Loading…
Reference in New Issue