KEYCLOAK-9656: Add linting to account management

This commit is contained in:
Stan Silvert 2019-03-17 19:21:36 -04:00 committed by Bruno Oliveira da Silva
parent 25c07f78bc
commit 44b0f4efd3
25 changed files with 1823 additions and 253 deletions

View file

@ -31,15 +31,15 @@
<#if referrer??> <#if referrer??>
var referrer = '${referrer}'; var referrer = '${referrer}';
var referrerName = '${referrerName}'; var referrerName = '${referrerName}';
var referrer_uri = '${referrer_uri}'; var referrerUri = '${referrer_uri}';
</#if> </#if>
<#if msg??> <#if msg??>
var locale = '${locale}'; var locale = '${locale}';
var l18n_msg = JSON.parse('${msgJSON?no_esc}'); var l18nMsg = JSON.parse('${msgJSON?no_esc}');
<#else> <#else>
var locale = 'en'; var locale = 'en';
var l18n_msg = {}; var l18Msg = {};
</#if> </#if>
</script> </script>

View file

@ -0,0 +1,28 @@
module.exports = {
parser: '@typescript-eslint/parser',
env: {
browser: true,
es6: true,
},
extends: ['plugin:@typescript-eslint/recommended', 'react-app'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parserOptions: {
ecmaFeatures: {
jsx: true,
impliedStrict: true
},
ecmaVersion: 2019,
sourceType: 'module',
},
plugins: [
'react',
],
rules: {
"no-useless-constructor" : "off",
"@typescript-eslint/indent" : "off",
"@typescript-eslint/no-empty-interface" : "off"
},
};

View file

@ -14,6 +14,6 @@ node
# Don't ignore these # Don't ignore these
!keycloak.js !keycloak.js
!systemjs-angular-loader.js
!systemjs.config.extras.js !systemjs.config.extras.js
!systemjs.config.js !systemjs.config.js
!.eslintrc.js

View file

@ -32,11 +32,11 @@ export interface MainProps {
export class Main extends React.Component<MainProps> { export class Main extends React.Component<MainProps> {
constructor(props: MainProps) { public constructor(props: MainProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<HashRouter> <HashRouter>
<App/> <App/>

View file

@ -44,13 +44,13 @@ export interface AppProps {};
export class App extends React.Component<AppProps> { export class App extends React.Component<AppProps> {
private kcSvc: KeycloakService = KeycloakService.Instance; private kcSvc: KeycloakService = KeycloakService.Instance;
constructor(props:AppProps) { public constructor(props: AppProps) {
super(props); super(props);
console.log('Called into App constructor'); console.log('Called into App constructor');
toggleReact(); toggleReact();
} }
render() { public render(): React.ReactNode {
toggleReact(); toggleReact();
// check login // check login

View file

@ -84,7 +84,7 @@ export class AccountServiceClient {
}); });
} }
private handleError(error: Error) { private handleError(error: Error): void {
console.log(error); console.log(error);
} }

View file

@ -36,7 +36,7 @@ interface FormFields {
interface AccountPageState { interface AccountPageState {
readonly canSubmit: boolean; readonly canSubmit: boolean;
readonly formFields: FormFields readonly formFields: FormFields;
} }
/** /**
@ -46,7 +46,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername; private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername;
private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed; private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed;
state: AccountPageState = { public state: AccountPageState = {
canSubmit: false, canSubmit: false,
formFields: {username: '', formFields: {username: '',
firstName: '', firstName: '',
@ -54,7 +54,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
email: ''} email: ''}
}; };
constructor(props: AccountPageProps) { public constructor(props: AccountPageProps) {
super(props); super(props);
AccountServiceClient.Instance.doGet("/") AccountServiceClient.Instance.doGet("/")
.then((response: AxiosResponse<FormFields>) => { .then((response: AxiosResponse<FormFields>) => {
@ -79,7 +79,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
AccountServiceClient.Instance.doPost("/", {data: reqData}) AccountServiceClient.Instance.doPost("/", {data: reqData})
.then((response: AxiosResponse<FormFields>) => { .then((response: AxiosResponse<FormFields>) => {
this.setState({canSubmit: false}); this.setState({canSubmit: false});
alert('Data posted'); alert('Data posted:' + response.statusText);
}); });
} }
@ -93,7 +93,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
return true; return true;
} }
render() { public render(): React.ReactNode {
const fields: FormFields = this.state.formFields; const fields: FormFields = this.state.formFields;
return ( return (
<span> <span>
@ -156,11 +156,11 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
); );
} }
UsernameInput = () => ( private UsernameInput = () => (
<input type="text" className="form-control" required id="username" name="username" onChange={this.handleChange} value={this.state.formFields.username} /> <input type="text" className="form-control" required id="username" name="username" onChange={this.handleChange} value={this.state.formFields.username} />
); );
RestrictedUsernameInput = () => ( private RestrictedUsernameInput = () => (
<div className="non-edit" id="username">{this.state.formFields.username}</div> <div className="non-edit" id="username">{this.state.formFields.username}</div>
); );

View file

@ -21,11 +21,11 @@ export interface ApplicationsPageProps {
export class ApplicationsPage extends React.Component<ApplicationsPageProps> { export class ApplicationsPage extends React.Component<ApplicationsPageProps> {
constructor(props: ApplicationsPageProps) { public constructor(props: ApplicationsPageProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<div> <div>
<h2>Hello Applications Page</h2> <h2>Hello Applications Page</h2>

View file

@ -21,11 +21,11 @@ export interface AuthenticatorPageProps {
export class AuthenticatorPage extends React.Component<AuthenticatorPageProps> { export class AuthenticatorPage extends React.Component<AuthenticatorPageProps> {
constructor(props: AuthenticatorPageProps) { public constructor(props: AuthenticatorPageProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<div> <div>
<h2>Hello Authenticator Page</h2> <h2>Hello Authenticator Page</h2>

View file

@ -21,11 +21,11 @@ export interface DeviceActivityPageProps {
export class DeviceActivityPage extends React.Component<DeviceActivityPageProps> { export class DeviceActivityPage extends React.Component<DeviceActivityPageProps> {
constructor(props: DeviceActivityPageProps) { public constructor(props: DeviceActivityPageProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<div> <div>
<h2>Hello Device Activity Page</h2> <h2>Hello Device Activity Page</h2>

View file

@ -18,16 +18,16 @@ import * as React from 'react';
import {Route, Link} from 'react-router-dom'; import {Route, Link} from 'react-router-dom';
export interface PageDef { export interface PageDef {
path: string, path: string;
label: string, label: string;
component: React.ComponentType<any>, component: React.ComponentType;
} }
declare const extensionPages: PageDef[]; declare const extensionPages: PageDef[];
export class ExtensionPages { // extends React.Component<ExtensionPagesProps> { export class ExtensionPages { // extends React.Component<ExtensionPagesProps> {
public static get Links(): React.ReactElement<any> { public static get Links(): React.ReactNode {
if (typeof extensionPages === 'undefined') return (<span/>); if (typeof extensionPages === 'undefined') return (<span/>);
const links: React.ReactElement<Link>[] = extensionPages.map((page: PageDef) => const links: React.ReactElement<Link>[] = extensionPages.map((page: PageDef) =>
@ -36,7 +36,7 @@ export class ExtensionPages { // extends React.Component<ExtensionPagesProps> {
return (<span>{links}</span>); return (<span>{links}</span>);
} }
public static get Routes(): React.ReactElement<any> { public static get Routes(): React.ReactNode {
if (typeof extensionPages === 'undefined') return (<span/>); if (typeof extensionPages === 'undefined') return (<span/>);
const routes: React.ReactElement<Route>[] = extensionPages.map((page) => const routes: React.ReactElement<Route>[] = extensionPages.map((page) =>

View file

@ -21,11 +21,11 @@ export interface LinkedAccountsPageProps {
export class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps> { export class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps> {
constructor(props: LinkedAccountsPageProps) { public constructor(props: LinkedAccountsPageProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<div> <div>
<h2>Hello Linked Accounts Page</h2> <h2>Hello Linked Accounts Page</h2>

View file

@ -21,11 +21,11 @@ export interface MyResourcesPageProps {
export class MyResourcesPage extends React.Component<MyResourcesPageProps> { export class MyResourcesPage extends React.Component<MyResourcesPageProps> {
constructor(props: MyResourcesPageProps) { public constructor(props: MyResourcesPageProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
return ( return (
<div> <div>
<h2>Hello My Resources Page</h2> <h2>Hello My Resources Page</h2>

View file

@ -31,14 +31,14 @@ interface FormFields {
} }
interface PasswordPageState { interface PasswordPageState {
readonly canSubmit: boolean, readonly canSubmit: boolean;
readonly registered: boolean; readonly registered: boolean;
readonly lastUpdate: number; readonly lastUpdate: number;
readonly formFields: FormFields; readonly formFields: FormFields;
} }
export class PasswordPage extends React.Component<PasswordPageProps, PasswordPageState> { export class PasswordPage extends React.Component<PasswordPageProps, PasswordPageState> {
state: PasswordPageState = { public state: PasswordPageState = {
canSubmit: false, canSubmit: false,
registered: false, registered: false,
lastUpdate: -1, lastUpdate: -1,
@ -47,7 +47,7 @@ export class PasswordPage extends React.Component<PasswordPageProps, PasswordPag
confirmation: ''} confirmation: ''}
} }
constructor(props: PasswordPageProps) { public constructor(props: PasswordPageProps) {
super(props); super(props);
AccountServiceClient.Instance.doGet("/credentials/password") AccountServiceClient.Instance.doGet("/credentials/password")
@ -75,7 +75,7 @@ export class PasswordPage extends React.Component<PasswordPageProps, PasswordPag
AccountServiceClient.Instance.doPost("/credentials/password", {data: reqData}) AccountServiceClient.Instance.doPost("/credentials/password", {data: reqData})
.then((response: AxiosResponse<FormFields>) => { .then((response: AxiosResponse<FormFields>) => {
this.setState({canSubmit: false}); this.setState({canSubmit: false});
alert('Data posted'); alert('Data posted:' + response.statusText);
}); });
} }
@ -89,7 +89,7 @@ export class PasswordPage extends React.Component<PasswordPageProps, PasswordPag
return true; return true;
} }
render() { public render(): React.ReactNode {
const displayNone = {display: 'none'}; const displayNone = {display: 'none'};
return ( return (

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import {KeycloakLoginOptions} from './keycloak.d'; import {KeycloakLoginOptions, KeycloakError} from './keycloak.d';
// If using a local keycloak.js, uncomment this import. With keycloak.js fetched // If using a local keycloak.js, uncomment this import. With keycloak.js fetched
// from the server, you get a compile-time warning on use of the Keycloak() // from the server, you get a compile-time warning on use of the Keycloak()
@ -49,7 +49,7 @@ export class KeycloakService {
* for details. * for details.
* @returns {Promise<T>} * @returns {Promise<T>}
*/ */
static init(configOptions?: string|{}, initOptions: InitOptions = {}): Promise<any> { public static init(configOptions?: string|{}, initOptions: InitOptions = {}): Promise<void> {
KeycloakService.keycloakAuth = Keycloak(configOptions); KeycloakService.keycloakAuth = Keycloak(configOptions);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -57,43 +57,43 @@ export class KeycloakService {
.success(() => { .success(() => {
resolve(); resolve();
}) })
.error((errorData: any) => { .error((errorData: KeycloakError) => {
reject(errorData); reject(errorData);
}); });
}); });
} }
authenticated(): boolean { public authenticated(): boolean {
return KeycloakService.keycloakAuth.authenticated ? KeycloakService.keycloakAuth.authenticated : false; return KeycloakService.keycloakAuth.authenticated ? KeycloakService.keycloakAuth.authenticated : false;
} }
login(options?: KeycloakLoginOptions) { public login(options?: KeycloakLoginOptions): void {
KeycloakService.keycloakAuth.login(options); KeycloakService.keycloakAuth.login(options);
} }
logout(redirectUri?: string) { public logout(redirectUri?: string): void {
KeycloakService.keycloakAuth.logout({redirectUri: redirectUri}); KeycloakService.keycloakAuth.logout({redirectUri: redirectUri});
} }
account() { public account(): void {
KeycloakService.keycloakAuth.accountManagement(); KeycloakService.keycloakAuth.accountManagement();
} }
authServerUrl(): string | undefined { public authServerUrl(): string | undefined {
return KeycloakService.keycloakAuth.authServerUrl; return KeycloakService.keycloakAuth.authServerUrl;
} }
realm(): string | undefined { public realm(): string | undefined {
return KeycloakService.keycloakAuth.realm; return KeycloakService.keycloakAuth.realm;
} }
getToken(): Promise<string> { public getToken(): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
if (KeycloakService.keycloakAuth.token) { if (KeycloakService.keycloakAuth.token) {
KeycloakService.keycloakAuth KeycloakService.keycloakAuth
.updateToken(5) .updateToken(5)
.success(() => { .success(() => {
resolve(<string>KeycloakService.keycloakAuth.token); resolve(KeycloakService.keycloakAuth.token as string);
}) })
.error(() => { .error(() => {
reject('Failed to refresh token'); reject('Failed to refresh token');

View file

@ -19,7 +19,7 @@ import * as React from 'react';
import {Msg} from '../widgets/Msg'; import {Msg} from '../widgets/Msg';
declare const referrerName: string; declare const referrerName: string;
declare const referrer_uri: string; declare const referrerUri: string;
export interface ReferrerProps { export interface ReferrerProps {
} }
@ -29,15 +29,15 @@ export interface ReferrerProps {
*/ */
export class Referrer extends React.Component<ReferrerProps> { export class Referrer extends React.Component<ReferrerProps> {
constructor(props: ReferrerProps) { public constructor(props: ReferrerProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
if (typeof referrerName === "undefined") return null; if (typeof referrerName === "undefined") return null;
return ( return (
<a className="nav-item-iconic" href={referrer_uri}> <a className="nav-item-iconic" href={referrerUri}>
<span className="pficon-arrow"></span> <span className="pficon-arrow"></span>
<Msg msgKey="backTo" params={[referrerName]}/> <Msg msgKey="backTo" params={[referrerName]}/>
</a> </a>

View file

@ -1,28 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type Vendor = "fa" | "pficon";
export class Icon {
constructor(public vendor: Vendor, public name: string) {
}
getClasses(): string {
return `${this.vendor} ${this.vendor}-${this.name}`;
}
}

View file

@ -27,15 +27,15 @@ export interface LogoutProps {
export class Logout extends React.Component<LogoutProps> { export class Logout extends React.Component<LogoutProps> {
constructor(props: LogoutProps) { public constructor(props: LogoutProps) {
super(props); super(props);
} }
private handleLogout() { private handleLogout(): void {
KeycloakService.Instance.logout(baseUrl); KeycloakService.Instance.logout(baseUrl);
} }
render() { public render(): React.ReactNode {
return ( return (
<Link to="/" className="pf-c-button pf-m-primary" type="button" onClick={this.handleLogout}><Msg msgKey="doSignOut"/></Link> <Link to="/" className="pf-c-button pf-m-primary" type="button" onClick={this.handleLogout}><Msg msgKey="doSignOut"/></Link>
); );

View file

@ -16,21 +16,21 @@
import * as React from 'react'; import * as React from 'react';
declare const l18n_msg: {[key:string]: string}; declare const l18nMsg: {[key: string]: string};
export interface MsgProps { export interface MsgProps {
readonly msgKey: string; readonly msgKey: string;
readonly params?:Array<string>; readonly params?: string[];
} }
export class Msg extends React.Component<MsgProps> { export class Msg extends React.Component<MsgProps> {
constructor(props: MsgProps) { public constructor(props: MsgProps) {
super(props); super(props);
} }
render() { public render(): React.ReactNode {
let message:string = l18n_msg[this.props.msgKey]; let message: string = l18nMsg[this.props.msgKey];
if (message === undefined) message = this.props.msgKey; if (message === undefined) message = this.props.msgKey;
if (this.props.params !== undefined) { if (this.props.params !== undefined) {
@ -52,7 +52,7 @@ export class Msg extends React.Component<MsgProps> {
// remove Freemarker syntax // remove Freemarker syntax
const key: string = param.substring(2, param.length - 1); const key: string = param.substring(2, param.length - 1);
let value:string = l18n_msg[key]; let value: string = l18nMsg[key];
if (value === undefined) return param; if (value === undefined) return param;
return value; return value;

View file

@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\node_modules\eslint\bin\eslint.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\node_modules\eslint\bin\eslint.js" %*
)

View file

@ -3,14 +3,15 @@
"version": "1.0.0", "version": "1.0.0",
"description": "keycloak-preview account management written in React", "description": "keycloak-preview account management written in React",
"scripts": { "scripts": {
"build": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./", "build": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ && npm run lint",
"build:watch": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ -w" "build:watch": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ -w",
"lint": "eslint --ignore-pattern *.d.ts ./**/*.ts*"
}, },
"keywords": [], "keywords": [],
"author": "Stan Silvert", "author": "Stan Silvert",
"license": "Apache 2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@patternfly/patternfly": "^1.0.196", "@patternfly/patternfly": "^1.0.227",
"axios": "^0.18.0", "axios": "^0.18.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"react": "^16.5.2", "react": "^16.5.2",
@ -23,7 +24,16 @@
"@types/react": "^16.4.14", "@types/react": "^16.4.14",
"@types/react-dom": "^16.0.8", "@types/react-dom": "^16.0.8",
"@types/react-router-dom": "^4.3.1", "@types/react-router-dom": "^4.3.1",
"typescript": "^3.1.1" "@typescript-eslint/eslint-plugin": "^1.4.2",
"@typescript-eslint/parser": "^1.4.2",
"babel-eslint": "^9.0.0",
"eslint": "^5.15.1",
"eslint-config-react-app": "^3.0.8",
"eslint-plugin-flowtype": "^2.50.3",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.4",
"typescript": "^3.3.3333"
}, },
"repository": {} "repository": {}
} }

View file

@ -1,50 +0,0 @@
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
module.exports.translate = function(load){
var url = document.createElement('a');
url.href = load.address;
var basePathParts = url.pathname.split('/');
basePathParts.pop();
var basePath = basePathParts.join('/');
// basePath leaves out leading slash on IE 11
if (!basePath.startsWith('/')) {
basePath = '/' + basePath;
}
var baseHref = document.createElement('a');
baseHref.href = this.baseURL;
baseHref = baseHref.pathname;
basePath = basePath.replace(baseHref, '');
load.source = load.source
.replace(templateUrlRegex, function(match, quote, url){
var resolvedUrl = url;
if (url.startsWith('.')) {
resolvedUrl = basePath + url.substr(1);
}
return 'templateUrl: "' + resolvedUrl + '"';
})
.replace(stylesRegex, function(match, relativeUrls) {
var urls = [];
while ((match = stringRegex.exec(relativeUrls)) !== null) {
if (match[2].startsWith('.')) {
urls.push('"' + basePath + match[2].substr(1) + '"');
} else {
urls.push('"' + match[2] + '"');
}
}
return "styleUrls: [" + urls.join(', ') + "]";
});
return load;
};

View file

@ -1,93 +0,0 @@
{
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-unreachable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}