Merge pull request #2038 from a1mery/react-graph-app-secret-expiration
New sample: react-graph-app-secret-expiration: Applications secrets expiration
This commit is contained in:
commit
1090175736
|
@ -0,0 +1,33 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
release
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": true,
|
||||
"environment": "spo",
|
||||
"version": "1.12.1",
|
||||
"libraryName": "react-graph-app-secret-expiration",
|
||||
"libraryId": "b25d85a4-7310-408a-a263-25959b0a5b1b",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
# Applications Secrets Expiration
|
||||
|
||||
## Summary
|
||||
|
||||
This sample web part shows the list of your applications registered in Azure AD along with their associated client secret/certificate expiration date.
|
||||
I got the idea from this great article [Use Power Automate to Notify of Upcoming Azure AD App Client Secrets and Certificate Expirations](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/use-power-automate-to-notify-of-upcoming-azure-ad-app-client/ba-p/2406145) and thought it would be nice to have a SPFx web part version of it.
|
||||
|
||||
![Animated sample](./assets/react-graph-app-secret-expiration-animated.gif)
|
||||
|
||||
## Compatibility
|
||||
|
||||
![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)
|
||||
![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")
|
||||
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
|
||||
|
||||
|
||||
## Applies to
|
||||
|
||||
- [SharePoint Framework](https://aka.ms/spfx)
|
||||
- [Microsoft 365 tenant](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-graph-app-secret-expiration | [Aimery Thomas](https://github.com/a1mery) ([@aimery_thomas](https://twitter.com/aimery_thomas))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 17, 2021|Initial release
|
||||
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- Ensure that you are at the solution folder
|
||||
- In the command-line run:
|
||||
- **npm install**
|
||||
- **gulp bundle**
|
||||
- **gulp package-solution**
|
||||
- Deploy the package to your app catalog
|
||||
- Approve the API permission request from the SharePoint admin
|
||||
- Add the web part to a page
|
||||
- In the command-line run:
|
||||
- **gulp serve --nobrowser**
|
||||
|
||||
## Features
|
||||
|
||||
This sample illustrates the following concepts:
|
||||
|
||||
- Consume the Microsoft Graph API from SPFx web part
|
||||
- Use [ListView control](https://pnp.github.io/sp-dev-fx-controls-react/controls/ListView/)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
## Help
|
||||
|
||||
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
|
||||
|
||||
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-graph-app-secret-expiration&authors=@a1mery&title=react-graph-app-secret-expiration%20-%20).
|
||||
|
||||
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-graph-app-secret-expiration&authors=@a1mery&title=react-graph-app-secret-expiration%20-%20).
|
||||
|
||||
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-graph-app-secret-expiration&authors=@a1mery&title=react-graph-app-secret-expiration%20-%20).
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-graph-app-secret-expiration" />
|
Binary file not shown.
After Width: | Height: | Size: 64 MiB |
|
@ -0,0 +1,51 @@
|
|||
[
|
||||
{
|
||||
"name": "pnp-sp-dev-spfx-web-parts-react-graph-app-secret-expiration",
|
||||
"source": "pnp",
|
||||
"title": "Applications Secrets Expiration",
|
||||
"shortDescription": "This sample web part shows the list of your applications registered in Azure AD along with their associated client secret/certificate expiration date.",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-graph-app-secret-expiration",
|
||||
"longDescription": [
|
||||
"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",
|
||||
"updateDateTime": "2021-09-17",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
],
|
||||
"metadata": [
|
||||
{
|
||||
"key": "CLIENT-SIDE-DEV",
|
||||
"value": "React"
|
||||
},
|
||||
{
|
||||
"key": "SPFX-VERSION",
|
||||
"value": "1.12.1"
|
||||
}
|
||||
],
|
||||
"thumbnails": [
|
||||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-graph-app-secret-expiration/assets/react-graph-app-secret-expiration-animated.gif",
|
||||
"alt": "Preview of web part"
|
||||
}
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"gitHubAccount": "a1mery ",
|
||||
"pictureUrl": "https://github.com/a1mery.png",
|
||||
"name": "Aimery Thomas",
|
||||
"twitter": "aimery_thomas"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"name": "Build your first SharePoint client-side web part",
|
||||
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
|
||||
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"graph-app-secret-expiration-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/graphAppSecretExpiration/GraphAppSecretExpirationWebPart.js",
|
||||
"manifest": "./src/webparts/graphAppSecretExpiration/GraphAppSecretExpirationWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"GraphAppSecretExpirationWebPartStrings": "lib/webparts/graphAppSecretExpiration/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "./release/assets/"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./release/assets/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-graph-app-secret-expiration",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-graph-app-secret-expiration-client-side-solution",
|
||||
"id": "b25d85a4-7310-408a-a263-25959b0a5b1b",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false,
|
||||
"skipFeatureDeployment": true,
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Directory.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "User.Read"
|
||||
}],
|
||||
"developer": {
|
||||
"name": "",
|
||||
"websiteUrl": "",
|
||||
"privacyUrl": "",
|
||||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-graph-app-secret-expiration.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
var getTasks = build.rig.getTasks;
|
||||
build.rig.getTasks = function () {
|
||||
var result = getTasks.call(build.rig);
|
||||
|
||||
result.set('serve', result.get('serve-deprecated'));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
build.initialize(require('gulp'));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "react-graph-app-secret-expiration",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react": "^8.34.2",
|
||||
"@microsoft/rush-stack-compiler-3.8": "^0.4.47",
|
||||
"@microsoft/sp-core-library": "1.12.1",
|
||||
"@microsoft/sp-lodash-subset": "1.12.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
|
||||
"@microsoft/sp-property-pane": "1.12.1",
|
||||
"@microsoft/sp-webpart-base": "1.12.1",
|
||||
"@pnp/spfx-controls-react": "3.3.0",
|
||||
"moment": "^2.29.1",
|
||||
"office-ui-fabric-react": "7.156.0",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "16.9.36",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@microsoft/sp-build-web": "1.12.1",
|
||||
"@microsoft/sp-tslint-rules": "1.12.1",
|
||||
"@microsoft/sp-module-interfaces": "1.12.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.12.1",
|
||||
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
|
||||
"gulp": "~4.0.2",
|
||||
"ajv": "~5.2.2",
|
||||
"@types/webpack-env": "1.13.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,40 @@
|
|||
export interface IApplications {
|
||||
value: IApplication[];
|
||||
}
|
||||
|
||||
export interface IApplication {
|
||||
appId: string;
|
||||
displayName: string;
|
||||
passwordCredentials: IPasswordCredential[];
|
||||
keyCredentials: IKeyCredential[];
|
||||
}
|
||||
|
||||
export interface IPasswordCredential {
|
||||
customKeyIdentifier: string;
|
||||
displayName: string;
|
||||
endDateTime: Date;
|
||||
hint: string;
|
||||
keyId: string;
|
||||
secretText: string;
|
||||
startDateTime: string;
|
||||
}
|
||||
|
||||
export interface IKeyCredential {
|
||||
customKeyIdentifier: string;
|
||||
displayName: string;
|
||||
endDateTime: Date;
|
||||
key: string;
|
||||
keyId: string;
|
||||
startDateTime: string;
|
||||
type: string;
|
||||
usage: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IFormattedApplication {
|
||||
applicationId: string;
|
||||
displayName: string;
|
||||
daysLeft: number;
|
||||
type: string;
|
||||
expirationDate: string;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "cf140678-2185-408e-9e98-6cf7a45f0649",
|
||||
"alias": "GraphAppSecretExpirationWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "graphAppSecretExpiration" },
|
||||
"description": { "default": "Lists all Azure AD applications secrets and their expiration date." },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "graphAppSecretExpiration"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'GraphAppSecretExpirationWebPartStrings';
|
||||
import GraphAppSecretExpiration from './components/GraphAppSecretExpiration';
|
||||
import { IGraphAppSecretExpirationProps } from './components/IGraphAppSecretExpirationProps';
|
||||
import { MSGraphClient } from '@microsoft/sp-http';
|
||||
|
||||
export interface IGraphAppSecretExpirationWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class GraphAppSecretExpirationWebPart extends BaseClientSideWebPart<IGraphAppSecretExpirationWebPartProps> {
|
||||
private graphClient: MSGraphClient;
|
||||
|
||||
public onInit(): Promise<void> {
|
||||
return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
|
||||
this.context.msGraphClientFactory
|
||||
.getClient()
|
||||
.then((client: MSGraphClient): void => {
|
||||
this.graphClient = client;
|
||||
resolve();
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IGraphAppSecretExpirationProps> = React.createElement(
|
||||
GraphAppSecretExpiration,
|
||||
{
|
||||
description: this.properties.description,
|
||||
graphClient: this.graphClient
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.graphAppSecretExpiration {
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
color: $ms-color-themeDark;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
import * as React from 'react';
|
||||
import styles from './GraphAppSecretExpiration.module.scss';
|
||||
import { IGraphAppSecretExpirationProps } from './IGraphAppSecretExpirationProps';
|
||||
import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp/spfx-controls-react/lib/ListView";
|
||||
import { IApplications, IApplication, IFormattedApplication } from '../../../models/IApplication';
|
||||
import { IGraphAppSecretExpirationState } from './GraphAppSecretExpirationState';
|
||||
import * as moment from 'moment';
|
||||
import { DefaultButton, Spinner, mergeStyles } from '@fluentui/react';
|
||||
|
||||
const stackItemHidden = mergeStyles({
|
||||
display: 'none',
|
||||
});
|
||||
const _viewFields: IViewField[] = [
|
||||
{
|
||||
name: "applicationId",
|
||||
displayName: "Application ID",
|
||||
minWidth: 250
|
||||
},
|
||||
{
|
||||
name: "displayName",
|
||||
displayName: "Display Name",
|
||||
minWidth: 150,
|
||||
maxWidth: 300
|
||||
},
|
||||
{
|
||||
name: "daysLeft",
|
||||
displayName: "Days left",
|
||||
minWidth: 80,
|
||||
sorting: true,
|
||||
render: (app: any) => {
|
||||
let fontColor;
|
||||
if (app.daysLeft == 0) {
|
||||
fontColor = "Red";
|
||||
} else if (app.daysLeft < 30) {
|
||||
fontColor = "Orange";
|
||||
}
|
||||
const element: any = React.createElement("span", { style: { color: fontColor } }, app.daysLeft);
|
||||
return element;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
displayName: "Type",
|
||||
minWidth: 60,
|
||||
},
|
||||
{
|
||||
name: "expirationDate",
|
||||
displayName: "Expiration Date",
|
||||
minWidth: 150,
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export default class GraphAppSecretExpiration extends React.Component<IGraphAppSecretExpirationProps, IGraphAppSecretExpirationState> {
|
||||
|
||||
constructor(props: IGraphAppSecretExpirationProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
applications: [],
|
||||
error: undefined,
|
||||
loading: true,
|
||||
groupByFields: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount(): void {
|
||||
this._getApplications();
|
||||
}
|
||||
|
||||
private _getApplications(): IApplication[] {
|
||||
let retrievedApplications: IApplication[] = [];
|
||||
if (!this.props.graphClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.graphClient
|
||||
.api("applications")
|
||||
.version("v1.0")
|
||||
.select("appId,displayName,passwordCredentials,keyCredentials")
|
||||
.get((err: any, res: IApplications): void => {
|
||||
if (err) {
|
||||
this.setState({
|
||||
error: err.message ? err.message : "An error occured",
|
||||
loading: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
// applications retrived successfully
|
||||
if (res && res.value && res.value.length > 0) {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
retrievedApplications = res.value;
|
||||
this._propertiesMapping(retrievedApplications);
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private _getSelection(items: any[]) {
|
||||
console.log('Selected items:', items);
|
||||
}
|
||||
|
||||
|
||||
private _propertiesMapping = (applications: IApplication[]) => {
|
||||
let displayedApplication: IFormattedApplication[] = [];
|
||||
var today = (moment(Date.now())).format('DD-MMM-YYYY');
|
||||
try {
|
||||
applications.forEach(app => {
|
||||
|
||||
if (app.passwordCredentials.length > 0) {
|
||||
app.passwordCredentials.forEach(pswd => {
|
||||
let daysBeforeExpiration = moment.duration((moment(pswd.endDateTime)).diff(today, 'days'), 'days').asDays();
|
||||
let formatedApp: IFormattedApplication = {
|
||||
applicationId: app.appId,
|
||||
displayName: app.displayName,
|
||||
type: "Secret",
|
||||
expirationDate: (moment(pswd.endDateTime)).format('DD-MMM-YYYY'),
|
||||
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
|
||||
};
|
||||
displayedApplication.push(formatedApp);
|
||||
});
|
||||
}
|
||||
if (app.keyCredentials.length > 0) {
|
||||
app.keyCredentials.forEach(keyCred => {
|
||||
let daysBeforeExpiration = moment.duration((moment(keyCred.endDateTime)).diff(today, 'days'), 'days').asDays();
|
||||
let formatedApp: IFormattedApplication = {
|
||||
applicationId: app.appId,
|
||||
displayName: app.displayName,
|
||||
type: "Certificate",
|
||||
expirationDate: (moment(keyCred.endDateTime)).format('DD-MMM-YYYY'),
|
||||
daysLeft: daysBeforeExpiration > 0 ? daysBeforeExpiration : 0
|
||||
};
|
||||
displayedApplication.push(formatedApp);
|
||||
});
|
||||
}
|
||||
});
|
||||
this.setState({
|
||||
applications: displayedApplication
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _groupView = () => {
|
||||
if (this.state.groupByFields.length === 0) {
|
||||
let groupByFields: IGrouping[] = [
|
||||
{
|
||||
name: "applicationId",
|
||||
order: GroupOrder.ascending
|
||||
}
|
||||
];
|
||||
this.setState({
|
||||
groupByFields: groupByFields
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
groupByFields: []
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IGraphAppSecretExpirationProps> {
|
||||
return (
|
||||
<div className={styles.graphAppSecretExpiration}>
|
||||
<div className={styles.container}>
|
||||
<br />
|
||||
<p className={styles.title}>Application list :</p>
|
||||
<div className={this.state.loading ? "" : stackItemHidden}>
|
||||
<Spinner label="Loading..." ariaLive="assertive" labelPosition="left" />
|
||||
<br/>
|
||||
</div>
|
||||
<div className={this.state.loading ? stackItemHidden : ""}>
|
||||
<DefaultButton text={this.state.groupByFields.length === 0 ? "Group by App ID" : "Ungroup"} onClick={this._groupView} />
|
||||
<ListView
|
||||
items={this.state.applications}
|
||||
viewFields={_viewFields}
|
||||
iconFieldName="ServerRelativeUrl"
|
||||
compact={true}
|
||||
selectionMode={SelectionMode.none}
|
||||
selection={this._getSelection}
|
||||
groupByFields={this.state.groupByFields}
|
||||
showFilter={true}
|
||||
filterPlaceHolder="Search..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { IGrouping } from '@pnp/spfx-controls-react/lib/ListView';
|
||||
import { IApplications, IApplication,IPasswordCredential,IKeyCredential,IFormattedApplication } from '../../../models/IApplication';
|
||||
|
||||
export interface IGraphAppSecretExpirationState {
|
||||
applications: IFormattedApplication[];
|
||||
groupByFields: IGrouping[];
|
||||
error: string;
|
||||
loading: boolean;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { MSGraphClient } from "@microsoft/sp-http";
|
||||
|
||||
export interface IGraphAppSecretExpirationProps {
|
||||
description: string;
|
||||
graphClient: MSGraphClient;
|
||||
}
|
7
samples/react-graph-app-secret-expiration/src/webparts/graphAppSecretExpiration/loc/en-us.js
vendored
Normal file
7
samples/react-graph-app-secret-expiration/src/webparts/graphAppSecretExpiration/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IGraphAppSecretExpirationWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'GraphAppSecretExpirationWebPartStrings' {
|
||||
const strings: IGraphAppSecretExpirationWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.8/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.promise"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue