KEYCLOAK-6503: Linked Accounts Page
This commit is contained in:
parent
2d3f771b70
commit
d439f4181a
6 changed files with 296 additions and 25 deletions
|
@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
*/
|
*/
|
||||||
public class LinkedAccountRepresentation implements Comparable<LinkedAccountRepresentation> {
|
public class LinkedAccountRepresentation implements Comparable<LinkedAccountRepresentation> {
|
||||||
private boolean connected;
|
private boolean connected;
|
||||||
|
private boolean isSocial;
|
||||||
private String providerAlias;
|
private String providerAlias;
|
||||||
private String providerName;
|
private String providerName;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
@ -47,6 +48,14 @@ public class LinkedAccountRepresentation implements Comparable<LinkedAccountRepr
|
||||||
public void setConnected(boolean connected) {
|
public void setConnected(boolean connected) {
|
||||||
this.connected = connected;
|
this.connected = connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSocial() {
|
||||||
|
return this.isSocial;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSocial(boolean isSocial) {
|
||||||
|
this.isSocial = isSocial;
|
||||||
|
}
|
||||||
|
|
||||||
public String getProviderAlias() {
|
public String getProviderAlias() {
|
||||||
return providerAlias;
|
return providerAlias;
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.services.resources.account;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
|
@ -35,6 +36,7 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -48,6 +50,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.account.AccountLinkUriRepresentation;
|
import org.keycloak.representations.account.AccountLinkUriRepresentation;
|
||||||
import org.keycloak.representations.account.LinkedAccountRepresentation;
|
import org.keycloak.representations.account.LinkedAccountRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
@ -96,6 +99,16 @@ public class LinkedAccountsResource {
|
||||||
SortedSet<LinkedAccountRepresentation> linkedAccounts = getLinkedAccounts(this.session, this.realm, this.user);
|
SortedSet<LinkedAccountRepresentation> linkedAccounts = getLinkedAccounts(this.session, this.realm, this.user);
|
||||||
return Cors.add(request, Response.ok(linkedAccounts)).auth().allowedOrigins(auth.getToken()).build();
|
return Cors.add(request, Response.ok(linkedAccounts)).auth().allowedOrigins(auth.getToken()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<String> findSocialIds() {
|
||||||
|
Set<String> socialIds = new HashSet();
|
||||||
|
List<ProviderFactory> providerFactories = session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class);
|
||||||
|
for (ProviderFactory factory: providerFactories) {
|
||||||
|
socialIds.add(factory.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return socialIds;
|
||||||
|
}
|
||||||
|
|
||||||
public SortedSet<LinkedAccountRepresentation> getLinkedAccounts(KeycloakSession session, RealmModel realm, UserModel user) {
|
public SortedSet<LinkedAccountRepresentation> getLinkedAccounts(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||||
|
@ -103,6 +116,7 @@ public class LinkedAccountsResource {
|
||||||
|
|
||||||
if (identityProviders == null || identityProviders.isEmpty()) return linkedAccounts;
|
if (identityProviders == null || identityProviders.isEmpty()) return linkedAccounts;
|
||||||
|
|
||||||
|
Set<String> socialIds = findSocialIds();
|
||||||
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
|
Set<FederatedIdentityModel> identities = session.users().getFederatedIdentities(user, realm);
|
||||||
for (IdentityProviderModel provider : identityProviders) {
|
for (IdentityProviderModel provider : identityProviders) {
|
||||||
if (!provider.isEnabled()) {
|
if (!provider.isEnabled()) {
|
||||||
|
@ -117,6 +131,7 @@ public class LinkedAccountsResource {
|
||||||
|
|
||||||
LinkedAccountRepresentation rep = new LinkedAccountRepresentation();
|
LinkedAccountRepresentation rep = new LinkedAccountRepresentation();
|
||||||
rep.setConnected(identity != null);
|
rep.setConnected(identity != null);
|
||||||
|
rep.setSocial(socialIds.contains(provider.getProviderId()));
|
||||||
rep.setProviderAlias(providerId);
|
rep.setProviderAlias(providerId);
|
||||||
rep.setDisplayName(displayName);
|
rep.setDisplayName(displayName);
|
||||||
rep.setGuiOrder(guiOrder);
|
rep.setGuiOrder(guiOrder);
|
||||||
|
|
|
@ -39,4 +39,17 @@ add=Add
|
||||||
shareSuccess=Resource successfully shared.
|
shareSuccess=Resource successfully shared.
|
||||||
resourceAlreadyShared=Resource is already shared with this user.
|
resourceAlreadyShared=Resource is already shared with this user.
|
||||||
resourceNotShared=This resource is not shared.
|
resourceNotShared=This resource is not shared.
|
||||||
permissionRequests=Permission Requests
|
permissionRequests=Permission Requests
|
||||||
|
|
||||||
|
# Linked Accounts Page
|
||||||
|
linkedAccountsTitle=Linked Accounts
|
||||||
|
linkedAccountsIntroMessage=Manage logins through third-party accounts.
|
||||||
|
linkedLoginProviders=Linked Login Providers
|
||||||
|
unlinkedLoginProviders=Unlinked Login Providers
|
||||||
|
linkedEmpty=No Linked Providers
|
||||||
|
unlinkedEmpty=No Unlinked Providers
|
||||||
|
socialLogin=Social Login
|
||||||
|
systemDefined=System Defined
|
||||||
|
link=Link Account
|
||||||
|
unLink=Unlink Account
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
|
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,21 +15,213 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||||
export interface LinkedAccountsPageProps {
|
import {AxiosResponse} from 'axios';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
DataList,
|
||||||
|
DataListAction,
|
||||||
|
DataListItemCells,
|
||||||
|
DataListCell,
|
||||||
|
DataListItemRow,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
|
Title,
|
||||||
|
TitleLevel,
|
||||||
|
DataListItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BitbucketIcon,
|
||||||
|
CubeIcon,
|
||||||
|
FacebookIcon,
|
||||||
|
GithubIcon,
|
||||||
|
GitlabIcon,
|
||||||
|
GoogleIcon,
|
||||||
|
InstagramIcon,
|
||||||
|
LinkIcon,
|
||||||
|
LinkedinIcon,
|
||||||
|
MicrosoftIcon,
|
||||||
|
OpenshiftIcon,
|
||||||
|
PaypalIcon,
|
||||||
|
StackOverflowIcon,
|
||||||
|
TwitterIcon,
|
||||||
|
UnlinkIcon
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
import {AccountServiceClient} from '../../account-service/account.service';
|
||||||
|
import {Msg} from '../../widgets/Msg';
|
||||||
|
import {ContentPage} from '../ContentPage';
|
||||||
|
import {createRedirect} from '../../util/RedirectUri';
|
||||||
|
|
||||||
|
interface LinkedAccount {
|
||||||
|
connected: boolean;
|
||||||
|
social: boolean;
|
||||||
|
providerAlias: string;
|
||||||
|
providerName: string;
|
||||||
|
displayName: string;
|
||||||
|
linkedUsername: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps> {
|
interface LinkedAccountsPageProps extends RouteComponentProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LinkedAccountsPageState {
|
||||||
|
linkedAccounts: LinkedAccount[];
|
||||||
|
unLinkedAccounts: LinkedAccount[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Stan Silvert
|
||||||
|
*/
|
||||||
|
class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, LinkedAccountsPageState> {
|
||||||
|
|
||||||
public constructor(props: LinkedAccountsPageProps) {
|
public constructor(props: LinkedAccountsPageProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
linkedAccounts: [],
|
||||||
|
unLinkedAccounts: []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getLinkedAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLinkedAccounts(): void {
|
||||||
|
AccountServiceClient.Instance.doGet("/linked-accounts")
|
||||||
|
.then((response: AxiosResponse<LinkedAccount[]>) => {
|
||||||
|
console.log({response});
|
||||||
|
const linkedAccounts = response.data.filter((account) => account.connected);
|
||||||
|
const unLinkedAccounts = response.data.filter((account) => !account.connected);
|
||||||
|
this.setState({linkedAccounts: linkedAccounts, unLinkedAccounts: unLinkedAccounts});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private unLinkAccount(account: LinkedAccount): void {
|
||||||
|
const url = '/linked-accounts/' + account.providerName;
|
||||||
|
|
||||||
|
AccountServiceClient.Instance.doDelete(url)
|
||||||
|
.then((response: AxiosResponse) => {
|
||||||
|
console.log({response});
|
||||||
|
this.getLinkedAccounts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private linkAccount(account: LinkedAccount): void {
|
||||||
|
const url = '/linked-accounts/' + account.providerName;
|
||||||
|
|
||||||
|
const redirectUri: string = createRedirect(this.props.location.pathname);
|
||||||
|
|
||||||
|
AccountServiceClient.Instance.doGet(url, { params: {providerId: account.providerName, redirectUri}})
|
||||||
|
.then((response: AxiosResponse<{accountLinkUri: string}>) => {
|
||||||
|
console.log({response});
|
||||||
|
window.location.href = response.data.accountLinkUri;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<ContentPage title={Msg.localize('linkedAccountsTitle')} introMessage={Msg.localize('linkedAccountsIntroMessage')}>
|
||||||
<h2>Hello Linked Accounts Page</h2>
|
<Stack gutter='md'>
|
||||||
</div>
|
<StackItem isFilled>
|
||||||
|
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
||||||
|
<Msg msgKey='linkedLoginProviders'/>
|
||||||
|
</Title>
|
||||||
|
<DataList aria-label='foo'>
|
||||||
|
{this.makeRows(this.state.linkedAccounts, true)}
|
||||||
|
</DataList>
|
||||||
|
</StackItem>
|
||||||
|
<StackItem isFilled/>
|
||||||
|
<StackItem isFilled>
|
||||||
|
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
||||||
|
<Msg msgKey='unlinkedLoginProviders'/>
|
||||||
|
</Title>
|
||||||
|
<DataList aria-label='foo'>
|
||||||
|
{this.makeRows(this.state.unLinkedAccounts, false)}
|
||||||
|
</DataList>
|
||||||
|
</StackItem>
|
||||||
|
</Stack>
|
||||||
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
private emptyRow(isLinked: boolean): React.ReactNode {
|
||||||
|
let isEmptyMessage = '';
|
||||||
|
if (isLinked) {
|
||||||
|
isEmptyMessage = Msg.localize('linkedEmpty');
|
||||||
|
} else {
|
||||||
|
isEmptyMessage = Msg.localize('unlinkedEmpty');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataListItem key='emptyItem' aria-labelledby="empty-item">
|
||||||
|
<DataListItemRow key='emptyRow'>
|
||||||
|
<DataListItemCells dataListCells={[
|
||||||
|
<DataListCell key='empty'><strong>{isEmptyMessage}</strong></DataListCell>
|
||||||
|
]}/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeRows(accounts: LinkedAccount[], isLinked: boolean): React.ReactNode {
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
return this.emptyRow(isLinked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<> {
|
||||||
|
|
||||||
|
accounts.map( (account: LinkedAccount) => (
|
||||||
|
<DataListItem key={account.providerName} aria-labelledby="simple-item1">
|
||||||
|
<DataListItemRow key={account.providerName}>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell key='idp'><Stack><StackItem isFilled>{this.findIcon(account)}</StackItem><StackItem isFilled><h2><strong>{account.displayName}</strong></h2></StackItem></Stack></DataListCell>,
|
||||||
|
<DataListCell key='badge'><Stack><StackItem isFilled/><StackItem isFilled>{this.badge(account)}</StackItem></Stack></DataListCell>,
|
||||||
|
<DataListCell key='username'><Stack><StackItem isFilled/><StackItem isFilled>{account.linkedUsername}</StackItem></Stack></DataListCell>,
|
||||||
|
]}/>
|
||||||
|
<DataListAction aria-labelledby='foo' aria-label='foo action' id='setPasswordAction'>
|
||||||
|
{isLinked && <Button variant='link' onClick={() => this.unLinkAccount(account)}><UnlinkIcon size='sm'/> <Msg msgKey='unLink'/></Button>}
|
||||||
|
{!isLinked && <Button variant='link' onClick={() => this.linkAccount(account)}><LinkIcon size='sm'/> <Msg msgKey='link'/></Button>}
|
||||||
|
</DataListAction>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
))
|
||||||
|
|
||||||
|
} </>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private badge(account: LinkedAccount): React.ReactNode {
|
||||||
|
if (account.social) {
|
||||||
|
return (<Badge><Msg msgKey='socialLogin'/></Badge>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<Badge style={{backgroundColor: "green"}} ><Msg msgKey='systemDefined'/></Badge>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findIcon(account: LinkedAccount): React.ReactNode {
|
||||||
|
if (account.providerName.toLowerCase().includes('github')) return (<GithubIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('linkedin')) return (<LinkedinIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('facebook')) return (<FacebookIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('google')) return (<GoogleIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('instagram')) return (<InstagramIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('microsoft')) return (<MicrosoftIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('bitbucket')) return (<BitbucketIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('twitter')) return (<TwitterIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('openshift')) return (<OpenshiftIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('gitlab')) return (<GitlabIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('paypal')) return (<PaypalIcon size='xl'/>);
|
||||||
|
if (account.providerName.toLowerCase().includes('stackoverflow')) return (<StackOverflowIcon size='xl'/>);
|
||||||
|
|
||||||
|
return (<CubeIcon size='xl'/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const LinkedAccountsPagewithRouter = withRouter(LinkedAccountsPage);
|
||||||
|
export {LinkedAccountsPagewithRouter as LinkedAccountsPage};
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare const baseUrl: string;
|
||||||
|
declare const referrer: string;
|
||||||
|
declare const referrerUri: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a redirect uri that can return to this application with referrer and referrer_uri intact.
|
||||||
|
*
|
||||||
|
* @param currentLocation The ReactRouter location to return to.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert
|
||||||
|
*/
|
||||||
|
export const createRedirect = (currentLocation: string): string => {
|
||||||
|
let redirectUri: string = baseUrl;
|
||||||
|
|
||||||
|
if (typeof referrer !== 'undefined') {
|
||||||
|
// '_hash_' is a workaround for when uri encoding is not
|
||||||
|
// sufficient to escape the # character properly.
|
||||||
|
// The problem is that both the redirect and the application URL contain a hash.
|
||||||
|
// The browser will consider anything after the first hash to be client-side. So
|
||||||
|
// it sees the hash in the redirect param and stops.
|
||||||
|
redirectUri += "?referrer=" + referrer + "&referrer_uri=" + referrerUri.replace('#', '_hash_');
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodeURIComponent(redirectUri) + encodeURIComponent("/#" + currentLocation);
|
||||||
|
}
|
|
@ -318,7 +318,7 @@
|
||||||
'./icons/binoculars-icon.js': '@empty',
|
'./icons/binoculars-icon.js': '@empty',
|
||||||
'./icons/biohazard-icon.js': '@empty',
|
'./icons/biohazard-icon.js': '@empty',
|
||||||
'./icons/birthday-cake-icon.js': '@empty',
|
'./icons/birthday-cake-icon.js': '@empty',
|
||||||
'./icons/bitbucket-icon.js': '@empty',
|
//'./icons/bitbucket-icon.js': '@empty',
|
||||||
'./icons/bitcoin-icon.js': '@empty',
|
'./icons/bitcoin-icon.js': '@empty',
|
||||||
'./icons/bity-icon.js': '@empty',
|
'./icons/bity-icon.js': '@empty',
|
||||||
'./icons/black-tie-icon.js': '@empty',
|
'./icons/black-tie-icon.js': '@empty',
|
||||||
|
@ -543,7 +543,7 @@
|
||||||
'./icons/crutch-icon.js': '@empty',
|
'./icons/crutch-icon.js': '@empty',
|
||||||
'./icons/css3-alt-icon.js': '@empty',
|
'./icons/css3-alt-icon.js': '@empty',
|
||||||
'./icons/css3-icon.js': '@empty',
|
'./icons/css3-icon.js': '@empty',
|
||||||
'./icons/cube-icon.js': '@empty',
|
//'./icons/cube-icon.js': '@empty',
|
||||||
'./icons/cubes-icon.js': '@empty',
|
'./icons/cubes-icon.js': '@empty',
|
||||||
'./icons/cut-icon.js': '@empty',
|
'./icons/cut-icon.js': '@empty',
|
||||||
'./icons/cuttlefish-icon.js': '@empty',
|
'./icons/cuttlefish-icon.js': '@empty',
|
||||||
|
@ -656,7 +656,7 @@
|
||||||
'./icons/eye-icon.js': '@empty',
|
'./icons/eye-icon.js': '@empty',
|
||||||
'./icons/eye-slash-icon.js': '@empty',
|
'./icons/eye-slash-icon.js': '@empty',
|
||||||
'./icons/facebook-f-icon.js': '@empty',
|
'./icons/facebook-f-icon.js': '@empty',
|
||||||
'./icons/facebook-icon.js': '@empty',
|
//'./icons/facebook-icon.js': '@empty',
|
||||||
'./icons/facebook-messenger-icon.js': '@empty',
|
'./icons/facebook-messenger-icon.js': '@empty',
|
||||||
'./icons/facebook-square-icon.js': '@empty',
|
'./icons/facebook-square-icon.js': '@empty',
|
||||||
'./icons/fantasy-flight-games-icon.js': '@empty',
|
'./icons/fantasy-flight-games-icon.js': '@empty',
|
||||||
|
@ -763,10 +763,10 @@
|
||||||
'./icons/git-icon.js': '@empty',
|
'./icons/git-icon.js': '@empty',
|
||||||
'./icons/git-square-icon.js': '@empty',
|
'./icons/git-square-icon.js': '@empty',
|
||||||
'./icons/github-alt-icon.js': '@empty',
|
'./icons/github-alt-icon.js': '@empty',
|
||||||
'./icons/github-icon.js': '@empty',
|
//'./icons/github-icon.js': '@empty',
|
||||||
'./icons/github-square-icon.js': '@empty',
|
'./icons/github-square-icon.js': '@empty',
|
||||||
'./icons/gitkraken-icon.js': '@empty',
|
'./icons/gitkraken-icon.js': '@empty',
|
||||||
'./icons/gitlab-icon.js': '@empty',
|
//'./icons/gitlab-icon.js': '@empty',
|
||||||
'./icons/gitter-icon.js': '@empty',
|
'./icons/gitter-icon.js': '@empty',
|
||||||
'./icons/glass-cheers-icon.js': '@empty',
|
'./icons/glass-cheers-icon.js': '@empty',
|
||||||
'./icons/glass-martini-alt-icon.js': '@empty',
|
'./icons/glass-martini-alt-icon.js': '@empty',
|
||||||
|
@ -786,7 +786,7 @@
|
||||||
'./icons/goodreads-g-icon.js': '@empty',
|
'./icons/goodreads-g-icon.js': '@empty',
|
||||||
'./icons/goodreads-icon.js': '@empty',
|
'./icons/goodreads-icon.js': '@empty',
|
||||||
'./icons/google-drive-icon.js': '@empty',
|
'./icons/google-drive-icon.js': '@empty',
|
||||||
'./icons/google-icon.js': '@empty',
|
//'./icons/google-icon.js': '@empty',
|
||||||
'./icons/google-play-icon.js': '@empty',
|
'./icons/google-play-icon.js': '@empty',
|
||||||
'./icons/google-plus-g-icon.js': '@empty',
|
'./icons/google-plus-g-icon.js': '@empty',
|
||||||
'./icons/google-plus-icon.js': '@empty',
|
'./icons/google-plus-icon.js': '@empty',
|
||||||
|
@ -910,7 +910,7 @@
|
||||||
//'./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',
|
||||||
'./icons/instagram-icon.js': '@empty',
|
//'./icons/instagram-icon.js': '@empty',
|
||||||
'./icons/integration-icon.js': '@empty',
|
'./icons/integration-icon.js': '@empty',
|
||||||
'./icons/intercom-icon.js': '@empty',
|
'./icons/intercom-icon.js': '@empty',
|
||||||
//'./icons/internet-explorer-icon.js': '@empty',
|
//'./icons/internet-explorer-icon.js': '@empty',
|
||||||
|
@ -970,8 +970,8 @@
|
||||||
'./icons/life-ring-icon.js': '@empty',
|
'./icons/life-ring-icon.js': '@empty',
|
||||||
'./icons/lightbulb-icon.js': '@empty',
|
'./icons/lightbulb-icon.js': '@empty',
|
||||||
'./icons/line-icon.js': '@empty',
|
'./icons/line-icon.js': '@empty',
|
||||||
'./icons/link-icon.js': '@empty',
|
//'./icons/link-icon.js': '@empty',
|
||||||
'./icons/linkedin-icon.js': '@empty',
|
//'./icons/linkedin-icon.js': '@empty',
|
||||||
'./icons/linkedin-in-icon.js': '@empty',
|
'./icons/linkedin-in-icon.js': '@empty',
|
||||||
'./icons/linode-icon.js': '@empty',
|
'./icons/linode-icon.js': '@empty',
|
||||||
'./icons/linux-icon.js': '@empty',
|
'./icons/linux-icon.js': '@empty',
|
||||||
|
@ -1040,7 +1040,7 @@
|
||||||
'./icons/microphone-icon.js': '@empty',
|
'./icons/microphone-icon.js': '@empty',
|
||||||
'./icons/microphone-slash-icon.js': '@empty',
|
'./icons/microphone-slash-icon.js': '@empty',
|
||||||
'./icons/microscope-icon.js': '@empty',
|
'./icons/microscope-icon.js': '@empty',
|
||||||
'./icons/microsoft-icon.js': '@empty',
|
//'./icons/microsoft-icon.js': '@empty',
|
||||||
'./icons/middleware-icon.js': '@empty',
|
'./icons/middleware-icon.js': '@empty',
|
||||||
'./icons/migration-icon.js': '@empty',
|
'./icons/migration-icon.js': '@empty',
|
||||||
'./icons/minus-circle-icon.js': '@empty',
|
'./icons/minus-circle-icon.js': '@empty',
|
||||||
|
@ -1098,7 +1098,7 @@
|
||||||
'./icons/on-running-icon.js': '@empty',
|
'./icons/on-running-icon.js': '@empty',
|
||||||
'./icons/opencart-icon.js': '@empty',
|
'./icons/opencart-icon.js': '@empty',
|
||||||
'./icons/openid-icon.js': '@empty',
|
'./icons/openid-icon.js': '@empty',
|
||||||
'./icons/openshift-icon.js': '@empty',
|
//'./icons/openshift-icon.js': '@empty',
|
||||||
//'./icons/opera-icon.js': '@empty',
|
//'./icons/opera-icon.js': '@empty',
|
||||||
'./icons/optimize-icon.js': '@empty',
|
'./icons/optimize-icon.js': '@empty',
|
||||||
'./icons/optin-monster-icon.js': '@empty',
|
'./icons/optin-monster-icon.js': '@empty',
|
||||||
|
@ -1281,7 +1281,7 @@
|
||||||
'./icons/pause-icon.js': '@empty',
|
'./icons/pause-icon.js': '@empty',
|
||||||
'./icons/paused-icon.js': '@empty',
|
'./icons/paused-icon.js': '@empty',
|
||||||
'./icons/paw-icon.js': '@empty',
|
'./icons/paw-icon.js': '@empty',
|
||||||
'./icons/paypal-icon.js': '@empty',
|
//'./icons/paypal-icon.js': '@empty',
|
||||||
'./icons/peace-icon.js': '@empty',
|
'./icons/peace-icon.js': '@empty',
|
||||||
'./icons/pen-alt-icon.js': '@empty',
|
'./icons/pen-alt-icon.js': '@empty',
|
||||||
'./icons/pen-fancy-icon.js': '@empty',
|
'./icons/pen-fancy-icon.js': '@empty',
|
||||||
|
@ -1549,7 +1549,7 @@
|
||||||
'./icons/square-root-alt-icon.js': '@empty',
|
'./icons/square-root-alt-icon.js': '@empty',
|
||||||
'./icons/squarespace-icon.js': '@empty',
|
'./icons/squarespace-icon.js': '@empty',
|
||||||
'./icons/stack-exchange-icon.js': '@empty',
|
'./icons/stack-exchange-icon.js': '@empty',
|
||||||
'./icons/stack-overflow-icon.js': '@empty',
|
//'./icons/stack-overflow-icon.js': '@empty',
|
||||||
'./icons/stackpath-icon.js': '@empty',
|
'./icons/stackpath-icon.js': '@empty',
|
||||||
'./icons/stamp-icon.js': '@empty',
|
'./icons/stamp-icon.js': '@empty',
|
||||||
'./icons/star-and-crescent-icon.js': '@empty',
|
'./icons/star-and-crescent-icon.js': '@empty',
|
||||||
|
@ -1688,7 +1688,7 @@
|
||||||
'./icons/tumblr-square-icon.js': '@empty',
|
'./icons/tumblr-square-icon.js': '@empty',
|
||||||
'./icons/tv-icon.js': '@empty',
|
'./icons/tv-icon.js': '@empty',
|
||||||
'./icons/twitch-icon.js': '@empty',
|
'./icons/twitch-icon.js': '@empty',
|
||||||
'./icons/twitter-icon.js': '@empty',
|
//'./icons/twitter-icon.js': '@empty',
|
||||||
'./icons/twitter-square-icon.js': '@empty',
|
'./icons/twitter-square-icon.js': '@empty',
|
||||||
'./icons/typo3-icon.js': '@empty',
|
'./icons/typo3-icon.js': '@empty',
|
||||||
'./icons/uber-icon.js': '@empty',
|
'./icons/uber-icon.js': '@empty',
|
||||||
|
@ -1703,7 +1703,7 @@
|
||||||
'./icons/universal-access-icon.js': '@empty',
|
'./icons/universal-access-icon.js': '@empty',
|
||||||
'./icons/university-icon.js': '@empty',
|
'./icons/university-icon.js': '@empty',
|
||||||
'./icons/unknown-icon.js': '@empty',
|
'./icons/unknown-icon.js': '@empty',
|
||||||
'./icons/unlink-icon.js': '@empty',
|
//'./icons/unlink-icon.js': '@empty',
|
||||||
'./icons/unlock-alt-icon.js': '@empty',
|
'./icons/unlock-alt-icon.js': '@empty',
|
||||||
'./icons/unlock-icon.js': '@empty',
|
'./icons/unlock-icon.js': '@empty',
|
||||||
'./icons/unlocked-icon.js': '@empty',
|
'./icons/unlocked-icon.js': '@empty',
|
||||||
|
|
Loading…
Reference in a new issue