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??>
var referrer = '${referrer}';
var referrerName = '${referrerName}';
var referrer_uri = '${referrer_uri}';
var referrerUri = '${referrer_uri}';
</#if>
<#if msg??>
var locale = '${locale}';
var l18n_msg = JSON.parse('${msgJSON?no_esc}');
var l18nMsg = JSON.parse('${msgJSON?no_esc}');
<#else>
var locale = 'en';
var l18n_msg = {};
var l18Msg = {};
</#if>
</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
!keycloak.js
!systemjs-angular-loader.js
!systemjs.config.extras.js
!systemjs.config.js
!.eslintrc.js

View file

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

View file

@ -34,7 +34,7 @@ import {ApplicationsPage} from './content/applications-page/ApplicationsPage';
import {MyResourcesPage} from './content/my-resources-page/MyResourcesPage';
import {ExtensionPages} from './content/extensions/ExtensionPages';
declare function toggleReact():void;
declare function toggleReact(): void;
declare function isWelcomePage(): boolean;
declare const locale: string;
@ -44,13 +44,13 @@ export interface AppProps {};
export class App extends React.Component<AppProps> {
private kcSvc: KeycloakService = KeycloakService.Instance;
constructor(props:AppProps) {
public constructor(props: AppProps) {
super(props);
console.log('Called into App constructor');
toggleReact();
}
render() {
public render(): React.ReactNode {
toggleReact();
// check login

View file

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

View file

@ -36,7 +36,7 @@ interface FormFields {
interface AccountPageState {
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 isEditUserNameAllowed: boolean = features.isEditUserNameAllowed;
state: AccountPageState = {
public state: AccountPageState = {
canSubmit: false,
formFields: {username: '',
firstName: '',
@ -54,7 +54,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
email: ''}
};
constructor(props: AccountPageProps) {
public constructor(props: AccountPageProps) {
super(props);
AccountServiceClient.Instance.doGet("/")
.then((response: AxiosResponse<FormFields>) => {
@ -79,7 +79,7 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
AccountServiceClient.Instance.doPost("/", {data: reqData})
.then((response: AxiosResponse<FormFields>) => {
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;
}
render() {
public render(): React.ReactNode {
const fields: FormFields = this.state.formFields;
return (
<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} />
);
RestrictedUsernameInput = () => (
private RestrictedUsernameInput = () => (
<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> {
constructor(props: ApplicationsPageProps) {
public constructor(props: ApplicationsPageProps) {
super(props);
}
render() {
public render(): React.ReactNode {
return (
<div>
<h2>Hello Applications Page</h2>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,12 +16,12 @@
*/
export interface Features {
isRegistrationEmailAsUsername : boolean;
isEditUserNameAllowed : boolean;
isInternationalizationEnabled : boolean;
isLinkedAccountsEnabled : boolean;
isEventsEnabled : boolean;
isMyResourcesEnabled : boolean;
isRegistrationEmailAsUsername: boolean;
isEditUserNameAllowed: boolean;
isInternationalizationEnabled: boolean;
isLinkedAccountsEnabled: boolean;
isEventsEnabled: boolean;
isMyResourcesEnabled: boolean;
}

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> {
constructor(props: LogoutProps) {
public constructor(props: LogoutProps) {
super(props);
}
private handleLogout() {
private handleLogout(): void {
KeycloakService.Instance.logout(baseUrl);
}
render() {
public render(): React.ReactNode {
return (
<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';
declare const l18n_msg: {[key:string]: string};
declare const l18nMsg: {[key: string]: string};
export interface MsgProps {
readonly msgKey:string;
readonly params?:Array<string>;
readonly msgKey: string;
readonly params?: string[];
}
export class Msg extends React.Component<MsgProps> {
constructor(props: MsgProps) {
public constructor(props: MsgProps) {
super(props);
}
render() {
let message:string = l18n_msg[this.props.msgKey];
public render(): React.ReactNode {
let message: string = l18nMsg[this.props.msgKey];
if (message === undefined) message = this.props.msgKey;
if (this.props.params !== undefined) {
@ -46,13 +46,13 @@ export class Msg extends React.Component<MsgProps> {
}
// if the param has Freemarker syntax, try to look up its value
private processParam(param:string): string {
private processParam(param: string): string {
if (!(param.startsWith('${') && param.endsWith('}'))) return param;
// 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;
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",
"description": "keycloak-preview account management written in React",
"scripts": {
"build": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./",
"build:watch": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ -w"
"build": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ && npm run lint",
"build:watch": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./ -w",
"lint": "eslint --ignore-pattern *.d.ts ./**/*.ts*"
},
"keywords": [],
"author": "Stan Silvert",
"license": "Apache 2.0",
"license": "Apache-2.0",
"dependencies": {
"@patternfly/patternfly": "^1.0.196",
"@patternfly/patternfly": "^1.0.227",
"axios": "^0.18.0",
"moment": "^2.22.2",
"react": "^16.5.2",
@ -23,7 +24,16 @@
"@types/react": "^16.4.14",
"@types/react-dom": "^16.0.8",
"@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": {}
}

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"
]
}
}