Updates patternfly libs and fixes breaking changes (#10748)
adding nvmrc CIAM-1048 Device Activity screen PF updates CIAM-1046: Personal Info sub-header update Updates SigningInPage to use EmptyState component when there are no credentials. rearanged some components used in signing in page Displays ApplicationPage content in description list. Updates refresh link on ContentPage, updates Resources screen. CIAM-1049 Linked Accounts screen PF updates CIAM-1043-General upstream updates Updates AccountPage to display form errors. fix: display Set up Authenticator Application link on large viewport fix(page structure): rearranges page sections CIAM-1254/Personal info PF4 updates & Sidebar text updates updating layouts updating layout on Signing in and Linked acounts adding patternfly-additions adding patternfly-addons styles Updates Application page based on designs feedback. moving page description Updates status label on Applications page to be capitalized. Updates the copy-fonts script for keycloak.v2 to copy all font directories instead of one. update Personal info screen - set max width of 600px for form input fields update Personal info - remove required indicator from input fields General updates (#2) * removed the extra lines being shown * tweaked general spacing * general alignment and spacer application * refactor to get proper alignments without css globals * forgot to add the conditional on displaying the set up buttons * try and adjust the alignments Co-authored-by: zwitter <zwitter@redhat.com> resolve merge conflicts Device activity updates (#4) * update text to sentence case * update device info columns to be dynamic across various viewport sizes * update signed in device layout * update based on feedback Co-authored-by: Jon Szeto <jszeto@redhat.com> Linked accounts update (#3) * linked accounts screen - updated icons & Linked/Unlinked Login Providers layout & update text to sentence case Co-authored-by: Jon Szeto <jszeto@redhat.com> fixing ts errors cleaning up fonts and messages final review updates message update for Back to admin console link fixing capitalization on 2fa updating landing page welcome message fix: reposition Back to... link adjusting size for confirm modal updating spacing and alignment issues updating resources page removing unused header class fixes ts issues and updates node version to match the themes install npm updates fixing pf addons adding chokidar to get babel:watch working fixing issues from pull request feedback fixing tests fixes signingin page test fixing tests Co-authored-by: Tyler Andor <tandor@highereducation.com>
|
@ -62,11 +62,7 @@ public class RecoveryAuthnCodesCredentialProvider
|
||||||
.helpText("recovery-authn-codes-help-text").iconCssClass("kcAuthenticatorRecoveryAuthnCodesClass")
|
.helpText("recovery-authn-codes-help-text").iconCssClass("kcAuthenticatorRecoveryAuthnCodesClass")
|
||||||
.removeable(true);
|
.removeable(true);
|
||||||
UserModel user = metadataContext.getUser();
|
UserModel user = metadataContext.getUser();
|
||||||
if (user != null && !isConfiguredFor(session.getContext().getRealm(), user, getType())) {
|
|
||||||
builder.createAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name());
|
builder.createAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name());
|
||||||
} else {
|
|
||||||
builder.updateAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name());
|
|
||||||
}
|
|
||||||
return builder.build(session);
|
return builder.build(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class AccountManagement extends AuthRealm implements PageWithLogOutAction
|
||||||
@FindBy(xpath = "//a[@id='referer']")
|
@FindBy(xpath = "//a[@id='referer']")
|
||||||
private WebElement backToRefererLink;
|
private WebElement backToRefererLink;
|
||||||
|
|
||||||
@FindBy(linkText = "Sign Out")
|
@FindBy(linkText = "Sign out")
|
||||||
private WebElement signOutLink;
|
private WebElement signOutLink;
|
||||||
|
|
||||||
@FindBy(linkText = "Account")
|
@FindBy(linkText = "Account")
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractAccountPage extends AbstractPage {
|
public abstract class AbstractAccountPage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(linkText = "Sign Out")
|
@FindBy(linkText = "Sign out")
|
||||||
private WebElement logoutLink;
|
private WebElement logoutLink;
|
||||||
|
|
||||||
@FindBy(id = "kc-current-locale-link")
|
@FindBy(id = "kc-current-locale-link")
|
||||||
|
|
|
@ -1357,7 +1357,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
||||||
loginPage.login("realm-admin", "password");
|
loginPage.login("realm-admin", "password");
|
||||||
Assert.assertTrue(applicationsPage.isCurrent());
|
Assert.assertTrue(applicationsPage.isCurrent());
|
||||||
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
|
||||||
Assert.assertThat(apps.keySet(), hasItems("Admin CLI", "Security Admin Console"));
|
Assert.assertThat(apps.keySet(), hasItems("Admin CLI", "security admin console"));
|
||||||
events.clear();
|
events.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,11 +139,11 @@ public class DeviceActivityPage extends AbstractLoggedInPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStarted() {
|
public String getStarted() {
|
||||||
return getTextFromItem("started").split("Started at ")[1];
|
return getTextFromItem("started").split("Started ")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExpires() {
|
public String getExpires() {
|
||||||
return getTextFromItem("expires").split("Expires at ")[1];
|
return getTextFromItem("expires").split("Expires ")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSignOutDisplayed() {
|
public boolean isSignOutDisplayed() {
|
||||||
|
|
|
@ -104,11 +104,11 @@ public class LinkedAccountsPage extends AbstractLoggedInPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSocialLoginBadge() {
|
public boolean hasSocialLoginBadge() {
|
||||||
return getTextFromElement(badgeElement).equals("Social Login");
|
return getTextFromElement(badgeElement).equals("Social login");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSystemDefinedBadge() {
|
public boolean hasSystemDefinedBadge() {
|
||||||
return getTextFromElement(badgeElement).equals("System Defined");
|
return getTextFromElement(badgeElement).equals("System defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasSocialIcon() {
|
public boolean hasSocialIcon() {
|
||||||
|
|
|
@ -28,6 +28,6 @@ public class PageNotFound extends AbstractLoggedInPage {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCurrent() {
|
public boolean isCurrent() {
|
||||||
return driver.getPageSource().contains("Page Not Found");
|
return driver.getPageSource().contains("Page not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||||
*/
|
*/
|
||||||
public class SigningInTest extends BaseAccountPageTest {
|
public class SigningInTest extends BaseAccountPageTest {
|
||||||
public static final String PASSWORD_LABEL = "My Password";
|
public static final String PASSWORD_LABEL = "My password";
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
private SigningInPage signingInPage;
|
private SigningInPage signingInPage;
|
||||||
|
@ -91,8 +91,8 @@ public class SigningInTest extends BaseAccountPageTest {
|
||||||
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
|
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
|
||||||
|
|
||||||
assertThat(signingInPage.getCategoriesCount(), is(2));
|
assertThat(signingInPage.getCategoriesCount(), is(2));
|
||||||
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic Authentication"));
|
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic authentication"));
|
||||||
assertThat(signingInPage.getCategoryTitle("two-factor"), is("Two-Factor Authentication"));
|
assertThat(signingInPage.getCategoryTitle("two-factor"), is("Two-factor authentication"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -160,7 +160,7 @@ public class SigningInTest extends BaseAccountPageTest {
|
||||||
|
|
||||||
signingInPage.assertCurrent();
|
signingInPage.assertCurrent();
|
||||||
assertThat(otpCredentialType.isSetUp(), is(false));
|
assertThat(otpCredentialType.isSetUp(), is(false));
|
||||||
assertThat(otpCredentialType.getTitle(), is("Authenticator Application"));
|
assertThat(otpCredentialType.getTitle(), is("authenticator application"));
|
||||||
|
|
||||||
final String label1 = "OTP is secure";
|
final String label1 = "OTP is secure";
|
||||||
final String label2 = "OTP is inconvenient";
|
final String label2 = "OTP is inconvenient";
|
||||||
|
|
|
@ -3,11 +3,16 @@ doCancel=Cancel
|
||||||
doLogOutAllSessions=Log out all sessions
|
doLogOutAllSessions=Log out all sessions
|
||||||
doRemove=Remove
|
doRemove=Remove
|
||||||
doAdd=Add
|
doAdd=Add
|
||||||
doSignOut=Sign Out
|
doSignOut=Sign out
|
||||||
doLogIn=Log In
|
doLogIn=Log In
|
||||||
doLink=Link
|
doLink=Link
|
||||||
noAccessMessage=Access not allowed
|
noAccessMessage=Access not allowed
|
||||||
|
|
||||||
|
personalInfoSidebarTitle=Personal info
|
||||||
|
accountSecuritySidebarTitle=Account security
|
||||||
|
signingInSidebarTitle=Signing in
|
||||||
|
deviceActivitySidebarTitle=Device activity
|
||||||
|
linkedAccountsSidebarTitle=Linked accounts
|
||||||
|
|
||||||
editAccountHtmlTitle=Edit Account
|
editAccountHtmlTitle=Edit Account
|
||||||
personalInfoHtmlTitle=Personal Info
|
personalInfoHtmlTitle=Personal Info
|
||||||
|
@ -19,7 +24,7 @@ sessionsHtmlTitle=Sessions
|
||||||
accountManagementTitle=Keycloak Account Management
|
accountManagementTitle=Keycloak Account Management
|
||||||
authenticatorTitle=Authenticator
|
authenticatorTitle=Authenticator
|
||||||
applicationsHtmlTitle=Applications
|
applicationsHtmlTitle=Applications
|
||||||
linkedAccountsHtmlTitle=Linked Accounts
|
linkedAccountsHtmlTitle=Linked accounts
|
||||||
|
|
||||||
accountManagementWelcomeMessage=Welcome to Keycloak Account Management
|
accountManagementWelcomeMessage=Welcome to Keycloak Account Management
|
||||||
personalInfoIntroMessage=Manage your basic information
|
personalInfoIntroMessage=Manage your basic information
|
||||||
|
@ -32,7 +37,7 @@ updatePasswordTitle=Update Password
|
||||||
updatePasswordMessageTitle=Make sure you choose a strong password
|
updatePasswordMessageTitle=Make sure you choose a strong password
|
||||||
updatePasswordMessage=A strong password contains a mix of numbers, letters, and symbols. It is hard to guess, does not resemble a real word, and is only used for this account.
|
updatePasswordMessage=A strong password contains a mix of numbers, letters, and symbols. It is hard to guess, does not resemble a real word, and is only used for this account.
|
||||||
personalSubTitle=Your Personal Info
|
personalSubTitle=Your Personal Info
|
||||||
personalSubMessage=Manage this basic information: your first name, last name and email
|
personalSubMessage=Manage your basic information.
|
||||||
|
|
||||||
authenticatorCode=One-time code
|
authenticatorCode=One-time code
|
||||||
email=Email
|
email=Email
|
||||||
|
@ -94,7 +99,7 @@ role_offline-access=Offline access
|
||||||
role_uma_authorization=Obtain permissions
|
role_uma_authorization=Obtain permissions
|
||||||
client_account=Account
|
client_account=Account
|
||||||
client_account-console=Account Console
|
client_account-console=Account Console
|
||||||
client_security-admin-console=Security Admin Console
|
client_security-admin-console=security admin console
|
||||||
client_admin-cli=Admin CLI
|
client_admin-cli=Admin CLI
|
||||||
client_realm-management=Realm Management
|
client_realm-management=Realm Management
|
||||||
client_broker=Broker
|
client_broker=Broker
|
||||||
|
@ -120,7 +125,7 @@ applications=Applications
|
||||||
account=Account
|
account=Account
|
||||||
federatedIdentity=Federated Identity
|
federatedIdentity=Federated Identity
|
||||||
authenticator=Authenticator
|
authenticator=Authenticator
|
||||||
device-activity=Device Activity
|
device-activity=Device activity
|
||||||
sessions=Sessions
|
sessions=Sessions
|
||||||
log=Log
|
log=Log
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-recovery-codes-settings-form" method="post">
|
<form action="${url.loginAction}" class="${properties.kcFormClass!}" id="kc-recovery-codes-settings-form" method="post">
|
||||||
<input type="hidden" name="generatedRecoveryAuthnCodes" value="${recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}" />
|
<input type="hidden" name="generatedRecoveryAuthnCodes" value="${recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}" />
|
||||||
<input type="hidden" name="generatedAt" value="${recoveryAuthnCodesConfigBean.generatedAt?c}" />
|
<input type="hidden" name="generatedAt" value="${recoveryAuthnCodesConfigBean.generatedAt?c}" />
|
||||||
<input type="hidden" name="userLabel" value=" " />
|
<input type="hidden" id="userLabel" name="userLabel" value="${msg("recovery-codes-label-default")}" />
|
||||||
|
|
||||||
<#if isAppInitiatedAction??>
|
<#if isAppInitiatedAction??>
|
||||||
<input type="submit"
|
<input type="submit"
|
||||||
|
|
|
@ -442,6 +442,7 @@ recovery-codes-action-cancel=Cancel setup
|
||||||
recovery-codes-download-file-header=Keep these recovery codes somewhere safe.
|
recovery-codes-download-file-header=Keep these recovery codes somewhere safe.
|
||||||
recovery-codes-download-file-description=Recovery codes are single-use passcodes that allow you to log in to your account if you do not have access to your authenticator.
|
recovery-codes-download-file-description=Recovery codes are single-use passcodes that allow you to log in to your account if you do not have access to your authenticator.
|
||||||
recovery-codes-download-file-date= These codes were generated on
|
recovery-codes-download-file-date= These codes were generated on
|
||||||
|
recovery-codes-label-default=Recovery codes
|
||||||
|
|
||||||
# WebAuthn
|
# WebAuthn
|
||||||
webauthn-display-name=Security Key
|
webauthn-display-name=Security Key
|
||||||
|
|
|
@ -112,8 +112,10 @@
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="${resourceCommonUrl}/web_modules/@patternfly/react-core/dist/styles/base.css"/>
|
<link rel="stylesheet" type="text/css" href="${resourceCommonUrl}/web_modules/@patternfly/react-core/dist/styles/base.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="${resourceCommonUrl}/web_modules/@patternfly/react-core/dist/styles/app.css"/>
|
<link rel="stylesheet" type="text/css" href="${resourceCommonUrl}/web_modules/@patternfly/react-core/dist/styles/app.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="${resourceCommonUrl}/web_modules/@patternfly/patternfly/patternfly-addons.css"/>
|
||||||
<link href="${resourceUrl}/public/layout.css" rel="stylesheet"/>
|
<link href="${resourceUrl}/public/layout.css" rel="stylesheet"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -181,18 +183,23 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-page__header-tools">
|
<div class="pf-c-page__header-tools">
|
||||||
<#if referrer?has_content && referrer_uri?has_content>
|
<#if referrer?has_content && referrer_uri?has_content>
|
||||||
<div class="pf-c-page__header-tools-group pf-m-icons">
|
<div class="pf-c-page__header-tools-group pf-m-icons pf-u-display-none pf-u-display-flex-on-md">
|
||||||
<a id="landingReferrerLink" href="${referrer_uri}" id="referrer" tabindex="0"><span class="pf-icon pf-icon-arrow"></span>${msg("backTo",referrerName)}</a>
|
<a id="landingReferrerLink" href="${referrer_uri}" class="pf-c-button pf-m-link" tabindex="0">
|
||||||
|
<span class="pf-c-button__icon pf-m-start">
|
||||||
|
<i class="pf-icon pf-icon-arrow" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
${msg("backToAdminConsole")}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<div class="pf-c-page__header-tools-group pf-m-icons">
|
<div class="pf-c-page__header-tools-group pf-m-icons pf-u-display-none pf-u-display-flex-on-md pf-u-mr-md">
|
||||||
<button id="landingSignInButton" tabindex="0" style="display:none" onclick="keycloak.login();" class="pf-c-button pf-m-primary" type="button">${msg("doSignIn")}</button>
|
<button id="landingSignInButton" tabindex="0" style="display:none" onclick="keycloak.login();" class="pf-c-button pf-m-primary" type="button">${msg("doSignIn")}</button>
|
||||||
<button id="landingSignOutButton" tabindex="0" style="display:none" onclick="keycloak.logout();" class="pf-c-button pf-m-primary" type="button">${msg("doSignOut")}</button>
|
<button id="landingSignOutButton" tabindex="0" style="display:none" onclick="keycloak.logout();" class="pf-c-button pf-m-primary" type="button">${msg("doSignOut")}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Kebab for mobile -->
|
<!-- Kebab for mobile -->
|
||||||
<div class="pf-c-page__header-tools-group">
|
<div class="pf-c-page__header-tools-group pf-u-display-none-on-md">
|
||||||
<div id="landingMobileKebab" class="pf-c-dropdown pf-m-mobile" onclick="toggleMobileDropdown();"> <!-- pf-m-expanded -->
|
<div id="landingMobileKebab" class="pf-c-dropdown pf-m-mobile" onclick="toggleMobileDropdown();"> <!-- pf-m-expanded -->
|
||||||
<button aria-label="Actions" tabindex="0" id="landingMobileKebabButton" class="pf-c-dropdown__toggle pf-m-plain" type="button" aria-expanded="true" aria-haspopup="true">
|
<button aria-label="Actions" tabindex="0" id="landingMobileKebabButton" class="pf-c-dropdown__toggle pf-m-plain" type="button" aria-expanded="true" aria-haspopup="true">
|
||||||
<svg fill="currentColor" height="1em" width="1em" viewBox="0 0 192 512" aria-hidden="true" role="img" style="vertical-align: -0.125em;"><path d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z" transform=""></path></svg>
|
<svg fill="currentColor" height="1em" width="1em" viewBox="0 0 192 512" aria-hidden="true" role="img" style="vertical-align: -0.125em;"><path d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z" transform=""></path></svg>
|
||||||
|
@ -200,7 +207,7 @@
|
||||||
<ul id="landingMobileDropdown" aria-labelledby="landingMobileKebabButton" class="pf-c-dropdown__menu pf-m-align-right" role="menu" style="display:none">
|
<ul id="landingMobileDropdown" aria-labelledby="landingMobileKebabButton" class="pf-c-dropdown__menu pf-m-align-right" role="menu" style="display:none">
|
||||||
<#if referrer?has_content && referrer_uri?has_content>
|
<#if referrer?has_content && referrer_uri?has_content>
|
||||||
<li role="none">
|
<li role="none">
|
||||||
<a id="landingMobileReferrerLink" href="${referrer_uri}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${msg("backTo",referrerName)}</a>
|
<a id="landingMobileReferrerLink" href="${referrer_uri}" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${msg("backToAdminConsole")}</a>
|
||||||
</li>
|
</li>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
|
@ -220,31 +227,35 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main role="main" class="pf-c-page__main">
|
<main role="main" class="pf-c-page__main">
|
||||||
<section class="pf-c-page__main-section pf-m-light">
|
<section class="pf-c-page__main-section pf-m-limit-width pf-m-light pf-m-shadow-bottom">
|
||||||
|
<div class="pf-c-page__main-body">
|
||||||
<div class="pf-c-content" id="landingWelcomeMessage">
|
<div class="pf-c-content" id="landingWelcomeMessage">
|
||||||
<h1>${msg("accountManagementWelcomeMessage")}</h1>
|
<h1>${msg("accountManagementWelcomeMessage")}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="pf-c-page__main-section">
|
<section class="pf-c-page__main-section pf-m-limit-width pf-m-overflow-scroll">
|
||||||
|
<div class="pf-c-page__main-body">
|
||||||
<div class="pf-l-gallery pf-m-gutter">
|
<div class="pf-l-gallery pf-m-gutter">
|
||||||
<#assign content=theme.apply("content.json")?eval>
|
<#assign content=theme.apply("content.json")?eval>
|
||||||
<#list content as item>
|
<#list content as item>
|
||||||
<div class="pf-l-gallery__item pf-c-card" id="landing-${item.id}">
|
<div class="pf-l-gallery__item" id="landing-${item.id}">
|
||||||
|
<div class="pf-c-card pf-m-full-height">
|
||||||
<div>
|
<div>
|
||||||
<div class="pf-c-card__header pf-c-content">
|
<div class="pf-c-card__title pf-c-content">
|
||||||
<h2>
|
<h2 class="pf-u-display-flex pf-u-w-100 pf-u-flex-direction-column">
|
||||||
<#if item.icon??>
|
<#if item.icon??>
|
||||||
<i class="pf-icon ${item.icon}"></i>
|
<i class="pf-icon ${item.icon}"></i>
|
||||||
<#elseif item.iconSvg??>
|
<#elseif item.iconSvg??>
|
||||||
<img src="${item.iconSvg}" alt="icon"/>
|
<img src="${item.iconSvg}" alt="icon"/>
|
||||||
</#if>
|
</#if>
|
||||||
${msg(item.label)}
|
${msg(item.label)}
|
||||||
</h2>
|
</h2>
|
||||||
<#if item.descriptionLabel??>
|
|
||||||
<p>${msg(item.descriptionLabel)}</p>
|
|
||||||
</#if>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body pf-c-content">
|
<div class="pf-c-card__body">
|
||||||
|
<#if item.descriptionLabel??>
|
||||||
|
<p class="pf-u-mb-md">${msg(item.descriptionLabel)}</p>
|
||||||
|
</#if>
|
||||||
<#if item.content??>
|
<#if item.content??>
|
||||||
<#list item.content as sub>
|
<#list item.content as sub>
|
||||||
<div id="landing-${sub.id}">
|
<div id="landing-${sub.id}">
|
||||||
|
@ -257,8 +268,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</#list>
|
</#list>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
# Put new messages for Account Console Here
|
# Put new messages for Account Console Here
|
||||||
# Feel free to use any existing messages from the base theme
|
# Feel free to use any existing messages from the base theme
|
||||||
pageNotFound=Page Not Found
|
pageNotFound=Page not found
|
||||||
forbidden=Forbidden
|
forbidden=Forbidden
|
||||||
needAccessRights=You do not have access rights to this request. Contact your administrator.
|
needAccessRights=You do not have access rights to this request. Contact your administrator.
|
||||||
invalidRoute={0} is not a valid route.
|
invalidRoute={0} is not a valid route.
|
||||||
actionRequiresIDP=This action requires redirection to your identity provider.
|
actionRequiresIDP=This action requires redirection to your identity provider.
|
||||||
actionNotDefined=No Action defined
|
actionNotDefined=No action defined
|
||||||
continue=Continue
|
continue=Continue
|
||||||
refreshPage=Refresh the page
|
refreshPage=Refresh the page
|
||||||
|
refresh=Refresh
|
||||||
done=Done
|
done=Done
|
||||||
cancel=Cancel
|
cancel=Cancel
|
||||||
remove=Remove
|
remove=Remove
|
||||||
|
@ -15,38 +16,46 @@ update=Update
|
||||||
loadingMessage=Account Console loading ...
|
loadingMessage=Account Console loading ...
|
||||||
unknownUser=Anonymous
|
unknownUser=Anonymous
|
||||||
fullName={0} {1}
|
fullName={0} {1}
|
||||||
|
allFieldsRequired=All fields are required.
|
||||||
|
|
||||||
selectLocale=Select a locale
|
selectLocale=Select a locale
|
||||||
doSignIn=Sign In
|
doSignIn=Sign in
|
||||||
|
|
||||||
# Device Activity Page
|
backToAdminConsole=Back to admin console
|
||||||
signedInDevices=Signed In Devices
|
accountManagementWelcomeMessage=Welcome to Keycloak account management
|
||||||
signedInDevicesExplanation=Sign out any device that is unfamiliar.
|
|
||||||
|
# Personal info page
|
||||||
|
personalInfoHtmlTitle=Personal info
|
||||||
|
|
||||||
|
# Device activity page
|
||||||
|
signedInDevices=Signed in devices
|
||||||
|
signedInDevicesExplanation=Sign out of any unfamiliar devices.
|
||||||
signOutWarning=Sign out the session?
|
signOutWarning=Sign out the session?
|
||||||
signOutAllDevices=Sign Out All Devices
|
signOutAllDevices=Sign out all devices
|
||||||
signOutAllDevicesWarning=This action will sign out all the devices that have signed in to your account, including the current device you are using.
|
signOutAllDevicesWarning=This action will sign out all the devices that have signed in to your account, including the current device you are using.
|
||||||
recentlyUsedDevices=Recently Used Devices
|
recentlyUsedDevices=Recently used devices
|
||||||
recentlyUsedDevicesExplanation=Devices used in the last month, but not currently logged in.
|
recentlyUsedDevicesExplanation=Devices used in the last month, but not currently logged in.
|
||||||
lastAccess=Last Access
|
lastAccess=Last access
|
||||||
unknownOperatingSystem=Unknown Operating System
|
unknownOperatingSystem=Unknown operating system
|
||||||
currentDevice=Current Device
|
currentDevice=Current device
|
||||||
currentSession=Current Session
|
currentSession=Current session
|
||||||
signedOutSession=Signed out {0}/{1}
|
signedOutSession=Signed out {0}/{1}
|
||||||
lastAccessedOn=Last accessed on
|
lastAccessedOn=Last accessed
|
||||||
clients=Clients
|
clients=Clients
|
||||||
startedAt=Started at
|
started=Started
|
||||||
expiresAt=Expires at
|
expires=Expires
|
||||||
ipAddress=IP Address
|
ipAddress=IP address
|
||||||
|
|
||||||
# Resources Page
|
# Resources page
|
||||||
resourceName=Resource Name
|
resourceName=Resource name
|
||||||
nextPage=Next
|
nextPage=Next
|
||||||
previousPage=Previous
|
previousPage=Previous
|
||||||
firstPage=First Page
|
firstPage=First page
|
||||||
resourceSharedWith=Resource is shared with {0}
|
resourceSharedWith=Resource is shared with {0}
|
||||||
and=\ and {0} other users
|
and=\ and {0} other users
|
||||||
add=Add
|
add=Add
|
||||||
share=Share
|
share=Share
|
||||||
|
shareWith=Share with
|
||||||
edit=Edit
|
edit=Edit
|
||||||
close=Close
|
close=Close
|
||||||
unShare=Unshare all
|
unShare=Unshare all
|
||||||
|
@ -57,75 +66,81 @@ 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
|
||||||
permissions=Permissions
|
permissions=Permissions
|
||||||
|
selectPermissions=Select the permissions
|
||||||
unShareAllConfirm=Are you sure you want to completely remove all shares?
|
unShareAllConfirm=Are you sure you want to completely remove all shares?
|
||||||
userNotFound=No user found with name or email {0}
|
userNotFound=No user found with name or email {0}
|
||||||
|
|
||||||
# Linked Accounts Page
|
# Linked accounts page
|
||||||
linkedAccountsTitle=Linked Accounts
|
linkedAccountsTitle=Linked accounts
|
||||||
linkedAccountsIntroMessage=Manage logins through third-party accounts.
|
linkedAccountsIntroMessage=Manage logins through third-party accounts.
|
||||||
linkedLoginProviders=Linked Login Providers
|
linkedLoginProviders=Linked login providers
|
||||||
unlinkedLoginProviders=Unlinked Login Providers
|
unlinkedLoginProviders=Unlinked login providers
|
||||||
linkedEmpty=No Linked Providers
|
linkedEmpty=No linked providers
|
||||||
unlinkedEmpty=No Unlinked Providers
|
unlinkedEmpty=No unlinked providers
|
||||||
socialLogin=Social Login
|
socialLogin=Social login
|
||||||
systemDefined=System Defined
|
systemDefined=System defined
|
||||||
link=Link Account
|
link=Link account
|
||||||
unLink=Unlink Account
|
unLink=Unlink account
|
||||||
|
|
||||||
# Signing In Page
|
# Signing in page
|
||||||
signingIn=Signing In
|
signingIn=Signing in
|
||||||
signingInSubMessage=Configure ways to sign in.
|
signingInSubMessage=Configure ways to sign in.
|
||||||
credentialCreatedAt=Created
|
credentialCreatedAt=Created
|
||||||
successRemovedMessage={0} was removed.
|
successRemovedMessage={0} was removed.
|
||||||
stopUsingCred=Stop using {0}?
|
stopUsingCred=Stop using {0}?
|
||||||
|
changePassword=Change password
|
||||||
removeCred=Remove {0}
|
removeCred=Remove {0}
|
||||||
setUpNew=Set up {0}
|
setUpNew=Set up {0}
|
||||||
|
removeCredAriaLabel=Remove credential
|
||||||
|
updateCredAriaLabel=Update credential
|
||||||
notSetUp={0} is not set up.
|
notSetUp={0} is not set up.
|
||||||
two-factor=Two-Factor Authentication
|
two-factor=Two-factor authentication
|
||||||
passwordless=Passwordless
|
passwordless=Passwordless
|
||||||
unknown=Unknown
|
unknown=Unknown
|
||||||
password-display-name=Password
|
password-display-name=Password
|
||||||
password-help-text=Log in by entering your password.
|
password-help-text=Log in by entering your password.
|
||||||
password=My Password
|
password=My password
|
||||||
otp-display-name=Authenticator Application
|
otp-display-name=authenticator application
|
||||||
otp-help-text=Enter a verification code from authenticator application.
|
otp-help-text=Enter a verification code from authenticator application.
|
||||||
recovery-authn-code=My Recovery Authentication Codes
|
recovery-authn-code=My recovery authentication codes
|
||||||
recovery-authn-codes-display-name=Recovery Authentication Codes
|
recovery-authn-codes-display-name=Recovery authentication codes
|
||||||
recovery-authn-codes-help-text=These codes can be used to regain your access in case your other 2FA means are not available.
|
recovery-authn-codes-help-text=These codes can be used to regain your access in case your other 2FA means are not available.
|
||||||
recovery-codes-number-used={0} recovery codes used
|
recovery-codes-number-used={0} recovery codes used
|
||||||
recovery-codes-number-remaining={0} recovery codes remaining
|
recovery-codes-number-remaining={0} recovery codes remaining
|
||||||
recovery-codes-generate-new-codes=Generate new codes to ensure access to your account
|
recovery-codes-generate-new-codes=Generate new codes to ensure access to your account
|
||||||
webauthn-display-name=Security Key
|
webauthn-display-name=Security key
|
||||||
webauthn-help-text=Use your security key to sign in.
|
webauthn-help-text=Use your security key to log in.
|
||||||
webauthn-passwordless-display-name=Security Key
|
webauthn-passwordless-display-name=Security key
|
||||||
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
|
webauthn-passwordless-help-text=Use your security key for passwordless log in.
|
||||||
basic-authentication=Basic Authentication
|
basic-authentication=Basic authentication
|
||||||
invalidRequestMessage=Invalid Request
|
invalidRequestMessage=Invalid request
|
||||||
|
|
||||||
# Applications page
|
# Applications page
|
||||||
applicationsPageTitle=Applications
|
applicationsPageTitle=Applications
|
||||||
internalApp=Internal
|
internalApp=Internal
|
||||||
thirdPartyApp=Third-party
|
thirdPartyApp=Third-party
|
||||||
offlineAccess=Offline Access
|
offlineAccess=Offline access
|
||||||
inUse=In use
|
inUse=In use
|
||||||
notInUse=Not in use
|
notInUse=Not in use
|
||||||
applicationDetails=Application Details
|
applicationDetails=Application details
|
||||||
client=Client
|
client=Client
|
||||||
description=Description
|
description=Description
|
||||||
baseUrl=URL
|
baseUrl=URL
|
||||||
accessGrantedOn=Access granted on
|
accessGrantedOn=Access granted on
|
||||||
removeButton=Remove access
|
removeButton=Remove access
|
||||||
removeModalTitle=Remove Access
|
removeModalTitle=Remove access
|
||||||
removeModalMessage=This will remove the currently granted access permission for {0}. You will need to grant access again if you want to use this app.
|
removeModalMessage=This will remove the currently granted access permission for {0}. You will need to grant access again if you want to use this app.
|
||||||
confirmButton=Confirm
|
confirmButton=Confirm
|
||||||
infoMessage=By clicking 'Remove Access', you will remove granted permissions of this application. This application will no longer use your information.
|
infoMessage=By clicking 'Remove Access', you will remove granted permissions of this application. This application will no longer use your information.
|
||||||
termsOfService=Terms of service
|
termsOfService=Terms of service
|
||||||
policy=Privacy policy
|
policy=Privacy policy
|
||||||
|
applicationType=Application type
|
||||||
|
status=Status
|
||||||
|
|
||||||
#Delete Account page
|
#Delete account page
|
||||||
doDelete=Delete
|
doDelete=Delete
|
||||||
deleteAccountSummary=Deleting your account will erase all your data and log you out immediately.
|
deleteAccountSummary=Deleting your account will erase all your data and log you out immediately.
|
||||||
deleteAccount=Delete Account
|
deleteAccount=Delete account
|
||||||
deleteAccountWarning=This is irreversible. All your data will be permanently destroyed, and irretrievable.
|
deleteAccountWarning=This is irreversible. All your data will be permanently destroyed, and irretrievable.
|
||||||
|
|
||||||
error-invalid-value=''{0}'' has invalid value.
|
error-invalid-value=''{0}'' has invalid value.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"id": "personal-info",
|
"id": "personal-info",
|
||||||
"path": "personal-info",
|
"path": "personal-info",
|
||||||
"icon": "pf-icon-user",
|
"icon": "pf-icon-user",
|
||||||
"label": "personalInfoHtmlTitle",
|
"label": "personalInfoSidebarTitle",
|
||||||
"descriptionLabel": "personalInfoIntroMessage",
|
"descriptionLabel": "personalInfoIntroMessage",
|
||||||
"modulePath": "/content/account-page/AccountPage.js",
|
"modulePath": "/content/account-page/AccountPage.js",
|
||||||
"componentName": "AccountPage"
|
"componentName": "AccountPage"
|
||||||
|
@ -11,27 +11,27 @@
|
||||||
{
|
{
|
||||||
"id": "security",
|
"id": "security",
|
||||||
"icon": "pf-icon-security",
|
"icon": "pf-icon-security",
|
||||||
"label": "accountSecurityTitle",
|
"label": "accountSecuritySidebarTitle",
|
||||||
"descriptionLabel": "accountSecurityIntroMessage",
|
"descriptionLabel": "accountSecurityIntroMessage",
|
||||||
"content": [
|
"content": [
|
||||||
{
|
{
|
||||||
"id": "signingin",
|
"id": "signingin",
|
||||||
"path": "security/signingin",
|
"path": "security/signingin",
|
||||||
"label": "signingIn",
|
"label": "signingInSidebarTitle",
|
||||||
"modulePath": "/content/signingin-page/SigningInPage.js",
|
"modulePath": "/content/signingin-page/SigningInPage.js",
|
||||||
"componentName": "SigningInPage"
|
"componentName": "SigningInPage"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "device-activity",
|
"id": "device-activity",
|
||||||
"path": "security/device-activity",
|
"path": "security/device-activity",
|
||||||
"label": "device-activity",
|
"label": "deviceActivitySidebarTitle",
|
||||||
"modulePath": "/content/device-activity-page/DeviceActivityPage.js",
|
"modulePath": "/content/device-activity-page/DeviceActivityPage.js",
|
||||||
"componentName": "DeviceActivityPage"
|
"componentName": "DeviceActivityPage"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "linked-accounts",
|
"id": "linked-accounts",
|
||||||
"path": "security/linked-accounts",
|
"path": "security/linked-accounts",
|
||||||
"label": "linkedAccountsHtmlTitle",
|
"label": "linkedAccountsSidebarTitle",
|
||||||
"modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js",
|
"modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js",
|
||||||
"componentName": "LinkedAccountsPage",
|
"componentName": "LinkedAccountsPage",
|
||||||
"hidden": "!features.isLinkedAccountsEnabled"
|
"hidden": "!features.isLinkedAccountsEnabled"
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.faac274b-9d49-4ba1-9ef0-610572d38128 {
|
||||||
|
fill: #1877f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f55f6e4a-14c2-4793-a1d5-db4b028479c8 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="b36e017a-ce19-4905-9b48-48066e87bbf5" data-name="New stuff">
|
||||||
|
<g>
|
||||||
|
<rect class="faac274b-9d49-4ba1-9ef0-610572d38128" width="32" height="32" rx="1.19"/>
|
||||||
|
<path class="f55f6e4a-14c2-4793-a1d5-db4b028479c8" d="M26.93,16.07a10.93,10.93,0,1,0-12.64,10.8V19.23H11.52V16.07h2.77V13.66c0-2.74,1.63-4.26,4.13-4.26a16.32,16.32,0,0,1,2.45.22v2.69H19.49A1.58,1.58,0,0,0,17.71,14v2.05h3l-.48,3.16H17.71v7.64a10.94,10.94,0,0,0,9.22-10.8Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 730 B |
|
@ -0,0 +1,16 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.b42551c7-a511-4544-a371-f6e3883f7abd {
|
||||||
|
fill: #fff;
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="f00b56ab-1ecb-4d94-b704-cf429e3c78a4" data-name="GitHub">
|
||||||
|
<g>
|
||||||
|
<rect width="32" height="32" rx="1.19"/>
|
||||||
|
<path class="b42551c7-a511-4544-a371-f6e3883f7abd" d="M16,5.13a11.06,11.06,0,0,0-3.5,21.55c.56.1.76-.24.76-.53s0-1,0-1.88c-3.07.67-3.72-1.48-3.72-1.48a2.91,2.91,0,0,0-1.23-1.62c-1-.69.08-.67.08-.67a2.32,2.32,0,0,1,1.69,1.14,2.36,2.36,0,0,0,3.22.92,2.36,2.36,0,0,1,.7-1.48c-2.45-.28-5-1.23-5-5.47a4.29,4.29,0,0,1,1.13-3,4,4,0,0,1,.11-2.93s.93-.3,3,1.13a10.55,10.55,0,0,1,5.54,0c2.11-1.43,3-1.13,3-1.13a4,4,0,0,1,.11,2.93,4.25,4.25,0,0,1,1.13,3c0,4.25-2.58,5.18-5.05,5.46a2.62,2.62,0,0,1,.75,2.05c0,1.47,0,2.67,0,3s.2.64.76.53A11.06,11.06,0,0,0,16,5.13Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 912 B |
|
@ -0,0 +1,77 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.e5ef51ce-3313-4c04-b152-23646fd2b306 {
|
||||||
|
fill: none;
|
||||||
|
clip-rule: evenodd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ef16f3ef-fb03-4a62-9cce-d65767c17c4c {
|
||||||
|
fill: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.e0f4cb03-a97e-447b-8c37-60d897bac484 {
|
||||||
|
clip-path: url(#fe0cecb0-916b-4b9d-828f-63ec94884d2e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.afb39849-fc2d-48aa-a4a4-070e36b84e51 {
|
||||||
|
fill: #3e82f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a2ee00a5-b156-4157-adc5-b40fd1ee5ebd {
|
||||||
|
clip-path: url(#e022c77c-5a12-4caf-aaed-c796166a8562);
|
||||||
|
}
|
||||||
|
|
||||||
|
.a025e47e-f997-4bd7-8964-88ada0c7c12f {
|
||||||
|
fill: #32a753;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b4e7c98a-856b-4280-8aaa-b9f157cf22ea {
|
||||||
|
clip-path: url(#b1e1e904-5b74-4ac6-8dc5-11a864f7f8ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
.b0bf74a2-e960-4e68-948c-0e3dfb1bcd2a {
|
||||||
|
fill: #f9bb00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ba55bfd1-dca2-4617-b45c-128fdf3bf3b4 {
|
||||||
|
clip-path: url(#bcdf0102-4e91-4218-8945-e2893d757d6d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fae83dfa-2d64-4e2a-9cd9-b00b32869370 {
|
||||||
|
fill: #e74235;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<clipPath id="fe0cecb0-916b-4b9d-828f-63ec94884d2e">
|
||||||
|
<path class="e5ef51ce-3313-4c04-b152-23646fd2b306" d="M25.85,16.23a12.53,12.53,0,0,0-.18-2.06H16.2v3.89h5.41a4.58,4.58,0,0,1-2,3v2.53h3.25a9.81,9.81,0,0,0,3-7.39Zm0,0"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="e022c77c-5a12-4caf-aaed-c796166a8562">
|
||||||
|
<path class="e5ef51ce-3313-4c04-b152-23646fd2b306" d="M16.2,26.05a9.61,9.61,0,0,0,6.65-2.43L19.6,21.09a6.06,6.06,0,0,1-9-3.18H7.22v2.6a10,10,0,0,0,9,5.54Zm0,0"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="b1e1e904-5b74-4ac6-8dc5-11a864f7f8ec">
|
||||||
|
<path class="e5ef51ce-3313-4c04-b152-23646fd2b306" d="M10.58,17.91a5.86,5.86,0,0,1,0-3.82v-2.6H7.22a10,10,0,0,0,0,9l3.36-2.6Zm0,0"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="bcdf0102-4e91-4218-8945-e2893d757d6d">
|
||||||
|
<path class="e5ef51ce-3313-4c04-b152-23646fd2b306" d="M16.2,10A5.39,5.39,0,0,1,20,11.45l2.89-2.88A9.7,9.7,0,0,0,16.2,6a10,10,0,0,0-9,5.54l3.36,2.6A6,6,0,0,1,16.2,10Zm0,0"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g id="a06b7517-c1f4-49d5-aa34-cee2287c4769" data-name="Google">
|
||||||
|
<g id="fbe0f094-aaab-4b03-a7bf-452a2c250397" data-name="Full color">
|
||||||
|
<rect class="ef16f3ef-fb03-4a62-9cce-d65767c17c4c" width="32" height="32" rx="1.19"/>
|
||||||
|
<g>
|
||||||
|
<g class="e0f4cb03-a97e-447b-8c37-60d897bac484">
|
||||||
|
<rect class="afb39849-fc2d-48aa-a4a4-070e36b84e51" x="10.62" y="8.59" width="20.81" height="20.61"/>
|
||||||
|
</g>
|
||||||
|
<g class="a2ee00a5-b156-4157-adc5-b40fd1ee5ebd">
|
||||||
|
<rect class="a025e47e-f997-4bd7-8964-88ada0c7c12f" x="1.64" y="12.33" width="26.8" height="19.31"/>
|
||||||
|
</g>
|
||||||
|
<g class="b4e7c98a-856b-4280-8aaa-b9f157cf22ea">
|
||||||
|
<rect class="b0bf74a2-e960-4e68-948c-0e3dfb1bcd2a" x="0.57" y="5.9" width="15.59" height="20.19"/>
|
||||||
|
</g>
|
||||||
|
<g class="ba55bfd1-dca2-4617-b45c-128fdf3bf3b4">
|
||||||
|
<rect class="fae83dfa-2d64-4e2a-9cd9-b00b32869370" x="1.64" y="0.37" width="26.87" height="19.31"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -0,0 +1,23 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.ac4e1fda-441f-45c3-89b5-eabc007f54ca {
|
||||||
|
fill: #e1306c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a7c172c4-b213-4581-937c-bb042fd94b3e {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="a864479b-0561-4117-aa66-744aa86c5250" data-name="Instagram">
|
||||||
|
<g id="bb9ebb9e-2d15-4e72-a42a-7eff89494a26" data-name="Black and white">
|
||||||
|
<rect class="ac4e1fda-441f-45c3-89b5-eabc007f54ca" width="32" height="32" rx="1.19"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="a7c172c4-b213-4581-937c-bb042fd94b3e" d="M16,6.09c-2.69,0-3,0-4.09.06a7.18,7.18,0,0,0-2.4.46,5,5,0,0,0-2.9,2.9,7.18,7.18,0,0,0-.46,2.4C6.1,13,6.09,13.31,6.09,16s0,3,.06,4.09a7.18,7.18,0,0,0,.46,2.4,5,5,0,0,0,2.9,2.9,7.18,7.18,0,0,0,2.4.46c1.06,0,1.4.06,4.09.06s3,0,4.09-.06a7.18,7.18,0,0,0,2.4-.46,5,5,0,0,0,2.9-2.9,7.18,7.18,0,0,0,.46-2.4c0-1.06.06-1.4.06-4.09s0-3-.06-4.09a7.18,7.18,0,0,0-.46-2.4,5,5,0,0,0-2.9-2.9,7.18,7.18,0,0,0-2.4-.46C19,6.1,18.69,6.09,16,6.09Zm0,1.79c2.65,0,3,0,4,0a5.73,5.73,0,0,1,1.84.34A3,3,0,0,1,23,9a3,3,0,0,1,.75,1.14A5.73,5.73,0,0,1,24.07,12c0,1,.05,1.35.05,4s0,3-.05,4a5.73,5.73,0,0,1-.34,1.84,3.38,3.38,0,0,1-1.89,1.89,5.73,5.73,0,0,1-1.84.34c-1,0-1.35.05-4,.05s-3,0-4-.05a5.73,5.73,0,0,1-1.84-.34A3,3,0,0,1,9,23a3,3,0,0,1-.75-1.14A5.73,5.73,0,0,1,7.93,20c0-1,0-1.35,0-4s0-3,0-4a5.73,5.73,0,0,1,.34-1.84A3,3,0,0,1,9,9a3,3,0,0,1,1.14-.75A5.73,5.73,0,0,1,12,7.93c1,0,1.35,0,4,0"/>
|
||||||
|
<path class="a7c172c4-b213-4581-937c-bb042fd94b3e" d="M16,19.3A3.3,3.3,0,1,1,19.3,16,3.3,3.3,0,0,1,16,19.3Zm0-8.39A5.09,5.09,0,1,0,21.09,16,5.09,5.09,0,0,0,16,10.91Z"/>
|
||||||
|
<path class="a7c172c4-b213-4581-937c-bb042fd94b3e" d="M22.48,10.71a1.19,1.19,0,1,1-1.19-1.19,1.19,1.19,0,0,1,1.19,1.19Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,23 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.a975829d-e85a-4fdc-b46a-16eba47b4e8e {
|
||||||
|
fill: #2867b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b17912fc-cab5-4688-935b-8aa15b250003 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="adf29cd2-f5a2-4433-a836-7d0b8cd64c5d" data-name="LinkedIn">
|
||||||
|
<g>
|
||||||
|
<rect class="a975829d-e85a-4fdc-b46a-16eba47b4e8e" width="32" height="32" rx="1.19"/>
|
||||||
|
<g>
|
||||||
|
<polygon class="b17912fc-cab5-4688-935b-8aa15b250003" points="10.47 25.5 6.46 25.5 6.46 12.59 10.47 12.59 10.47 25.5 10.47 25.5"/>
|
||||||
|
<path class="b17912fc-cab5-4688-935b-8aa15b250003" d="M8.46,10.83A2.33,2.33,0,1,1,10.79,8.5a2.33,2.33,0,0,1-2.33,2.33Z"/>
|
||||||
|
<path class="b17912fc-cab5-4688-935b-8aa15b250003" d="M25.5,25.5h-4V19.22c0-1.49,0-3.42-2.09-3.42S17,17.43,17,19.11V25.5H13V12.59h3.84v1.76h.06a4.21,4.21,0,0,1,3.8-2.08c4.06,0,4.81,2.67,4.81,6.15V25.5Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 951 B |
|
@ -0,0 +1,34 @@
|
||||||
|
<svg id="bf5a9f86-a166-4609-aea2-c789db71fd48" data-name="Microsoft" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.fe82c64e-6d2d-42d3-aeec-ae7ea0741e78 {
|
||||||
|
fill: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.be49cf2d-e5c3-4773-b2c7-b422380b776a {
|
||||||
|
fill: #7fba00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a9f66f41-846e-499a-af6d-5503813bb401 {
|
||||||
|
fill: #ffb900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a8084a6b-0b46-4437-8e12-638c29ccbea1 {
|
||||||
|
fill: #f25022;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bb872b1d-e833-4ba3-99af-3b105ed602a0 {
|
||||||
|
fill: #00a4ef;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="e0b7e64b-db07-46e0-9f19-72b142175f6b" data-name="Full color">
|
||||||
|
<rect class="fe82c64e-6d2d-42d3-aeec-ae7ea0741e78" width="32" height="32" rx="1.19"/>
|
||||||
|
<g>
|
||||||
|
<rect class="be49cf2d-e5c3-4773-b2c7-b422380b776a" x="16.47" y="6.5" width="9.03" height="9.03" rx="0.1"/>
|
||||||
|
<rect class="a9f66f41-846e-499a-af6d-5503813bb401" x="16.47" y="16.47" width="9.03" height="9.03" rx="0.1"/>
|
||||||
|
<rect class="a8084a6b-0b46-4437-8e12-638c29ccbea1" x="6.5" y="6.5" width="9.03" height="9.03" rx="0.1"/>
|
||||||
|
<rect class="bb872b1d-e833-4ba3-99af-3b105ed602a0" x="6.5" y="16.47" width="9.03" height="9.03" rx="0.1"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.321739,0,0,0.321739,-0.234299,0.0972928)">
|
||||||
|
<path d="M64.38,23.5C67.556,24.99 70.436,27.044 72.88,29.56L88.62,23.83C80.176,12.044 66.537,5.037 52.039,5.037C27.353,5.037 7.039,25.351 7.039,50.037C7.039,51.183 7.083,52.328 7.17,53.47L22.91,47.74C24.033,32.597 36.806,20.737 51.99,20.737C56.273,20.737 60.503,21.68 64.38,23.5" style="fill:rgb(238,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M16,58.19L1,63.63C2.371,69.099 4.744,74.267 8,78.87L23.7,73.16C19.696,69.029 16.99,63.813 15.92,58.16" style="fill:rgb(238,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M81.16,52.25C80.904,55.75 80.009,59.173 78.52,62.35C71.736,76.878 54.205,83.258 39.67,76.49C36.487,75.027 33.599,72.994 31.15,70.49L15.45,76.21C23.88,88.016 37.524,95.039 52.032,95.039C69.504,95.039 85.459,84.851 92.81,69C96.075,61.982 97.454,54.233 96.81,46.52L81.16,52.25Z" style="fill:rgb(238,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M85,33L70,38.45C72.851,43.476 74.153,49.236 73.74,55L89.44,49.29C88.992,43.63 87.486,38.104 85,33" style="fill:rgb(238,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M29.46,45.36L13.72,51.09C13.94,53.604 14.368,56.096 15,58.54L30,53.1C29.501,50.552 29.346,47.949 29.54,45.36" style="fill:rgb(204,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M99,28C97.903,25.724 96.619,23.543 95.16,21.48L79.43,27.18C81.238,29.04 82.791,31.132 84.05,33.4L99,28Z" style="fill:rgb(204,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M15.45,76.17C16.671,77.879 18.007,79.503 19.45,81.03L36.54,74.79C34.578,73.561 32.769,72.105 31.15,70.45L15.45,76.17ZM96.86,46.54L81.16,52.25C80.985,54.545 80.539,56.81 79.83,59L96.92,52.76C97.04,50.684 97.02,48.603 96.86,46.53" style="fill:rgb(204,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M29.4,48.52C29.36,47.466 29.38,46.411 29.46,45.36L13.72,51.09C13.8,52.09 13.93,53.09 14.08,54.09L29.4,48.52Z" style="fill:rgb(163,0,0);fill-rule:nonzero;"/>
|
||||||
|
<path d="M96.72,23.82C96.22,23.01 95.72,22.22 95.16,21.44L79.43,27.18C80.116,27.884 80.764,28.626 81.37,29.4L96.72,23.82Z" style="fill:rgb(163,0,0);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,26 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.f425009f-d653-4243-b52f-e6e7efdbf57a {
|
||||||
|
fill: #4d4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bac6a0a4-fc2d-4f36-9ded-667acc6f4b52 {
|
||||||
|
fill: #bcbbbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b9d59bf2-7616-43cd-a672-04b81955bad9 {
|
||||||
|
fill: #f48024;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="b393dd85-54a9-4370-beab-fda62d40e5d8" data-name="StackOverflow">
|
||||||
|
<g id="beef34ed-ad1e-45a9-8dc6-781d71c5dd19" data-name="Full color">
|
||||||
|
<rect class="f425009f-d653-4243-b52f-e6e7efdbf57a" width="32" height="32" rx="1.19"/>
|
||||||
|
<g>
|
||||||
|
<polygon class="bac6a0a4-fc2d-4f36-9ded-667acc6f4b52" points="21.29 23.44 21.29 18.82 23.34 18.82 23.34 25.5 7.94 25.5 7.94 18.82 9.99 18.82 9.99 23.44 21.29 23.44"/>
|
||||||
|
<path class="b9d59bf2-7616-43cd-a672-04b81955bad9" d="M11.53,21.9h8.22V20.36H11.53ZM19,6.5l-1.39,1,5.08,6.83,1.39-1Zm-4.21,4L21.29,16l1.08-1.28L15.85,9.22Zm-2.16,3.8,7.7,3.59L21,16.36l-7.7-3.59Zm7.18,5.53.36-1.51-8.27-1.72-.35,1.7Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.b48eca1a-55ea-4187-8747-2a0a7c0905ff {
|
||||||
|
fill: #1da1f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a0fa0208-710f-4171-b935-86383a49537b {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="e43e3b03-e423-44e8-9d4d-c8c1f8f88064" data-name="Twitter">
|
||||||
|
<g>
|
||||||
|
<rect class="b48eca1a-55ea-4187-8747-2a0a7c0905ff" width="32" height="32" rx="1.19"/>
|
||||||
|
<path class="a0fa0208-710f-4171-b935-86383a49537b" d="M24.42,11.5c0,.18,0,.37,0,.56A12.25,12.25,0,0,1,5.58,22.37,8.72,8.72,0,0,0,12,20.59a4.3,4.3,0,0,1-4-3,4.28,4.28,0,0,0,.81.07,4.22,4.22,0,0,0,1.13-.15A4.29,4.29,0,0,1,6.43,13.3v-.05a4.4,4.4,0,0,0,2,.54A4.31,4.31,0,0,1,7,8a12.26,12.26,0,0,0,8.88,4.5,4,4,0,0,1-.11-1,4.3,4.3,0,0,1,7.44-3,8.59,8.59,0,0,0,2.73-1A4.31,4.31,0,0,1,24.09,10a8.86,8.86,0,0,0,2.47-.68,8.72,8.72,0,0,1-2.14,2.23Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 895 B |
|
@ -1,3 +1,4 @@
|
||||||
|
/* Globals */
|
||||||
.brand {
|
.brand {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
@ -12,5 +13,114 @@
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linked Accounts screen */
|
||||||
|
.idp-icon-social {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#github-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_github_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#linkedin-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_linkedin_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#facebook-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_facebook_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#google-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_google_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#microsoft-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_microsoft_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#instagram-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_instagram_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#stackoverflow-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_stack_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#twitter-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_twitter_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#openshift-idp-icon-social {
|
||||||
|
background-image: url(../img/socialmedia/socialmedia_icons_openshift_transparent.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Account Page screen */
|
||||||
|
.personal-info-form .pf-c-form__group-control {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Device Activity screen */
|
||||||
|
.signed-in-device-list .pf-c-data-list__item-row {
|
||||||
|
--pf-c-data-list__item-row--PaddingRight: 0;
|
||||||
|
--pf-c-data-list__item-row--PaddingLeft: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signed-in-device-list .pf-c-data-list__expandable-content-body {
|
||||||
|
--pf-c-data-list__expandable-content-body--PaddingRight: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signed-in-device-grid {
|
||||||
|
grid-template-columns: auto repeat(11, [col-start] 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.signed-in-device-list.pf-c-data-list {
|
||||||
|
--pf-c-data-list--sm--BorderTopWidth: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-data-list__item {
|
||||||
|
--pf-c-data-list__item--BorderBottomWidth: 1px;
|
||||||
|
--pf-c-data-list__item--BorderBottomColor: var(--pf-global--BorderColor--100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.signed-in-device-list.pf-c-data-list {
|
||||||
|
--pf-c-data-list--BorderTopWidth: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.pf-c-data-list__item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--pf-c-data-list__item--BackgroundColor);
|
||||||
|
border-bottom: var(--pf-c-data-list__item--BorderBottomWidth) solid var(--pf-c-data-list__item--BorderBottomColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.signed-in-device-list.pf-c-data-list {
|
||||||
|
--pf-c-data-list--BorderTopWidth: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.signed-in-device-list .pf-c-description-list {
|
||||||
|
--pf-c-description-list--GridTemplateColumns--count: 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signing in screen */
|
||||||
|
.title-case:first-letter,
|
||||||
|
.cred-title:first-letter,
|
||||||
|
#otp-not-set-up .pf-c-empty-state__body:first-letter {
|
||||||
|
text-transform: capitalize
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Applications screen */
|
||||||
|
#applications-list-header .pf-c-data-list__item-content {
|
||||||
|
--pf-c-data-list__item-content--md--PaddingBottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-u-pl-35xl {
|
||||||
|
padding-left: 4.5rem;
|
||||||
}
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
v16.13.0
|
|
@ -19,15 +19,14 @@ import * as React from 'react';
|
||||||
import {KeycloakService} from './keycloak-service/keycloak.service';
|
import {KeycloakService} from './keycloak-service/keycloak.service';
|
||||||
|
|
||||||
import {PageNav} from './PageNav';
|
import {PageNav} from './PageNav';
|
||||||
import {PageToolbar} from './PageToolbar';
|
import {PageHeaderTool} from './PageHeaderTool';
|
||||||
import {makeRoutes} from './ContentPages';
|
import {makeRoutes} from './ContentPages';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Brand,
|
Brand,
|
||||||
Page,
|
Page,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageSection,
|
PageSidebar
|
||||||
PageSidebar,
|
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import { KeycloakContext } from './keycloak-service/KeycloakContext';
|
import { KeycloakContext } from './keycloak-service/KeycloakContext';
|
||||||
|
@ -61,11 +60,11 @@ export class App extends React.Component<AppProps> {
|
||||||
const username = (
|
const username = (
|
||||||
<span style={{marginLeft: '10px'}} id="loggedInUser">{loggedInUserName()}</span>
|
<span style={{marginLeft: '10px'}} id="loggedInUser">{loggedInUserName()}</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Header = (
|
const Header = (
|
||||||
<PageHeader
|
<PageHeader
|
||||||
logo={<a id="brandLink" href={brandUrl}><Brand src={brandImg} alt="Logo" className="brand"/></a>}
|
logo={<a id="brandLink" href={brandUrl}><Brand src={brandImg} alt="Logo" className="brand"/></a>}
|
||||||
toolbar={<PageToolbar/>}
|
headerTools={<PageHeaderTool/>}
|
||||||
avatar={username}
|
|
||||||
showNavToggle
|
showNavToggle
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -73,13 +72,9 @@ export class App extends React.Component<AppProps> {
|
||||||
const Sidebar = <PageSidebar nav={<PageNav/>} />;
|
const Sidebar = <PageSidebar nav={<PageNav/>} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span style={{ height: '100%'}}>
|
|
||||||
<Page header={Header} sidebar={Sidebar} isManagedSidebar>
|
<Page header={Header} sidebar={Sidebar} isManagedSidebar>
|
||||||
<PageSection>
|
|
||||||
{makeRoutes()}
|
{makeRoutes()}
|
||||||
</PageSection>
|
|
||||||
</Page>
|
</Page>
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -85,7 +85,8 @@ function createNavItems(activePage: PageDef, contentParam: ContentItem[], groupN
|
||||||
groupId={item.groupId}
|
groupId={item.groupId}
|
||||||
key={item.groupId}
|
key={item.groupId}
|
||||||
title={Msg.localize(item.label, item.labelParams)}
|
title={Msg.localize(item.label, item.labelParams)}
|
||||||
isExpanded={isChildOf(item, activePage)}>
|
isExpanded={isChildOf(item, activePage)}
|
||||||
|
>
|
||||||
{createNavItems(activePage, item.content, groupNum + 1)}
|
{createNavItems(activePage, item.content, groupNum + 1)}
|
||||||
</NavExpandable>
|
</NavExpandable>
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import {PageHeaderTools} from '@patternfly/react-core';
|
||||||
|
import {ReferrerLink} from './widgets/ReferrerLink';
|
||||||
|
import {LogoutButton} from './widgets/Logout';
|
||||||
|
|
||||||
|
declare const referrerName: string;
|
||||||
|
|
||||||
|
export class PageHeaderTool extends React.Component {
|
||||||
|
private hasReferrer: boolean = typeof referrerName !== 'undefined';
|
||||||
|
|
||||||
|
public render(): React.ReactNode {
|
||||||
|
return (
|
||||||
|
<PageHeaderTools>
|
||||||
|
{this.hasReferrer &&
|
||||||
|
<div className="pf-c-page__header-tools-group">
|
||||||
|
<ReferrerLink/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className="pf-c-page__header-tools-group">
|
||||||
|
<LogoutButton/>
|
||||||
|
</div>
|
||||||
|
</PageHeaderTools>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,39 +46,20 @@ export class PageToolbar extends React.Component<PageToolbarProps, PageToolbarSt
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const kebabDropdownItems = [];
|
|
||||||
if (this.hasReferrer) {
|
|
||||||
kebabDropdownItems.push(
|
|
||||||
<ReferrerDropdownItem key='referrerDropdownItem'/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
kebabDropdownItems.push(<LogoutDropdownItem key='LogoutDropdownItem'/>);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{this.hasReferrer &&
|
{this.hasReferrer &&
|
||||||
<ToolbarGroup key='referrerGroup'>
|
<ToolbarGroup key='referrerGroup' alignment={{default:"alignRight"}}>
|
||||||
<ToolbarItem className="pf-m-icons" key='referrer'>
|
<ToolbarItem className="pf-m-icons" key='referrer'>
|
||||||
<ReferrerLink/>
|
<ReferrerLink/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ToolbarGroup key='secondGroup'>
|
<ToolbarGroup key='secondGroup' alignment={{default:"alignRight"}}>
|
||||||
<ToolbarItem className="pf-m-icons" key='logout'>
|
<ToolbarItem className="pf-m-icons" key='logout'>
|
||||||
<LogoutButton/>
|
<LogoutButton/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
|
|
||||||
<ToolbarItem key='kebab' className="pf-m-mobile">
|
|
||||||
<Dropdown
|
|
||||||
isPlain
|
|
||||||
position="right"
|
|
||||||
toggle={<KebabToggle id="mobileKebab" onToggle={this.onKebabDropdownToggle} />}
|
|
||||||
isOpen={this.state.isKebabDropdownOpen}
|
|
||||||
dropdownItems={kebabDropdownItems}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
|
@ -97,7 +97,7 @@ export class ContentAlert extends React.Component<ContentAlertProps, ContentAler
|
||||||
isLiveRegion
|
isLiveRegion
|
||||||
variant={variant}
|
variant={variant}
|
||||||
title={message}
|
title={message}
|
||||||
action={
|
actionClose={
|
||||||
<AlertActionCloseButton
|
<AlertActionCloseButton
|
||||||
title={message}
|
title={message}
|
||||||
variantLabel={`${variant} alert`}
|
variantLabel={`${variant} alert`}
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Button, Grid, GridItem, Title, Tooltip} from '@patternfly/react-core';
|
import {Button, Grid, GridItem, Text, Title, Tooltip, Card, CardBody, Stack, StackItem, PageSection, TextContent, PageSectionVariants, SplitItem, Split} from '@patternfly/react-core';
|
||||||
import {RedoIcon} from '@patternfly/react-icons';
|
import {RedoIcon, SyncAltIcon} from '@patternfly/react-icons';
|
||||||
|
|
||||||
import {Msg} from '../widgets/Msg';
|
import {Msg} from '../widgets/Msg';
|
||||||
import {ContentAlert} from './ContentAlert';
|
import {ContentAlert} from './ContentAlert';
|
||||||
|
@ -41,25 +41,39 @@ export class ContentPage extends React.Component<ContentPageProps> {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ContentAlert />
|
<ContentAlert />
|
||||||
<section id="page-heading" className="pf-c-page__main-section pf-m-light">
|
|
||||||
<Grid>
|
<PageSection variant={PageSectionVariants.light} className="pf-u-pb-xs">
|
||||||
<GridItem span={11}><Title headingLevel='h1' size='3xl'><strong><Msg msgKey={this.props.title}/></strong></Title></GridItem>
|
<Split>
|
||||||
{this.props.onRefresh &&
|
<SplitItem isFilled>
|
||||||
<GridItem span={1}>
|
<TextContent>
|
||||||
<Tooltip content={<Msg msgKey='refreshPage'/>}>
|
<Title headingLevel="h1" size="2xl" className="pf-u-mb-xl">
|
||||||
<Button aria-describedby="refresh page" id='refresh-page' variant='plain' onClick={this.props.onRefresh}>
|
<Msg msgKey={this.props.title} />
|
||||||
<RedoIcon size='sm'/>
|
</Title>
|
||||||
|
{this.props.introMessage && (
|
||||||
|
<Text component="p">
|
||||||
|
<Msg msgKey={this.props.introMessage} />
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</TextContent>
|
||||||
|
</SplitItem>
|
||||||
|
{this.props.onRefresh && (
|
||||||
|
<SplitItem>
|
||||||
|
<Tooltip content={<Msg msgKey="refreshPage" />}>
|
||||||
|
<Button
|
||||||
|
aria-label={Msg.localize('refreshPage')}
|
||||||
|
id="refresh-page"
|
||||||
|
variant="link"
|
||||||
|
onClick={this.props.onRefresh}
|
||||||
|
icon={<SyncAltIcon />}
|
||||||
|
>
|
||||||
|
<Msg msgKey="refresh" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GridItem>
|
</SplitItem>
|
||||||
}
|
)}
|
||||||
{this.props.introMessage && <GridItem span={12}> <Msg msgKey={this.props.introMessage}/></GridItem>}
|
</Split>
|
||||||
</Grid>
|
</PageSection>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="pf-c-page__main-section pf-m-no-padding-mobile">
|
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</section>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ActionGroup, Button, Form, FormGroup, TextInput, Grid, GridItem, Expandable} from '@patternfly/react-core';
|
import { ActionGroup,
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
TextInput,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
ExpandableSection,
|
||||||
|
ValidatedOptions,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Text,
|
||||||
|
TextVariants,
|
||||||
|
TextContent
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import { HttpResponse } from '../../account-service/account.service';
|
import { HttpResponse } from '../../account-service/account.service';
|
||||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||||
|
@ -144,27 +158,46 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const fields: FormFields = this.state.formFields;
|
const fields: FormFields = this.state.formFields;
|
||||||
return (
|
return (
|
||||||
<ContentPage title="personalInfoHtmlTitle"
|
<ContentPage
|
||||||
introMessage="personalSubMessage">
|
title="personalInfoHtmlTitle"
|
||||||
<Form isHorizontal onSubmit={event => this.handleSubmit(event)}>
|
introMessage="personalSubMessage"
|
||||||
{!this.isRegistrationEmailAsUsername &&
|
>
|
||||||
|
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||||
|
<TextContent className="pf-u-mb-lg">
|
||||||
|
<Text component={TextVariants.small}>
|
||||||
|
{Msg.localize('allFieldsRequired')}
|
||||||
|
</Text>
|
||||||
|
</TextContent>
|
||||||
|
<Form
|
||||||
|
onSubmit={(event) => this.handleSubmit(event)}
|
||||||
|
className="personal-info-form"
|
||||||
|
>
|
||||||
|
{!this.isRegistrationEmailAsUsername && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={Msg.localize('username')}
|
label={Msg.localize("username")}
|
||||||
isRequired
|
|
||||||
fieldId="user-name"
|
fieldId="user-name"
|
||||||
helperTextInvalid={this.state.errors.username}
|
helperTextInvalid={this.state.errors.username}
|
||||||
isValid={this.state.errors.username === ''}
|
validated={
|
||||||
|
this.state.errors.username !== ""
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{this.isEditUserNameAllowed && <this.UsernameInput />}
|
{this.isEditUserNameAllowed && <this.UsernameInput />}
|
||||||
{!this.isEditUserNameAllowed && <this.RestrictedUsernameInput />}
|
{!this.isEditUserNameAllowed && (
|
||||||
|
<this.RestrictedUsernameInput />
|
||||||
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
}
|
)}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={Msg.localize('email')}
|
label={Msg.localize("email")}
|
||||||
isRequired
|
|
||||||
fieldId="email-address"
|
fieldId="email-address"
|
||||||
helperTextInvalid={this.state.errors.email}
|
helperTextInvalid={this.state.errors.email}
|
||||||
isValid={this.state.errors.email === ''}
|
validated={
|
||||||
|
this.state.errors.email !== ""
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -174,16 +207,22 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
maxLength={254}
|
maxLength={254}
|
||||||
value={fields.email}
|
value={fields.email}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
isValid={this.state.errors.email === ''}
|
validated={
|
||||||
>
|
this.state.errors.email !== ""
|
||||||
</TextInput>
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
></TextInput>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={Msg.localize('firstName')}
|
label={Msg.localize("firstName")}
|
||||||
isRequired
|
|
||||||
fieldId="first-name"
|
fieldId="first-name"
|
||||||
helperTextInvalid={this.state.errors.firstName}
|
helperTextInvalid={this.state.errors.firstName}
|
||||||
isValid={this.state.errors.firstName === ''}
|
validated={
|
||||||
|
this.state.errors.firstName !== ""
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -193,16 +232,22 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
maxLength={254}
|
maxLength={254}
|
||||||
value={fields.firstName}
|
value={fields.firstName}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
isValid={this.state.errors.firstName === ''}
|
validated={
|
||||||
>
|
this.state.errors.firstName !== ""
|
||||||
</TextInput>
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
></TextInput>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={Msg.localize('lastName')}
|
label={Msg.localize("lastName")}
|
||||||
isRequired
|
|
||||||
fieldId="last-name"
|
fieldId="last-name"
|
||||||
helperTextInvalid={this.state.errors.lastName}
|
helperTextInvalid={this.state.errors.lastName}
|
||||||
isValid={this.state.errors.lastName === ''}
|
validated={
|
||||||
|
this.state.errors.lastName !== ""
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -212,35 +257,52 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
maxLength={254}
|
maxLength={254}
|
||||||
value={fields.lastName}
|
value={fields.lastName}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
isValid={this.state.errors.lastName === ''}
|
validated={
|
||||||
>
|
this.state.errors.lastName !== ""
|
||||||
</TextInput>
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
></TextInput>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{features.isInternationalizationEnabled && <FormGroup
|
{features.isInternationalizationEnabled && (
|
||||||
label={Msg.localize('selectLocale')}
|
<FormGroup
|
||||||
|
label={Msg.localize("selectLocale")}
|
||||||
isRequired
|
isRequired
|
||||||
fieldId="locale"
|
fieldId="locale"
|
||||||
>
|
>
|
||||||
<LocaleSelector id="locale-selector"
|
<LocaleSelector
|
||||||
value={fields.attributes!.locale || ''}
|
id="locale-selector"
|
||||||
onChange={value => this.setState({
|
value={fields.attributes!.locale || ""}
|
||||||
|
onChange={(value) =>
|
||||||
|
this.setState({
|
||||||
errors: this.state.errors,
|
errors: this.state.errors,
|
||||||
formFields: { ...this.state.formFields, attributes: { ...this.state.formFields.attributes, locale: [value] }}
|
formFields: {
|
||||||
})}
|
...this.state.formFields,
|
||||||
|
attributes: {
|
||||||
|
...this.state.formFields.attributes,
|
||||||
|
locale: [value],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormGroup>}
|
</FormGroup>
|
||||||
|
)}
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
id="save-btn"
|
id="save-btn"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
isDisabled={Object.values(this.state.errors).filter(e => e !== '').length !== 0}
|
isDisabled={
|
||||||
|
Object.values(this.state.errors).filter((e) => e !== "")
|
||||||
|
.length !== 0
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Msg msgKey="doSave" />
|
<Msg msgKey="doSave" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
id="cancel-btn"
|
id="cancel-btn"
|
||||||
variant="secondary"
|
variant="link"
|
||||||
onClick={this.handleCancel}
|
onClick={this.handleCancel}
|
||||||
>
|
>
|
||||||
<Msg msgKey="doCancel" />
|
<Msg msgKey="doCancel" />
|
||||||
|
@ -248,10 +310,10 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{ this.isDeleteAccountAllowed &&
|
{this.isDeleteAccountAllowed && (
|
||||||
<div id="delete-account" style={{ marginTop: "30px" }}>
|
<div id="delete-account" style={{ marginTop: "30px" }}>
|
||||||
<Expandable toggleText={Msg.localize('deleteAccount')}>
|
<ExpandableSection toggleText="Delete Account">
|
||||||
<Grid gutter={"sm"}>
|
<Grid hasGutter>
|
||||||
<GridItem span={6}>
|
<GridItem span={6}>
|
||||||
<p>
|
<p>
|
||||||
<Msg msgKey="deleteAccountWarning" />
|
<Msg msgKey="deleteAccountWarning" />
|
||||||
|
@ -260,16 +322,23 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
<GridItem span={4}>
|
<GridItem span={4}>
|
||||||
<KeycloakContext.Consumer>
|
<KeycloakContext.Consumer>
|
||||||
{(keycloak: KeycloakService) => (
|
{(keycloak: KeycloakService) => (
|
||||||
<Button id="delete-account-btn" variant="danger" onClick={() => this.handleDelete(keycloak)} className="delete-button"><Msg msgKey="doDelete" /></Button>
|
<Button
|
||||||
|
id="delete-account-btn"
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => this.handleDelete(keycloak)}
|
||||||
|
className="delete-button"
|
||||||
|
>
|
||||||
|
<Msg msgKey="doDelete" />
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</KeycloakContext.Consumer>
|
</KeycloakContext.Consumer>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem span={2}>
|
<GridItem span={2}></GridItem>
|
||||||
</GridItem>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</ExpandableSection>
|
||||||
</Expandable>
|
</div>
|
||||||
</div>}
|
)}
|
||||||
|
</PageSection>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -283,14 +352,14 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
||||||
maxLength={254}
|
maxLength={254}
|
||||||
value={this.state.formFields.username}
|
value={this.state.formFields.username}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
isValid={this.state.errors.username === ''}
|
validated={this.state.errors.username !== '' ? ValidatedOptions.error : ValidatedOptions.default}
|
||||||
>
|
>
|
||||||
</TextInput>
|
</TextInput>
|
||||||
);
|
);
|
||||||
|
|
||||||
private RestrictedUsernameInput = () => (
|
private RestrictedUsernameInput = () => (
|
||||||
<TextInput
|
<TextInput
|
||||||
isDisabled
|
isReadOnly
|
||||||
type="text"
|
type="text"
|
||||||
id="user-name"
|
id="user-name"
|
||||||
name="username"
|
name="username"
|
||||||
|
|
|
@ -23,12 +23,12 @@ import {Msg} from '../../widgets/Msg';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Title,
|
Title,
|
||||||
TitleLevel,
|
|
||||||
Button,
|
Button,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
EmptyStateVariant,
|
EmptyStateVariant,
|
||||||
EmptyStateIcon,
|
EmptyStateIcon,
|
||||||
EmptyStateBody
|
EmptyStateBody,
|
||||||
|
TitleSizes
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { PassportIcon } from '@patternfly/react-icons';
|
import { PassportIcon } from '@patternfly/react-icons';
|
||||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||||
|
@ -67,7 +67,7 @@ class ApplicationInitiatedActionPage extends React.Component<AppInitiatedActionP
|
||||||
return (
|
return (
|
||||||
<EmptyState variant={EmptyStateVariant.full}>
|
<EmptyState variant={EmptyStateVariant.full}>
|
||||||
<EmptyStateIcon icon={PassportIcon} />
|
<EmptyStateIcon icon={PassportIcon} />
|
||||||
<Title headingLevel={TitleLevel.h5} size="lg">
|
<Title headingLevel="h5" size={TitleSizes.lg}>
|
||||||
<Msg msgKey={this.props.pageDef.label} params={this.props.pageDef.labelParams}/>
|
<Msg msgKey={this.props.pageDef.label} params={this.props.pageDef.labelParams}/>
|
||||||
</Title>
|
</Title>
|
||||||
<EmptyStateBody>
|
<EmptyStateBody>
|
||||||
|
|
|
@ -24,12 +24,23 @@ import {
|
||||||
DataListToggle,
|
DataListToggle,
|
||||||
DataListContent,
|
DataListContent,
|
||||||
DataListItemCells,
|
DataListItemCells,
|
||||||
|
DescriptionList,
|
||||||
|
DescriptionListTerm,
|
||||||
|
DescriptionListGroup,
|
||||||
|
DescriptionListDescription,
|
||||||
Grid,
|
Grid,
|
||||||
GridItem,
|
GridItem,
|
||||||
Button,
|
Button,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
|
SplitItem,
|
||||||
|
Split,
|
||||||
|
TextContent
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import { InfoAltIcon, CheckIcon, BuilderImageIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
import { InfoAltIcon, CheckIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||||
import { ContentPage } from '../ContentPage';
|
import { ContentPage } from '../ContentPage';
|
||||||
import { ContinueCancelModal } from '../../widgets/ContinueCancelModal';
|
import { ContinueCancelModal } from '../../widgets/ContinueCancelModal';
|
||||||
import { HttpResponse } from '../../account-service/account.service';
|
import { HttpResponse } from '../../account-service/account.service';
|
||||||
|
@ -118,12 +129,18 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<ContentPage title={Msg.localize('applicationsPageTitle')}>
|
<ContentPage
|
||||||
<DataList id="applications-list" aria-label={Msg.localize('applicationsPageTitle')} isCompact>
|
title={Msg.localize('applicationsPageTitle')}
|
||||||
|
introMessage="Manage your application permissions."
|
||||||
|
>
|
||||||
|
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||||
|
|
||||||
|
<Stack hasGutter>
|
||||||
|
<DataList id="applications-list" aria-label={Msg.localize('applicationsPageTitle')}>
|
||||||
<DataListItem id="applications-list-header" aria-labelledby="Columns names">
|
<DataListItem id="applications-list-header" aria-labelledby="Columns names">
|
||||||
<DataListItemRow>
|
<DataListItemRow>
|
||||||
// invisible toggle allows headings to line up properly
|
// invisible toggle allows headings to line up properly
|
||||||
<span style={{ visibility: 'hidden' }}>
|
<span style={{ visibility: 'hidden', height: 55 }}>
|
||||||
<DataListToggle
|
<DataListToggle
|
||||||
isExpanded={false}
|
isExpanded={false}
|
||||||
id='applications-list-header-invisible-toggle'
|
id='applications-list-header-invisible-toggle'
|
||||||
|
@ -132,13 +149,13 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
</span>
|
</span>
|
||||||
<DataListItemCells
|
<DataListItemCells
|
||||||
dataListCells={[
|
dataListCells={[
|
||||||
<DataListCell key='applications-list-client-id-header' width={2}>
|
<DataListCell key='applications-list-client-id-header' width={2} className="pf-u-pt-md">
|
||||||
<strong><Msg msgKey='applicationName' /></strong>
|
<strong><Msg msgKey='applicationName' /></strong>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key='applications-list-app-type-header' width={2}>
|
<DataListCell key='applications-list-app-type-header' width={2} className="pf-u-pt-md">
|
||||||
<strong><Msg msgKey='applicationType' /></strong>
|
<strong><Msg msgKey='applicationType' /></strong>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key='applications-list-status' width={2}>
|
<DataListCell key='applications-list-status' width={2} className="pf-u-pt-md">
|
||||||
<strong><Msg msgKey='status' /></strong>
|
<strong><Msg msgKey='status' /></strong>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
]}
|
]}
|
||||||
|
@ -148,7 +165,7 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
{this.state.applications.map((application: Application, appIndex: number) => {
|
{this.state.applications.map((application: Application, appIndex: number) => {
|
||||||
return (
|
return (
|
||||||
<DataListItem id={this.elementId("client-id", application)} key={'application-' + appIndex} aria-labelledby="applications-list" isExpanded={this.state.isRowOpen[appIndex]}>
|
<DataListItem id={this.elementId("client-id", application)} key={'application-' + appIndex} aria-labelledby="applications-list" isExpanded={this.state.isRowOpen[appIndex]}>
|
||||||
<DataListItemRow>
|
<DataListItemRow className="pf-u-align-items-center">
|
||||||
<DataListToggle
|
<DataListToggle
|
||||||
onClick={() => this.onToggle(appIndex)}
|
onClick={() => this.onToggle(appIndex)}
|
||||||
isExpanded={this.state.isRowOpen[appIndex]}
|
isExpanded={this.state.isRowOpen[appIndex]}
|
||||||
|
@ -156,9 +173,10 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
aria-controls={this.elementId("expandable", application)}
|
aria-controls={this.elementId("expandable", application)}
|
||||||
/>
|
/>
|
||||||
<DataListItemCells
|
<DataListItemCells
|
||||||
|
className="pf-u-align-items-center"
|
||||||
dataListCells={[
|
dataListCells={[
|
||||||
<DataListCell id={this.elementId('name', application)} width={2} key={'app-' + appIndex}>
|
<DataListCell id={this.elementId('name', application)} width={2} key={'app-' + appIndex}>
|
||||||
<Button component="a" variant="link" onClick={() => window.open(application.effectiveUrl)}>
|
<Button className="pf-u-pl-0 title-case" component="a" variant="link" onClick={() => window.open(application.effectiveUrl)}>
|
||||||
{application.clientName || application.clientId} <ExternalLinkAltIcon/>
|
{application.clientName || application.clientId} <ExternalLinkAltIcon/>
|
||||||
</Button>
|
</Button>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
|
@ -172,36 +190,46 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
|
|
||||||
<DataListContent
|
<DataListContent
|
||||||
noPadding={false}
|
className="pf-u-pl-35xl"
|
||||||
|
hasNoPadding={false}
|
||||||
aria-label={Msg.localize('applicationDetails')}
|
aria-label={Msg.localize('applicationDetails')}
|
||||||
id={this.elementId("expandable", application)}
|
id={this.elementId("expandable", application)}
|
||||||
isHidden={!this.state.isRowOpen[appIndex]}
|
isHidden={!this.state.isRowOpen[appIndex]}
|
||||||
>
|
>
|
||||||
<Grid sm={6} md={6} lg={6}>
|
<DescriptionList>
|
||||||
<div className='pf-c-content'>
|
<DescriptionListGroup>
|
||||||
<GridItem><strong>{Msg.localize('client') + ': '}</strong> {application.clientId}</GridItem>
|
<DescriptionListTerm>{Msg.localize('client')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{application.clientId}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
{application.description &&
|
{application.description &&
|
||||||
<GridItem><strong>{Msg.localize('description') + ': '}</strong> {application.description}</GridItem>
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('description')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{application.description}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
}
|
}
|
||||||
{application.effectiveUrl &&
|
{application.effectiveUrl &&
|
||||||
<GridItem><strong>URL: </strong> <span id={this.elementId('effectiveurl', application)}>{application.effectiveUrl.split('"')}</span></GridItem>
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>URL</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{application.effectiveUrl.split('"')}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
}
|
}
|
||||||
{application.consent &&
|
{application.consent &&
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<GridItem span={12}>
|
<DescriptionListGroup>
|
||||||
<strong>Has access to:</strong>
|
<DescriptionListTerm>Has access to</DescriptionListTerm>
|
||||||
</GridItem>
|
|
||||||
{application.consent.grantedScopes.map((scope: GrantedScope, scopeIndex: number) => {
|
{application.consent.grantedScopes.map((scope: GrantedScope, scopeIndex: number) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={'scope-' + scopeIndex} >
|
<React.Fragment key={'scope-' + scopeIndex} >
|
||||||
<GridItem offset={1}><CheckIcon /> {scope.name}</GridItem>
|
<DescriptionListDescription><CheckIcon /> {scope.name}</DescriptionListDescription>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{application.tosUri && <GridItem><strong>{Msg.localize('termsOfService') + ': '}</strong>{application.tosUri}</GridItem>}
|
</DescriptionListGroup>
|
||||||
{application.policyUri && <GridItem><strong>{Msg.localize('policy') + ': '}</strong>{application.policyUri}</GridItem>}
|
<DescriptionListGroup>
|
||||||
<GridItem><strong>{Msg.localize('accessGrantedOn') + ': '}</strong>
|
<DescriptionListTerm>{Msg.localize('accessGrantedOn') + ': '}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
{new Intl.DateTimeFormat(locale, {
|
{new Intl.DateTimeFormat(locale, {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
|
@ -210,14 +238,13 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
second: 'numeric'
|
second: 'numeric'
|
||||||
}).format(application.consent.createDate)}
|
}).format(application.consent.createDate)}
|
||||||
</GridItem>
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</DescriptionList>
|
||||||
{application.logoUri && <div className='pf-c-content'><img src={application.logoUri} /></div> }
|
|
||||||
</Grid>
|
|
||||||
{(application.consent || application.offlineAccess) &&
|
{(application.consent || application.offlineAccess) &&
|
||||||
<Grid gutter='sm'>
|
<Grid hasGutter>
|
||||||
<hr />
|
<hr />
|
||||||
<GridItem>
|
<GridItem>
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -239,6 +266,8 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</DataList>
|
</DataList>
|
||||||
|
</Stack>
|
||||||
|
</PageSection>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,28 +21,30 @@ import { AccountServiceContext } from '../../account-service/AccountServiceConte
|
||||||
import TimeUtil from '../../util/TimeUtil';
|
import TimeUtil from '../../util/TimeUtil';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Bullseye,
|
Button,
|
||||||
DataList,
|
DataList,
|
||||||
DataListItem,
|
DataListItem,
|
||||||
DataListItemRow,
|
DataListItemRow,
|
||||||
DataListCell,
|
DataListContent,
|
||||||
DataListItemCells,
|
DescriptionList,
|
||||||
|
DescriptionListTerm,
|
||||||
|
DescriptionListDescription,
|
||||||
|
DescriptionListGroup,
|
||||||
Grid,
|
Grid,
|
||||||
GridItem,
|
GridItem,
|
||||||
Stack,
|
Label,
|
||||||
StackItem
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
SplitItem,
|
||||||
|
Split
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AmazonIcon,
|
DesktopIcon,
|
||||||
ChromeIcon,
|
MobileAltIcon,
|
||||||
EdgeIcon,
|
SyncAltIcon,
|
||||||
FirefoxIcon,
|
|
||||||
GlobeIcon,
|
|
||||||
InternetExplorerIcon,
|
|
||||||
OperaIcon,
|
|
||||||
SafariIcon,
|
|
||||||
YandexInternationalIcon,
|
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
import {Msg} from '../../widgets/Msg';
|
import {Msg} from '../../widgets/Msg';
|
||||||
|
@ -161,22 +163,15 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
||||||
return TimeUtil.format(time * 1000);
|
return TimeUtil.format(time * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private elementId(item: string, session: Session): string {
|
private elementId(item: string, session: Session, element: string='session'): string {
|
||||||
return `session-${session.id.substring(0,7)}-${item}`;
|
return `${element}-${session.id.substring(0,7)}-${item}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findBrowserIcon(session: Session): React.ReactNode {
|
private findDeviceTypeIcon(session: Session, device: Device): React.ReactNode {
|
||||||
const browserName: string = session.browser.toLowerCase();
|
const deviceType: boolean = device.mobile;
|
||||||
if (browserName.includes("chrom")) return (<ChromeIcon id={this.elementId('icon-chrome', session)} size='lg'/>); // chrome or chromium
|
if (deviceType === true) return (<MobileAltIcon id={this.elementId('icon-mobile', session, 'device')} />);
|
||||||
if (browserName.includes("firefox")) return (<FirefoxIcon id={this.elementId('icon-firefox', session)} size='lg'/>);
|
|
||||||
if (browserName.includes("edge")) return (<EdgeIcon id={this.elementId('icon-edge', session)} size='lg'/>);
|
|
||||||
if (browserName.startsWith("ie/")) return (<InternetExplorerIcon id={this.elementId('icon-ie', session)} size='lg'/>);
|
|
||||||
if (browserName.includes("safari")) return (<SafariIcon id={this.elementId('icon-safari', session)} size='lg'/>);
|
|
||||||
if (browserName.includes("opera")) return (<OperaIcon id={this.elementId('icon-opera', session)} size='lg'/>);
|
|
||||||
if (browserName.includes("yandex")) return (<YandexInternationalIcon id={this.elementId('icon-yandex', session)} size='lg'/>);
|
|
||||||
if (browserName.includes("amazon")) return (<AmazonIcon id={this.elementId('icon-amazon', session)} size='lg'/>);
|
|
||||||
|
|
||||||
return (<GlobeIcon id={this.elementId('icon-default', session)} size='lg'/>);
|
return (<DesktopIcon id={this.elementId('icon-desktop', session, 'device')} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findOS(device: Device): string {
|
private findOS(device: Device): string {
|
||||||
|
@ -220,74 +215,62 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentPage title="device-activity" onRefresh={this.fetchDevices.bind(this)}>
|
<ContentPage
|
||||||
<Stack gutter="md">
|
title="device-activity"
|
||||||
<StackItem isFilled>
|
introMessage="signedInDevicesExplanation"
|
||||||
<DataList aria-label={Msg.localize('signedInDevices')}>
|
>
|
||||||
<DataListItem key="SignedInDevicesHeader" aria-labelledby="signedInDevicesTitle" isExpanded={false}>
|
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||||
<DataListItemRow>
|
<Split hasGutter className="pf-u-mb-lg">
|
||||||
<DataListItemCells
|
<SplitItem isFilled>
|
||||||
dataListCells={[
|
<div id="signedInDevicesTitle" className="pf-c-content"><Title headingLevel="h2" size="xl"><Msg msgKey="signedInDevices"/></Title></div>
|
||||||
<DataListCell key='signedInDevicesTitle' width={4}>
|
</SplitItem>
|
||||||
<div id="signedInDevicesTitle" className="pf-c-content">
|
<SplitItem>
|
||||||
<h2><Msg msgKey="signedInDevices"/></h2>
|
<Tooltip content={<Msg msgKey="refreshPage" />}>
|
||||||
<p>
|
<Button
|
||||||
<Msg msgKey="signedInDevicesExplanation"/>
|
aria-describedby="refresh page"
|
||||||
</p>
|
id="refresh-page"
|
||||||
</div>
|
variant="link"
|
||||||
</DataListCell>,
|
onClick={this.fetchDevices.bind(this)}
|
||||||
|
icon={<SyncAltIcon />}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem>
|
||||||
<KeycloakContext.Consumer>
|
<KeycloakContext.Consumer>
|
||||||
{ (keycloak: KeycloakService) => (
|
{ (keycloak: KeycloakService) => (
|
||||||
<DataListCell key='signOutAllButton' width={1}>
|
this.isShowSignOutAll(this.state.devices) &&
|
||||||
{this.isShowSignOutAll(this.state.devices) &&
|
|
||||||
<ContinueCancelModal buttonTitle='signOutAllDevices'
|
<ContinueCancelModal buttonTitle='signOutAllDevices'
|
||||||
buttonId='sign-out-all'
|
buttonId='sign-out-all'
|
||||||
modalTitle='signOutAllDevices'
|
modalTitle='signOutAllDevices'
|
||||||
modalMessage='signOutAllDevicesWarning'
|
modalMessage='signOutAllDevicesWarning'
|
||||||
onContinue={() => this.signOutAll(keycloak)}
|
onContinue={() => this.signOutAll(keycloak)}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
</DataListCell>
|
|
||||||
)}
|
)}
|
||||||
</KeycloakContext.Consumer>
|
</KeycloakContext.Consumer>
|
||||||
]}
|
</SplitItem>
|
||||||
/>
|
</Split>
|
||||||
</DataListItemRow>
|
<DataList className="signed-in-device-list" aria-label={Msg.localize('signedInDevices')}>
|
||||||
</DataListItem>
|
<DataListItem aria-labelledby='sessions' id='device-activity-sessions'>
|
||||||
|
|
||||||
<DataListItem aria-labelledby='sessions'>
|
|
||||||
<DataListItemRow>
|
|
||||||
<Grid gutter='sm'>
|
|
||||||
<GridItem span={12} /> {/* <-- top spacing */}
|
|
||||||
{this.state.devices.map((device: Device, deviceIndex: number) => {
|
{this.state.devices.map((device: Device, deviceIndex: number) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{device.sessions.map((session: Session, sessionIndex: number) => {
|
{device.sessions.map((session: Session, sessionIndex: number) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={'device-' + deviceIndex + '-session-' + sessionIndex}>
|
<React.Fragment key={'device-' + deviceIndex + '-session-' + sessionIndex}>
|
||||||
|
<DataListItemRow>
|
||||||
<GridItem md={3}>
|
<DataListContent aria-label="device-sessions-content" isHidden={false} className="pf-u-flex-grow-1">
|
||||||
<Stack>
|
<Grid className="signed-in-device-grid" hasGutter>
|
||||||
<StackItem isFilled={false}>
|
<GridItem className="device-icon" span={1} rowSpan={2}>
|
||||||
<Bullseye>{this.findBrowserIcon(session)}</Bullseye>
|
<span>{this.findDeviceTypeIcon(session, device)}</span>
|
||||||
</StackItem>
|
|
||||||
<StackItem isFilled={false}>
|
|
||||||
<Bullseye id={this.elementId('ip', session)}>{session.ipAddress}</Bullseye>
|
|
||||||
</StackItem>
|
|
||||||
{session.current &&
|
|
||||||
<StackItem isFilled={false}>
|
|
||||||
<Bullseye id={this.elementId('current-badge', session)}><strong className='pf-c-badge pf-m-read'><Msg msgKey="currentSession" /></strong></Bullseye>
|
|
||||||
</StackItem>
|
|
||||||
}
|
|
||||||
</Stack>
|
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem md={9}>
|
<GridItem sm={8} md={9} span={10}>
|
||||||
{!session.browser.toLowerCase().includes('unknown') &&
|
<span id={this.elementId('browser', session)} className="pf-u-mr-md">{this.findOS(device)} {this.findOSVersion(device)} / {session.browser}</span>
|
||||||
<p id={this.elementId('browser', session)}><strong>{session.browser} / {this.findOS(device)} {this.findOSVersion(device)}</strong></p>}
|
{session.current &&
|
||||||
<p id={this.elementId('last-access', session)}><strong>{Msg.localize('lastAccessedOn')}</strong> {this.time(session.lastAccess)}</p>
|
<Label color="green"><Msg msgKey="currentSession" /></Label>}
|
||||||
<p id={this.elementId('clients', session)}><strong>{Msg.localize('clients')}</strong> {this.makeClientsString(session.clients)}</p>
|
</GridItem>
|
||||||
<p id={this.elementId('started', session)}><strong>{Msg.localize('startedAt')}</strong> {this.time(session.started)}</p>
|
<GridItem className="pf-u-text-align-right" sm={3} md={2} span={1}>
|
||||||
<p id={this.elementId('expires', session)}><strong>{Msg.localize('expiresAt')}</strong> {this.time(session.expires)}</p>
|
|
||||||
{!session.current &&
|
{!session.current &&
|
||||||
<ContinueCancelModal buttonTitle='doSignOut'
|
<ContinueCancelModal buttonTitle='doSignOut'
|
||||||
buttonId={this.elementId('sign-out', session)}
|
buttonId={this.elementId('sign-out', session)}
|
||||||
|
@ -297,23 +280,43 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
||||||
onContinue={() => this.signOutSession(device, session)}
|
onContinue={() => this.signOutSession(device, session)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
</GridItem>
|
</GridItem>
|
||||||
|
<GridItem span={11}>
|
||||||
|
<DescriptionList columnModifier={{ sm: '2Col', lg: '3Col' }}>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('ipAddress')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{session.ipAddress}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('lastAccessedOn')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{this.time(session.lastAccess)}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('clients')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{this.makeClientsString(session.clients)}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('started')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{this.time(session.started)}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{Msg.localize('expires')}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>{this.time(session.expires)}</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
</DescriptionList>
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
</DataListContent>
|
||||||
|
</DataListItemRow>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<GridItem span={12} /> {/* <-- bottom spacing */}
|
|
||||||
</Grid>
|
|
||||||
</DataListItemRow>
|
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
</DataList>
|
</DataList>
|
||||||
</StackItem>
|
</PageSection>
|
||||||
|
|
||||||
</Stack>
|
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,35 +18,31 @@ import * as React from 'react';
|
||||||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
Button,
|
||||||
DataList,
|
DataList,
|
||||||
DataListAction,
|
DataListAction,
|
||||||
DataListItemCells,
|
DataListItemCells,
|
||||||
DataListCell,
|
DataListCell,
|
||||||
DataListItemRow,
|
DataListItemRow,
|
||||||
|
Divider,
|
||||||
|
Label,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Split,
|
||||||
|
SplitItem,
|
||||||
Stack,
|
Stack,
|
||||||
StackItem,
|
StackItem,
|
||||||
Title,
|
Title,
|
||||||
TitleLevel,
|
|
||||||
DataListItem,
|
DataListItem,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BitbucketIcon,
|
BitbucketIcon,
|
||||||
CubeIcon,
|
CubeIcon,
|
||||||
FacebookIcon,
|
|
||||||
GithubIcon,
|
|
||||||
GitlabIcon,
|
GitlabIcon,
|
||||||
GoogleIcon,
|
|
||||||
InstagramIcon,
|
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
LinkedinIcon,
|
|
||||||
MicrosoftIcon,
|
|
||||||
OpenshiftIcon,
|
OpenshiftIcon,
|
||||||
PaypalIcon,
|
PaypalIcon,
|
||||||
StackOverflowIcon,
|
|
||||||
TwitterIcon,
|
|
||||||
UnlinkIcon
|
UnlinkIcon
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
@ -128,25 +124,26 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentPage title={Msg.localize('linkedAccountsTitle')} introMessage={Msg.localize('linkedAccountsIntroMessage')}>
|
<ContentPage title={Msg.localize('linkedAccountsTitle')} introMessage={Msg.localize('linkedAccountsIntroMessage')}>
|
||||||
<Stack gutter='md'>
|
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||||
<StackItem isFilled>
|
<Stack hasGutter>
|
||||||
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
<StackItem>
|
||||||
|
<Title headingLevel="h2" className="pf-u-mb-lg" size='xl'>
|
||||||
<Msg msgKey='linkedLoginProviders'/>
|
<Msg msgKey='linkedLoginProviders'/>
|
||||||
</Title>
|
</Title>
|
||||||
<DataList id="linked-idps" aria-label='foo'>
|
<DataList id="linked-idps" aria-label={Msg.localize('linkedLoginProviders')}>
|
||||||
{this.makeRows(this.state.linkedAccounts, true)}
|
{this.makeRows(this.state.linkedAccounts, true)}
|
||||||
</DataList>
|
</DataList>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
<StackItem isFilled/>
|
<StackItem>
|
||||||
<StackItem isFilled>
|
<Title headingLevel="h2" className="pf-u-mt-xl pf-u-mb-lg" size='xl'>
|
||||||
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
|
||||||
<Msg msgKey='unlinkedLoginProviders'/>
|
<Msg msgKey='unlinkedLoginProviders'/>
|
||||||
</Title>
|
</Title>
|
||||||
<DataList id="unlinked-idps" aria-label='foo'>
|
<DataList id="unlinked-idps" aria-label={Msg.localize('unlinkedLoginProviders')}>
|
||||||
{this.makeRows(this.state.unLinkedAccounts, false)}
|
{this.makeRows(this.state.unLinkedAccounts, false)}
|
||||||
</DataList>
|
</DataList>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</PageSection>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -160,10 +157,10 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataListItem key='emptyItem' aria-labelledby="empty-item">
|
<DataListItem key='emptyItem' aria-labelledby={Msg.localize('isEmptyMessage')}>
|
||||||
<DataListItemRow key='emptyRow'>
|
<DataListItemRow key='emptyRow'>
|
||||||
<DataListItemCells dataListCells={[
|
<DataListItemCells dataListCells={[
|
||||||
<DataListCell key='empty'><strong>{isEmptyMessage}</strong></DataListCell>
|
<DataListCell key='empty'>{isEmptyMessage}</DataListCell>
|
||||||
]}/>
|
]}/>
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
|
@ -179,15 +176,28 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
||||||
<> {
|
<> {
|
||||||
|
|
||||||
accounts.map( (account: LinkedAccount) => (
|
accounts.map( (account: LinkedAccount) => (
|
||||||
<DataListItem id={`${account.providerAlias}-idp`} key={account.providerName} aria-labelledby="simple-item1">
|
<DataListItem id={`${account.providerAlias}-idp`} key={account.providerName} aria-labelledby={Msg.localize('linkedAccountsTitle')}>
|
||||||
<DataListItemRow key={account.providerName}>
|
<DataListItemRow key={account.providerName}>
|
||||||
<DataListItemCells
|
<DataListItemCells
|
||||||
dataListCells={[
|
dataListCells={[
|
||||||
<DataListCell key='idp'><Stack><StackItem isFilled>{this.findIcon(account)}</StackItem><StackItem id={`${account.providerAlias}-idp-name`} isFilled><h2><strong>{account.displayName}</strong></h2></StackItem></Stack></DataListCell>,
|
<DataListCell key='idp'>
|
||||||
<DataListCell key='badge'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-badge`} isFilled>{this.badge(account)}</StackItem></Stack></DataListCell>,
|
<Split>
|
||||||
<DataListCell key='username'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-username`} isFilled>{account.linkedUsername}</StackItem></Stack></DataListCell>,
|
<SplitItem className="pf-u-mr-sm">{this.findIcon(account)}</SplitItem>
|
||||||
|
<SplitItem className="pf-u-my-xs" isFilled><span id={`${account.providerAlias}-idp-name`}>{account.displayName}</span></SplitItem>
|
||||||
|
</Split>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key='label'>
|
||||||
|
<Split>
|
||||||
|
<SplitItem className="pf-u-my-xs" isFilled><span id={`${account.providerAlias}-idp-label`}>{this.label(account)}</span></SplitItem>
|
||||||
|
</Split>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell key='username' width={5}>
|
||||||
|
<Split>
|
||||||
|
<SplitItem className="pf-u-my-xs" isFilled><span id={`${account.providerAlias}-idp-username`}>{account.linkedUsername}</span></SplitItem>
|
||||||
|
</Split>
|
||||||
|
</DataListCell>,
|
||||||
]}/>
|
]}/>
|
||||||
<DataListAction aria-labelledby='foo' aria-label='foo action' id='setPasswordAction'>
|
<DataListAction aria-labelledby={Msg.localize('link')} aria-label={Msg.localize('unLink')} id='setPasswordAction'>
|
||||||
{isLinked && <Button id={`${account.providerAlias}-idp-unlink`} variant='link' onClick={() => this.unLinkAccount(account)}><UnlinkIcon size='sm'/> <Msg msgKey='unLink'/></Button>}
|
{isLinked && <Button id={`${account.providerAlias}-idp-unlink`} variant='link' onClick={() => this.unLinkAccount(account)}><UnlinkIcon size='sm'/> <Msg msgKey='unLink'/></Button>}
|
||||||
{!isLinked && <Button id={`${account.providerAlias}-idp-link`} variant='link' onClick={() => this.linkAccount(account)}><LinkIcon size='sm'/> <Msg msgKey='link'/></Button>}
|
{!isLinked && <Button id={`${account.providerAlias}-idp-link`} variant='link' onClick={() => this.linkAccount(account)}><LinkIcon size='sm'/> <Msg msgKey='link'/></Button>}
|
||||||
</DataListAction>
|
</DataListAction>
|
||||||
|
@ -200,30 +210,31 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private badge(account: LinkedAccount): React.ReactNode {
|
private label(account: LinkedAccount): React.ReactNode {
|
||||||
if (account.social) {
|
if (account.social) {
|
||||||
return (<Badge><Msg msgKey='socialLogin'/></Badge>);
|
return (<Label color="blue"><Msg msgKey='socialLogin'/></Label>);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<Badge style={{backgroundColor: "green"}} ><Msg msgKey='systemDefined'/></Badge>);
|
return (<Label color="green"><Msg msgKey='systemDefined'/></Label>);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findIcon(account: LinkedAccount): React.ReactNode {
|
private findIcon(account: LinkedAccount): React.ReactNode {
|
||||||
const socialIconId = `${account.providerAlias}-idp-icon-social`;
|
const socialIconId = `${account.providerAlias}-idp-icon-social`;
|
||||||
if (account.providerName.toLowerCase().includes('github')) return (<GithubIcon id={socialIconId} size='xl'/>);
|
console.log(account);
|
||||||
if (account.providerName.toLowerCase().includes('linkedin')) return (<LinkedinIcon id={socialIconId} size='xl'/>);
|
switch (true) {
|
||||||
if (account.providerName.toLowerCase().includes('facebook')) return (<FacebookIcon id={socialIconId} size='xl'/>);
|
case account.providerName.toLowerCase().includes('bitbucket'):
|
||||||
if (account.providerName.toLowerCase().includes('google')) return (<GoogleIcon id={socialIconId} size='xl'/>);
|
return <BitbucketIcon id={socialIconId} size='lg'/>;
|
||||||
if (account.providerName.toLowerCase().includes('instagram')) return (<InstagramIcon id={socialIconId} size='xl'/>);
|
case account.providerName.toLowerCase().includes('openshift'):
|
||||||
if (account.providerName.toLowerCase().includes('microsoft')) return (<MicrosoftIcon id={socialIconId} size='xl'/>);
|
return <div className="idp-icon-social" id="openshift-idp-icon-social" />;
|
||||||
if (account.providerName.toLowerCase().includes('bitbucket')) return (<BitbucketIcon id={socialIconId} size='xl'/>);
|
case account.providerName.toLowerCase().includes('gitlab'):
|
||||||
if (account.providerName.toLowerCase().includes('twitter')) return (<TwitterIcon id={socialIconId} size='xl'/>);
|
return <GitlabIcon id={socialIconId} size='lg'/>;
|
||||||
if (account.providerName.toLowerCase().includes('openshift')) return (<OpenshiftIcon id={socialIconId} size='xl'/>);
|
case account.providerName.toLowerCase().includes('paypal'):
|
||||||
if (account.providerName.toLowerCase().includes('gitlab')) return (<GitlabIcon id={socialIconId} size='xl'/>);
|
return <PaypalIcon id={socialIconId} size='lg'/>;
|
||||||
if (account.providerName.toLowerCase().includes('paypal')) return (<PaypalIcon id={socialIconId} size='xl'/>);
|
case (account.providerName !== '' && account.social):
|
||||||
if (account.providerName.toLowerCase().includes('stackoverflow')) return (<StackOverflowIcon id={socialIconId} size='xl'/>);
|
return <div className="idp-icon-social" id={socialIconId}/>;
|
||||||
|
default:
|
||||||
return (<CubeIcon id={`${account.providerAlias}-idp-icon-default`} size='xl'/>);
|
return <CubeIcon id={`${account.providerAlias}-idp-icon-default`} size='lg'/>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,11 +22,12 @@ import {
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
TextInput,
|
TextInput,
|
||||||
InputGroup
|
InputGroup,
|
||||||
|
ModalVariant
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { OkIcon } from '@patternfly/react-icons';
|
import { OkIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
import { Resource, Permission, Scope } from './resource-model';
|
import { Resource, Permission, Permissions, Scope } from './resource-model';
|
||||||
import { Msg } from '../../widgets/Msg';
|
import { Msg } from '../../widgets/Msg';
|
||||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||||
import { ContentAlert } from '../ContentAlert';
|
import { ContentAlert } from '../ContentAlert';
|
||||||
|
@ -45,7 +46,7 @@ interface EditTheResourceState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditTheResource extends React.Component<EditTheResourceProps, EditTheResourceState> {
|
export class EditTheResource extends React.Component<EditTheResourceProps, EditTheResourceState> {
|
||||||
protected static defaultProps = { permissions: [] };
|
protected static defaultProps:Permissions = { permissions: [] };
|
||||||
static contextType = AccountServiceContext;
|
static contextType = AccountServiceContext;
|
||||||
context: React.ContextType<typeof AccountServiceContext>;
|
context: React.ContextType<typeof AccountServiceContext>;
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ export class EditTheResource extends React.Component<EditTheResourceProps, EditT
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title={'Edit the resource - ' + this.props.resource.name}
|
title={'Edit the resource - ' + this.props.resource.name}
|
||||||
isLarge
|
variant={ModalVariant.large}
|
||||||
isOpen={this.state.isOpen}
|
isOpen={this.state.isOpen}
|
||||||
onClose={this.handleToggleDialog}
|
onClose={this.handleToggleDialog}
|
||||||
actions={[
|
actions={[
|
||||||
|
|
|
@ -18,7 +18,18 @@ import * as React from 'react';
|
||||||
|
|
||||||
import parse from '../../util/ParseLink';
|
import parse from '../../util/ParseLink';
|
||||||
|
|
||||||
import { Button, Level, LevelItem, Stack, StackItem, Tab, Tabs, TextInput } from '@patternfly/react-core';
|
import {
|
||||||
|
Button,
|
||||||
|
Level,
|
||||||
|
LevelItem,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Stack,
|
||||||
|
StackItem,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TextInput
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import {HttpResponse} from '../../account-service/account.service';
|
import {HttpResponse} from '../../account-service/account.service';
|
||||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
||||||
|
@ -170,12 +181,12 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
||||||
private makeTab(eventKey: number, title: string, resources: PaginatedResources, sharedResourcesTab: boolean): React.ReactNode {
|
private makeTab(eventKey: number, title: string, resources: PaginatedResources, sharedResourcesTab: boolean): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<Tab id={title} eventKey={eventKey} title={Msg.localize(title)}>
|
<Tab id={title} eventKey={eventKey} title={Msg.localize(title)}>
|
||||||
<Stack gutter="md">
|
<Stack hasGutter>
|
||||||
<StackItem isFilled><span/></StackItem>
|
<StackItem isFilled><span/></StackItem>
|
||||||
<StackItem isFilled>
|
<StackItem isFilled>
|
||||||
<Level gutter='md'>
|
<Level hasGutter>
|
||||||
<LevelItem>
|
<LevelItem>
|
||||||
<TextInput value={this.state.nameFilter} onChange={this.handleFilterRequest} id={'filter-' + title} type="text" placeholder={Msg.localize('filterByName')} />
|
<TextInput value={this.state.nameFilter} onChange={this.handleFilterRequest} id={'filter-' + title} type="text" placeholder={Msg.localize('filterByName')} iconVariant="search"/>
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
</Level>
|
</Level>
|
||||||
</StackItem>
|
</StackItem>
|
||||||
|
@ -191,12 +202,13 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<ContentPage title="resources" onRefresh={this.fetchInitialResources.bind(this)}>
|
<ContentPage title="resources" onRefresh={this.fetchInitialResources.bind(this)}>
|
||||||
<Tabs isFilled activeKey={this.state.activeTabKey} onSelect={this.handleTabClick}>
|
<PageSection variant={PageSectionVariants.light}>
|
||||||
|
<Tabs activeKey={this.state.activeTabKey} onSelect={this.handleTabClick}>
|
||||||
{this.makeTab(0, 'myResources', this.state.myResources, false)}
|
{this.makeTab(0, 'myResources', this.state.myResources, false)}
|
||||||
{this.makeTab(1, 'sharedwithMe', this.state.sharedWithMe, true)}
|
{this.makeTab(1, 'sharedwithMe', this.state.sharedWithMe, true)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Level gutter='md'>
|
<Level hasGutter>
|
||||||
<LevelItem>
|
<LevelItem>
|
||||||
{this.hasPrevious() && <Button onClick={this.handlePreviousClick}><<Msg msgKey='previousPage'/></Button>}
|
{this.hasPrevious() && <Button onClick={this.handlePreviousClick}><<Msg msgKey='previousPage'/></Button>}
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
|
@ -209,6 +221,7 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
||||||
{this.hasNext() && <Button onClick={this.handleNextClick}><Msg msgKey='nextPage'/>></Button>}
|
{this.hasNext() && <Button onClick={this.handleNextClick}><Msg msgKey='nextPage'/>></Button>}
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
</Level>
|
</Level>
|
||||||
|
</PageSection>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ import {
|
||||||
DataListCell,
|
DataListCell,
|
||||||
Chip,
|
Chip,
|
||||||
Split,
|
Split,
|
||||||
SplitItem
|
SplitItem,
|
||||||
|
ModalVariant
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { UserCheckIcon } from '@patternfly/react-icons';
|
import { UserCheckIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ import { HttpResponse } from '../../account-service/account.service';
|
||||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||||
import { Msg } from '../../widgets/Msg';
|
import { Msg } from '../../widgets/Msg';
|
||||||
import { ContentAlert } from '../ContentAlert';
|
import { ContentAlert } from '../ContentAlert';
|
||||||
import { Resource, Scope, Permission } from './resource-model';
|
import { Resource, Scope, Permission, Permissions } from './resource-model';
|
||||||
|
|
||||||
|
|
||||||
interface PermissionRequestProps {
|
interface PermissionRequestProps {
|
||||||
|
@ -48,7 +49,7 @@ interface PermissionRequestState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PermissionRequest extends React.Component<PermissionRequestProps, PermissionRequestState> {
|
export class PermissionRequest extends React.Component<PermissionRequestProps, PermissionRequestState> {
|
||||||
protected static defaultProps = { permissions: [], row: 0 };
|
protected static defaultProps:Permissions = { permissions: [], row: 0 };
|
||||||
static contextType = AccountServiceContext;
|
static contextType = AccountServiceContext;
|
||||||
context: React.ContextType<typeof AccountServiceContext>;
|
context: React.ContextType<typeof AccountServiceContext>;
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ export class PermissionRequest extends React.Component<PermissionRequestProps, P
|
||||||
<Modal
|
<Modal
|
||||||
id={`modal-${id}`}
|
id={`modal-${id}`}
|
||||||
title={Msg.localize('permissionRequests') + ' - ' + this.props.resource.name}
|
title={Msg.localize('permissionRequests') + ' - ' + this.props.resource.name}
|
||||||
isLarge={true}
|
variant={ModalVariant.large}
|
||||||
isOpen={this.state.isOpen}
|
isOpen={this.state.isOpen}
|
||||||
onClose={this.handleToggleDialog}
|
onClose={this.handleToggleDialog}
|
||||||
actions={[
|
actions={[
|
||||||
|
@ -146,7 +147,7 @@ export class PermissionRequest extends React.Component<PermissionRequestProps, P
|
||||||
{(shareRequest.scopes as Scope[]).map((scope, j) => <Chip key={j} isReadOnly>{scope}</Chip>)}
|
{(shareRequest.scopes as Scope[]).map((scope, j) => <Chip key={j} isReadOnly>{scope}</Chip>)}
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key={`actions${i}`}>
|
<DataListCell key={`actions${i}`}>
|
||||||
<Split gutter="sm">
|
<Split hasGutter>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<Button
|
<Button
|
||||||
id={`accept-${i}-${id}`}
|
id={`accept-${i}-${id}`}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { Select, SelectOption, SelectVariant, SelectOptionObject } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant, SelectOptionObject } from '@patternfly/react-core';
|
||||||
import { Scope } from './resource-model';
|
import { Scope } from './resource-model';
|
||||||
|
import { Msg } from '../../widgets/Msg';
|
||||||
|
|
||||||
interface PermissionSelectState {
|
interface PermissionSelectState {
|
||||||
selected: ScopeValue[];
|
selected: ScopeValue[];
|
||||||
|
@ -86,19 +87,19 @@ export class PermissionSelect extends React.Component<PermissionSelectProps, Per
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span id={titleId} hidden>
|
<span id={titleId} hidden>
|
||||||
Select the permissions
|
<Msg msgKey='selectPermissions' />
|
||||||
</span>
|
</span>
|
||||||
<Select
|
<Select
|
||||||
direction={this.props.direction || 'down'}
|
direction={this.props.direction || 'down'}
|
||||||
variant={SelectVariant.typeaheadMulti}
|
variant={SelectVariant.typeaheadMulti}
|
||||||
ariaLabelTypeAhead="Select the permissions"
|
typeAheadAriaLabel={Msg.localize("selectPermissions")}
|
||||||
onToggle={this.onToggle}
|
onToggle={this.onToggle}
|
||||||
onSelect={this.onSelect}
|
onSelect={this.onSelect}
|
||||||
onClear={this.clearSelection}
|
onClear={this.clearSelection}
|
||||||
selections={selected}
|
selections={selected}
|
||||||
isExpanded={isExpanded}
|
isOpen={isExpanded}
|
||||||
ariaLabelledBy={titleId}
|
aria-labelledby={titleId}
|
||||||
placeholderText="Select the permissions"
|
placeholderText={Msg.localize("selectPermissions")}
|
||||||
>
|
>
|
||||||
{this.state.scopes}
|
{this.state.scopes}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {
|
||||||
LevelItem,
|
LevelItem,
|
||||||
Button,
|
Button,
|
||||||
DataListAction,
|
DataListAction,
|
||||||
DataListActionVisibility,
|
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownPosition,
|
DropdownPosition,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
|
@ -169,7 +168,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<DataListAction
|
<DataListAction
|
||||||
className={DataListActionVisibility.hiddenOnLg}
|
visibility={{ lg: 'hidden' }}
|
||||||
aria-labelledby="check-action-item3 check-action-action3"
|
aria-labelledby="check-action-item3 check-action-action3"
|
||||||
id="check-action-action3"
|
id="check-action-action3"
|
||||||
aria-label="Actions"
|
aria-label="Actions"
|
||||||
|
@ -246,7 +245,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
||||||
</DataListAction>
|
</DataListAction>
|
||||||
<DataListAction
|
<DataListAction
|
||||||
id={`actions-${row}`}
|
id={`actions-${row}`}
|
||||||
className={css(DataListActionVisibility.visibleOnLg, DataListActionVisibility.hidden)}
|
visibility={{ default: 'hidden', lg: 'visible' }}
|
||||||
aria-labelledby="Row actions"
|
aria-labelledby="Row actions"
|
||||||
aria-label="Actions"
|
aria-label="Actions"
|
||||||
>
|
>
|
||||||
|
@ -319,15 +318,14 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</DataListAction>
|
</DataListAction>
|
||||||
|
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
<DataListContent
|
<DataListContent
|
||||||
noPadding={false}
|
hasNoPadding={false}
|
||||||
aria-label="Session Details"
|
aria-label="Session Details"
|
||||||
id={'ex-expand' + row}
|
id={'ex-expand' + row}
|
||||||
isHidden={!this.state.isRowOpen[row]}
|
isHidden={!this.state.isRowOpen[row]}
|
||||||
>
|
>
|
||||||
<Level gutter='md'>
|
<Level hasGutter>
|
||||||
<LevelItem><span /></LevelItem>
|
<LevelItem><span /></LevelItem>
|
||||||
<LevelItem id={'shared-with-user-message-' + row}>{this.sharedWithUsersMessage(row)}</LevelItem>
|
<LevelItem id={'shared-with-user-message-' + row}>{this.sharedWithUsersMessage(row)}</LevelItem>
|
||||||
<LevelItem><span /></LevelItem>
|
<LevelItem><span /></LevelItem>
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
ChipGroup,
|
ChipGroup,
|
||||||
ChipGroupToolbarItem,
|
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Gallery,
|
Gallery,
|
||||||
|
@ -28,7 +27,8 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
Stack,
|
Stack,
|
||||||
StackItem,
|
StackItem,
|
||||||
TextInput
|
TextInput,
|
||||||
|
ModalVariant
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||||
|
@ -57,7 +57,7 @@ interface ShareTheResourceState {
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
export class ShareTheResource extends React.Component<ShareTheResourceProps, ShareTheResourceState> {
|
export class ShareTheResource extends React.Component<ShareTheResourceProps, ShareTheResourceState> {
|
||||||
protected static defaultProps = {permissions: []};
|
protected static defaultProps:any = {permissions: []};
|
||||||
static contextType = AccountServiceContext;
|
static contextType = AccountServiceContext;
|
||||||
context: React.ContextType<typeof AccountServiceContext>;
|
context: React.ContextType<typeof AccountServiceContext>;
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title={'Share the resource - ' + this.props.resource.name}
|
title={'Share the resource - ' + this.props.resource.name}
|
||||||
isLarge={true}
|
variant={ModalVariant.large}
|
||||||
isOpen={this.state.isOpen}
|
isOpen={this.state.isOpen}
|
||||||
onClose={this.handleToggleDialog}
|
onClose={this.handleToggleDialog}
|
||||||
actions={[
|
actions={[
|
||||||
|
@ -178,7 +178,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Stack gutter='md'>
|
<Stack hasGutter>
|
||||||
<StackItem isFilled>
|
<StackItem isFilled>
|
||||||
<Form>
|
<Form>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
|
@ -187,13 +187,11 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
||||||
helperTextInvalid={Msg.localize('resourceAlreadyShared')}
|
helperTextInvalid={Msg.localize('resourceAlreadyShared')}
|
||||||
fieldId="username"
|
fieldId="username"
|
||||||
isRequired
|
isRequired
|
||||||
isValid={!this.isAlreadyShared()}
|
|
||||||
>
|
>
|
||||||
<Gallery gutter='sm'>
|
<Gallery hasGutter>
|
||||||
<GalleryItem>
|
<GalleryItem>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={this.state.usernameInput}
|
value={this.state.usernameInput}
|
||||||
isValid={!this.isAlreadyShared()}
|
|
||||||
id="username"
|
id="username"
|
||||||
aria-describedby="username-helper"
|
aria-describedby="username-helper"
|
||||||
placeholder="Username or email"
|
placeholder="Username or email"
|
||||||
|
@ -208,14 +206,12 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
||||||
</GalleryItem>
|
</GalleryItem>
|
||||||
|
|
||||||
</Gallery>
|
</Gallery>
|
||||||
<ChipGroup withToolbar>
|
<ChipGroup categoryName={Msg.localize('shareWith')}>
|
||||||
<ChipGroupToolbarItem key='users-selected' categoryName='Share with '>
|
|
||||||
{this.state.usernames.map((currentChip: string) => (
|
{this.state.usernames.map((currentChip: string) => (
|
||||||
<Chip key={currentChip} onClick={() => this.handleDeleteUsername(currentChip)}>
|
<Chip key={currentChip} onClick={() => this.handleDeleteUsername(currentChip)}>
|
||||||
{currentChip}
|
{currentChip}
|
||||||
</Chip>
|
</Chip>
|
||||||
))}
|
))}
|
||||||
</ChipGroupToolbarItem>
|
|
||||||
</ChipGroup>
|
</ChipGroup>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
|
|
|
@ -23,7 +23,6 @@ import {
|
||||||
DataListCell,
|
DataListCell,
|
||||||
DataListItemCells,
|
DataListItemCells,
|
||||||
ChipGroup,
|
ChipGroup,
|
||||||
ChipGroupToolbarItem,
|
|
||||||
Chip
|
Chip
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { RepositoryIcon } from '@patternfly/react-icons';
|
import { RepositoryIcon } from '@patternfly/react-icons';
|
||||||
|
@ -85,8 +84,7 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key={'permissions-' + row} width={2}>
|
<DataListCell key={'permissions-' + row} width={2}>
|
||||||
{ resource.scopes.length > 0 &&
|
{ resource.scopes.length > 0 &&
|
||||||
<ChipGroup withToolbar>
|
<ChipGroup categoryName={Msg.localize('permissions')}>
|
||||||
<ChipGroupToolbarItem key='permissions' categoryName={Msg.localize('permissions')}>
|
|
||||||
{
|
{
|
||||||
resource.scopes.map(scope => (
|
resource.scopes.map(scope => (
|
||||||
<Chip key={scope.name} isReadOnly>
|
<Chip key={scope.name} isReadOnly>
|
||||||
|
@ -94,13 +92,11 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
||||||
</Chip>
|
</Chip>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ChipGroupToolbarItem>
|
|
||||||
</ChipGroup>}
|
</ChipGroup>}
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key={'pending-' + row} width={2}>
|
<DataListCell key={'pending-' + row} width={2}>
|
||||||
{resource.shareRequests.length > 0 &&
|
{resource.shareRequests.length > 0 &&
|
||||||
<ChipGroup withToolbar>
|
<ChipGroup categoryName={Msg.localize('pending')}>
|
||||||
<ChipGroupToolbarItem key='permissions' categoryName={Msg.localize('pending')}>
|
|
||||||
{
|
{
|
||||||
(resource.shareRequests[0].scopes as Scope[]).map(scope => (
|
(resource.shareRequests[0].scopes as Scope[]).map(scope => (
|
||||||
<Chip key={scope.name} isReadOnly>
|
<Chip key={scope.name} isReadOnly>
|
||||||
|
@ -108,7 +104,6 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
||||||
</Chip>
|
</Chip>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ChipGroupToolbarItem>
|
|
||||||
</ChipGroup>
|
</ChipGroup>
|
||||||
}
|
}
|
||||||
</DataListCell>
|
</DataListCell>
|
||||||
|
|
|
@ -38,3 +38,8 @@ export interface Permission {
|
||||||
scopes: Scope[] | string[]; // this should be Scope[] - fix API
|
scopes: Scope[] | string[]; // this should be Scope[] - fix API
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Permissions {
|
||||||
|
permissions: Permission[];
|
||||||
|
row?: number;
|
||||||
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
DataList,
|
DataList,
|
||||||
DataListAction,
|
DataListAction,
|
||||||
|
@ -25,28 +26,34 @@ import {
|
||||||
DataListCell,
|
DataListCell,
|
||||||
DataListItem,
|
DataListItem,
|
||||||
DataListItemRow,
|
DataListItemRow,
|
||||||
Stack,
|
EmptyState,
|
||||||
StackItem,
|
EmptyStateVariant,
|
||||||
|
EmptyStateBody,
|
||||||
|
Split,
|
||||||
|
SplitItem,
|
||||||
Title,
|
Title,
|
||||||
TitleLevel,
|
|
||||||
DataListActionVisibility,
|
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownPosition,
|
DropdownPosition,
|
||||||
KebabToggle,
|
KebabToggle,
|
||||||
} from '@patternfly/react-core';
|
PageSection,
|
||||||
|
PageSectionVariants
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
import {AIACommand} from '../../util/AIACommand';
|
import { AIACommand } from "../../util/AIACommand";
|
||||||
import TimeUtil from '../../util/TimeUtil';
|
import TimeUtil from "../../util/TimeUtil";
|
||||||
import {HttpResponse, AccountServiceClient} from '../../account-service/account.service';
|
import {
|
||||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
HttpResponse,
|
||||||
import {ContinueCancelModal} from '../../widgets/ContinueCancelModal';
|
AccountServiceClient,
|
||||||
import {Features} from '../../widgets/features';
|
} from "../../account-service/account.service";
|
||||||
import {Msg} from '../../widgets/Msg';
|
import { AccountServiceContext } from "../../account-service/AccountServiceContext";
|
||||||
import {ContentPage} from '../ContentPage';
|
import { ContinueCancelModal } from "../../widgets/ContinueCancelModal";
|
||||||
import {ContentAlert} from '../ContentAlert';
|
import { Features } from "../../widgets/features";
|
||||||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
import { Msg } from "../../widgets/Msg";
|
||||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
import { ContentPage } from "../ContentPage";
|
||||||
import { css } from '@patternfly/react-styles';
|
import { ContentAlert } from "../ContentAlert";
|
||||||
|
import { KeycloakContext } from "../../keycloak-service/KeycloakContext";
|
||||||
|
import { KeycloakService } from "../../keycloak-service/keycloak.service";
|
||||||
|
import { css } from "@patternfly/react-styles";
|
||||||
|
|
||||||
declare const features: Features;
|
declare const features: Features;
|
||||||
|
|
||||||
|
@ -55,7 +62,7 @@ interface PasswordDetails {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CredCategory = 'password' | 'two-factor' | 'passwordless';
|
type CredCategory = "password" | "two-factor" | "passwordless";
|
||||||
type CredType = string;
|
type CredType = string;
|
||||||
type CredTypeMap = Map<CredType, CredentialContainer>;
|
type CredTypeMap = Map<CredType, CredentialContainer>;
|
||||||
type CredContainerMap = Map<CredCategory, CredTypeMap>;
|
type CredContainerMap = Map<CredCategory, CredTypeMap>;
|
||||||
|
@ -89,8 +96,7 @@ interface CredentialContainer {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SigningInPageProps extends RouteComponentProps {
|
interface SigningInPageProps extends RouteComponentProps {}
|
||||||
}
|
|
||||||
|
|
||||||
interface SigningInPageState {
|
interface SigningInPageState {
|
||||||
// Credential containers organized by category then type
|
// Credential containers organized by category then type
|
||||||
|
@ -100,28 +106,33 @@ interface SigningInPageState {
|
||||||
/**
|
/**
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
class SigningInPage extends React.Component<SigningInPageProps, SigningInPageState> {
|
class SigningInPage extends React.Component<
|
||||||
|
SigningInPageProps,
|
||||||
|
SigningInPageState
|
||||||
|
> {
|
||||||
static contextType = AccountServiceContext;
|
static contextType = AccountServiceContext;
|
||||||
context: React.ContextType<typeof AccountServiceContext>;
|
context: React.ContextType<typeof AccountServiceContext>;
|
||||||
|
|
||||||
public constructor(props: SigningInPageProps, context: React.ContextType<typeof AccountServiceContext>) {
|
public constructor(
|
||||||
|
props: SigningInPageProps,
|
||||||
|
context: React.ContextType<typeof AccountServiceContext>
|
||||||
|
) {
|
||||||
super(props);
|
super(props);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
credentialContainers: new Map(),
|
credentialContainers: new Map(),
|
||||||
}
|
};
|
||||||
|
|
||||||
this.getCredentialContainers();
|
this.getCredentialContainers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCredentialContainers(): void {
|
private getCredentialContainers(): void {
|
||||||
this.context!.doGet("/credentials")
|
this.context!.doGet("/credentials").then(
|
||||||
.then((response: HttpResponse<CredentialContainer[]>) => {
|
(response: HttpResponse<CredentialContainer[]>) => {
|
||||||
|
|
||||||
const allContainers: CredContainerMap = new Map();
|
const allContainers: CredContainerMap = new Map();
|
||||||
const containers: CredentialContainer[] = response.data || [];
|
const containers: CredentialContainer[] = response.data || [];
|
||||||
containers.forEach(container => {
|
containers.forEach((container) => {
|
||||||
let categoryMap = allContainers.get(container.category);
|
let categoryMap = allContainers.get(container.category);
|
||||||
if (!categoryMap) {
|
if (!categoryMap) {
|
||||||
categoryMap = new Map();
|
categoryMap = new Map();
|
||||||
|
@ -131,60 +142,80 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ credentialContainers: allContainers });
|
this.setState({ credentialContainers: allContainers });
|
||||||
console.log({allContainers})
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRemove = (credentialId: string, userLabel: string) => {
|
private handleRemove = (credentialId: string, userLabel: string) => {
|
||||||
this.context!.doDelete("/credentials/" + credentialId)
|
this.context!.doDelete("/credentials/" + credentialId).then(() => {
|
||||||
.then(() => {
|
|
||||||
this.getCredentialContainers();
|
this.getCredentialContainers();
|
||||||
ContentAlert.success('successRemovedMessage', [userLabel]);
|
ContentAlert.success("successRemovedMessage", [userLabel]);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
public static credElementId(credType: CredType, credId: string, item: string): string {
|
public static credElementId(
|
||||||
|
credType: CredType,
|
||||||
|
credId: string,
|
||||||
|
item: string
|
||||||
|
): string {
|
||||||
return `${credType}-${item}-${credId.substring(0, 8)}`;
|
return `${credType}-${item}-${credId.substring(0, 8)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<ContentPage title="signingIn"
|
<ContentPage title="signingIn" introMessage="signingInSubMessage">
|
||||||
introMessage="signingInSubMessage">
|
|
||||||
<Stack gutter='md'>
|
|
||||||
{this.renderCategories()}
|
{this.renderCategories()}
|
||||||
</Stack>
|
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderCategories(): React.ReactNode {
|
private renderCategories(): React.ReactNode {
|
||||||
return (<> {
|
return Array.from(this.state.credentialContainers.keys()).map(
|
||||||
Array.from(this.state.credentialContainers.keys()).map(category => (
|
(category) => (
|
||||||
<StackItem key={category} isFilled>
|
<PageSection key={category} variant={PageSectionVariants.light}>
|
||||||
<Title id={`${category}-categ-title`} headingLevel={TitleLevel.h2} size='2xl'>
|
<Title
|
||||||
<strong><Msg msgKey={category}/></strong>
|
id={`${category}-categ-title`}
|
||||||
|
headingLevel="h2"
|
||||||
|
size="xl"
|
||||||
|
>
|
||||||
|
<Msg msgKey={category} />
|
||||||
</Title>
|
</Title>
|
||||||
<DataList aria-label='foo'>
|
{this.renderTypes(category!)}
|
||||||
{this.renderTypes(this.state.credentialContainers.get(category)!)}
|
</PageSection>
|
||||||
</DataList>
|
)
|
||||||
</StackItem>
|
)
|
||||||
))
|
|
||||||
|
|
||||||
}</>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTypes(credTypeMap: CredTypeMap): React.ReactNode {
|
private renderTypes(category: CredCategory): React.ReactNode {
|
||||||
|
let credTypeMap: CredTypeMap = this.state.credentialContainers.get(
|
||||||
|
category
|
||||||
|
)!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeycloakContext.Consumer>
|
<KeycloakContext.Consumer>
|
||||||
{ keycloak => (
|
{(keycloak) => (
|
||||||
<>{
|
<>
|
||||||
Array.from(credTypeMap.keys()).map((credType: CredType, index: number, typeArray: string[]) => ([
|
{Array.from(
|
||||||
this.renderCredTypeTitle(credTypeMap.get(credType)!, keycloak!),
|
credTypeMap.keys()
|
||||||
this.renderUserCredentials(credTypeMap, credType, keycloak!),
|
).map(
|
||||||
this.renderEmptyRow(credTypeMap.get(credType)!.type, index === typeArray.length - 1)
|
(
|
||||||
]))
|
credType: CredType,
|
||||||
}</>
|
index: number,
|
||||||
|
typeArray: string[]
|
||||||
|
) => [
|
||||||
|
this.renderCredTypeTitle(
|
||||||
|
credTypeMap.get(credType)!,
|
||||||
|
keycloak!,
|
||||||
|
category
|
||||||
|
),
|
||||||
|
this.renderUserCredentials(
|
||||||
|
credTypeMap,
|
||||||
|
credType,
|
||||||
|
keycloak!
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</KeycloakContext.Consumer>
|
</KeycloakContext.Consumer>
|
||||||
);
|
);
|
||||||
|
@ -194,15 +225,21 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
||||||
if (isLast) return; // don't put empty row at the end
|
if (isLast) return; // don't put empty row at the end
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataListItem aria-labelledby={'empty-list-item-' + type}>
|
<DataListItem aria-labelledby={"empty-list-item-" + type}>
|
||||||
<DataListItemRow key={'empty-row-' + type}>
|
<DataListItemRow key={"empty-row-" + type}>
|
||||||
<DataListItemCells dataListCells={[<DataListCell></DataListCell>]}/>
|
<DataListItemCells
|
||||||
|
dataListCells={[<DataListCell></DataListCell>]}
|
||||||
|
/>
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderUserCredentials(credTypeMap: CredTypeMap, credType: CredType, keycloak: KeycloakService): React.ReactNode {
|
private renderUserCredentials(
|
||||||
|
credTypeMap: CredTypeMap,
|
||||||
|
credType: CredType,
|
||||||
|
keycloak: KeycloakService
|
||||||
|
): React.ReactNode {
|
||||||
const credContainer: CredentialContainer = credTypeMap.get(credType)!;
|
const credContainer: CredentialContainer = credTypeMap.get(credType)!;
|
||||||
const userCredentialMetadatas: CredMetadata[] = credContainer.userCredentialMetadatas;
|
const userCredentialMetadatas: CredMetadata[] = credContainer.userCredentialMetadatas;
|
||||||
const removeable: boolean = credContainer.removeable;
|
const removeable: boolean = credContainer.removeable;
|
||||||
|
@ -212,17 +249,22 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
||||||
if (!userCredentialMetadatas || userCredentialMetadatas.length === 0) {
|
if (!userCredentialMetadatas || userCredentialMetadatas.length === 0) {
|
||||||
const localizedDisplayName = Msg.localize(displayName);
|
const localizedDisplayName = Msg.localize(displayName);
|
||||||
return (
|
return (
|
||||||
<DataListItem key='no-credentials-list-item' aria-labelledby='no-credentials-list-item'>
|
<DataList aria-label={Msg.localize('notSetUp', [localizedDisplayName])} className="pf-u-mb-xl">
|
||||||
<DataListItemRow key='no-credentials-list-item-row'>
|
<DataListItem key='no-credentials-list-item' aria-labelledby={Msg.localize('notSetUp', [localizedDisplayName])}>
|
||||||
|
<DataListItemRow key='no-credentials-list-item-row' className="pf-u-align-items-center">
|
||||||
<DataListItemCells
|
<DataListItemCells
|
||||||
dataListCells={[
|
dataListCells={[
|
||||||
<DataListCell key={'no-credentials-cell-0'}/>,
|
<DataListCell key={'no-credentials-cell-0'}/>,
|
||||||
<strong id={`${type}-not-set-up`} key={'no-credentials-cell-1'}><Msg msgKey='notSetUp' params={[localizedDisplayName]}/></strong>,
|
<EmptyState id={`${type}-not-set-up`} key={'no-credentials-cell-1'} variant={EmptyStateVariant.xs}>
|
||||||
|
<EmptyStateBody>
|
||||||
|
<Msg msgKey='notSetUp' params={[localizedDisplayName]}/>
|
||||||
|
</EmptyStateBody>
|
||||||
|
</EmptyState>,
|
||||||
<DataListCell key={'no-credentials-cell-2'}/>
|
<DataListCell key={'no-credentials-cell-2'}/>
|
||||||
]}
|
]}/>
|
||||||
/>
|
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
|
</DataList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,11 +281,24 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
||||||
updateAIA = new AIACommand(keycloak, credContainer.updateAction);
|
updateAIA = new AIACommand(keycloak, credContainer.updateAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let maxWidth = { maxWidth: 689 } as React.CSSProperties;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key='userCredentialMetadatas'> {
|
<React.Fragment key='userCredentialMetadatas'> {
|
||||||
userCredentialMetadatas.map(credentialMetadata => (
|
userCredentialMetadatas.map(credentialMetadata => (
|
||||||
|
<>
|
||||||
|
{(credentialMetadata.infoMessage && !credentialMetadata.warningMessageTitle && !credentialMetadata.warningMessageDescription) &&
|
||||||
|
<Alert variant="default" className="pf-u-mb-md" isInline isPlain title={Msg.localize(JSON.parse(credentialMetadata.infoMessage).key, JSON.parse(credentialMetadata.infoMessage).parameters)} />
|
||||||
|
}
|
||||||
|
{(credentialMetadata.warningMessageTitle && credentialMetadata.warningMessageDescription) &&
|
||||||
|
<Alert variant="warning" className="pf-u-mb-md" isInline title={Msg.localize(JSON.parse(credentialMetadata.warningMessageTitle).key, JSON.parse(credentialMetadata.warningMessageTitle).parameters)} style={maxWidth}>
|
||||||
|
|
||||||
|
<p>{Msg.localize(JSON.parse(credentialMetadata.warningMessageDescription).key, JSON.parse(credentialMetadata.warningMessageDescription).parameters)}</p>
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
<DataList aria-label="user credential" className="pf-u-mb-xl">
|
||||||
<DataListItem id={`${SigningInPage.credElementId(type, credentialMetadata.credential.id, 'row')}`} key={'credential-list-item-' + credentialMetadata.credential.id} aria-labelledby={'credential-list-item-' + credentialMetadata.credential.userLabel}>
|
<DataListItem id={`${SigningInPage.credElementId(type, credentialMetadata.credential.id, 'row')}`} key={'credential-list-item-' + credentialMetadata.credential.id} aria-labelledby={'credential-list-item-' + credentialMetadata.credential.userLabel}>
|
||||||
<DataListItemRow key={'userCredentialRow-' + credentialMetadata.credential.id}>
|
<DataListItemRow key={'userCredentialRow-' + credentialMetadata.credential.id} className="pf-u-align-items-center">
|
||||||
<DataListItemCells dataListCells={this.credentialRowCells(credentialMetadata, type)}/>
|
<DataListItemCells dataListCells={this.credentialRowCells(credentialMetadata, type)}/>
|
||||||
<CredentialAction
|
<CredentialAction
|
||||||
credential={credentialMetadata.credential}
|
credential={credentialMetadata.credential}
|
||||||
|
@ -253,125 +308,164 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
||||||
/>
|
/>
|
||||||
</DataListItemRow>
|
</DataListItemRow>
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
|
</DataList>
|
||||||
|
</>
|
||||||
))
|
))
|
||||||
}
|
} </React.Fragment>
|
||||||
</React.Fragment>)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private credentialRowCells(credMetadata: CredMetadata, type: string): React.ReactNode[] {
|
private credentialRowCells(credMetadata: CredMetadata, type: string): React.ReactNode[] {
|
||||||
const credRowCells: React.ReactNode[] = [];
|
const credRowCells: React.ReactNode[] = [];
|
||||||
const credential = credMetadata.credential;
|
const credential = credMetadata.credential;
|
||||||
const infoMessage = credMetadata.infoMessage ? JSON.parse(credMetadata.infoMessage) : null;
|
let maxWidth = { "--pf-u-max-width--MaxWidth": "300px" } as React.CSSProperties;
|
||||||
const warningMessageTitle = credMetadata.warningMessageTitle ? JSON.parse(credMetadata.warningMessageTitle) : null;
|
|
||||||
const warningMessageDescription = credMetadata.warningMessageDescription ? JSON.parse(credMetadata.warningMessageDescription) : null;
|
|
||||||
credRowCells.push(
|
credRowCells.push(
|
||||||
<DataListCell id={`${SigningInPage.credElementId(type, credential.id, 'label')}`} key={'userLabel-' + credential.id}>
|
<DataListCell id={`${SigningInPage.credElementId(type, credential.id, 'label')}`} key={'userLabel-' + credential.id} className="pf-u-max-width" style={maxWidth}>
|
||||||
{credential.userLabel}
|
{credential.userLabel}
|
||||||
{infoMessage &&
|
|
||||||
<div>{Msg.localize(infoMessage.key, infoMessage.parameters)}</div>
|
|
||||||
}
|
|
||||||
{warningMessageTitle &&
|
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<div className="pf-c-alert pf-m-warning pf-m-inline" aria-label="Success alert">
|
|
||||||
<div className="pf-c-alert__icon">
|
|
||||||
<i className="pficon-warning-triangle-o" aria-hidden="true"></i>
|
|
||||||
</div>
|
|
||||||
<h4 className="pf-c-alert__title">
|
|
||||||
<span className="pf-screen-reader">Warning alert:</span>
|
|
||||||
{Msg.localize(warningMessageTitle.key, warningMessageTitle.parameters)}
|
|
||||||
</h4>
|
|
||||||
{credMetadata.warningMessageDescription &&
|
|
||||||
<div className="pf-c-alert__description">
|
|
||||||
{Msg.localize(warningMessageDescription.key, warningMessageDescription.parameters)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</DataListCell>
|
</DataListCell>
|
||||||
);
|
);
|
||||||
if (credential.strCreatedDate) {
|
if (credential.strCreatedDate) {
|
||||||
credRowCells.push(<DataListCell id={`${SigningInPage.credElementId(type, credential.id, 'created-at')}`} key={'created-' + credential.id}><strong><Msg msgKey='credentialCreatedAt'/>: </strong>{credential.strCreatedDate}</DataListCell>);
|
credRowCells.push(
|
||||||
credRowCells.push(<DataListCell key={'spacer-' + credential.id}/>);
|
<DataListCell
|
||||||
|
id={`${SigningInPage.credElementId(
|
||||||
|
type,
|
||||||
|
credential.id,
|
||||||
|
"created-at"
|
||||||
|
)}`}
|
||||||
|
key={"created-" + credential.id}
|
||||||
|
>
|
||||||
|
<strong className="pf-u-mr-md">
|
||||||
|
<Msg msgKey="credentialCreatedAt" />{" "}
|
||||||
|
</strong>
|
||||||
|
{credential.strCreatedDate}
|
||||||
|
</DataListCell>
|
||||||
|
);
|
||||||
|
credRowCells.push(<DataListCell key={"spacer-" + credential.id} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return credRowCells;
|
return credRowCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderCredTypeTitle(credContainer: CredentialContainer, keycloak: KeycloakService): React.ReactNode {
|
private renderCredTypeTitle(
|
||||||
if (!credContainer.hasOwnProperty('helptext') && !credContainer.hasOwnProperty('createAction')) return;
|
credContainer: CredentialContainer,
|
||||||
|
keycloak: KeycloakService,
|
||||||
|
category: CredCategory
|
||||||
|
): React.ReactNode {
|
||||||
|
|
||||||
|
if (
|
||||||
|
!credContainer.hasOwnProperty("helptext") &&
|
||||||
|
!credContainer.hasOwnProperty("createAction")
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
let setupAction: AIACommand;
|
let setupAction: AIACommand;
|
||||||
if (credContainer.createAction) {
|
if (credContainer.createAction) {
|
||||||
setupAction = new AIACommand(keycloak, credContainer.createAction);
|
setupAction = new AIACommand(keycloak, credContainer.createAction);
|
||||||
}
|
}
|
||||||
const credContainerDisplayName: string = Msg.localize(credContainer.displayName);
|
|
||||||
|
|
||||||
|
const credContainerDisplayName: string = Msg.localize(
|
||||||
|
credContainer.displayName
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={'credTypeTitle-' + credContainer.type}>
|
<React.Fragment key={"credTypeTitle-" + credContainer.type}>
|
||||||
<DataListItem aria-labelledby={'type-datalistitem-' + credContainer.type}>
|
<Split className="pf-u-mt-lg pf-u-mb-lg">
|
||||||
<DataListItemRow key={'credTitleRow-' + credContainer.type}>
|
<SplitItem>
|
||||||
<DataListItemCells
|
<Title
|
||||||
dataListCells={[
|
headingLevel="h3"
|
||||||
<DataListCell width={5} key={'credTypeTitle-' + credContainer.type}>
|
size="md"
|
||||||
<Title headingLevel={TitleLevel.h3} size='2xl'>
|
className="pf-u-mb-md"
|
||||||
<strong id={`${credContainer.type}-cred-title`}><Msg msgKey={credContainer.displayName}/></strong>
|
>
|
||||||
|
<span className="cred-title pf-u-display-block" id={`${credContainer.type}-cred-title`}>
|
||||||
|
<Msg msgKey={credContainer.displayName} />
|
||||||
|
</span>
|
||||||
</Title>
|
</Title>
|
||||||
<span id={`${credContainer.type}-cred-help`}>
|
<span id={`${credContainer.type}-cred-help`}>
|
||||||
{credContainer.helptext && <Msg msgKey={credContainer.helptext}/>}
|
{credContainer.helptext && (
|
||||||
|
<Msg msgKey={credContainer.helptext} />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</DataListCell>,
|
</SplitItem>
|
||||||
|
|
||||||
]}/>
|
<SplitItem isFilled>
|
||||||
{credContainer.createAction &&
|
{credContainer.createAction && (
|
||||||
<DataListAction
|
<div
|
||||||
aria-labelledby='create'
|
id={"mob-setUpAction-" + credContainer.type}
|
||||||
aria-label='create action'
|
className="pf-u-display-none-on-lg pf-u-float-right"
|
||||||
id={'mob-setUpAction-' + credContainer.type}
|
|
||||||
className={DataListActionVisibility.hiddenOnLg}
|
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
isPlain
|
isPlain
|
||||||
position={DropdownPosition.right}
|
position={DropdownPosition.right}
|
||||||
toggle={<KebabToggle onToggle={isOpen => {
|
toggle={
|
||||||
|
<KebabToggle
|
||||||
|
onToggle={(isOpen) => {
|
||||||
credContainer.open = isOpen;
|
credContainer.open = isOpen;
|
||||||
this.setState({ credentialContainers: new Map(this.state.credentialContainers) });
|
this.setState({
|
||||||
}} />}
|
credentialContainers: new Map(
|
||||||
|
this.state.credentialContainers
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
isOpen={credContainer.open}
|
isOpen={credContainer.open}
|
||||||
dropdownItems={[
|
dropdownItems={[
|
||||||
<button id={`mob-${credContainer.type}-set-up`} className="pf-c-button pf-m-link" type="button" onClick={() => setupAction.execute()}>
|
<button
|
||||||
<span className="pf-c-button__icon">
|
id={`mob-${credContainer.type}-set-up`}
|
||||||
<i className="fas fa-plus-circle" aria-hidden="true"></i>
|
className="pf-c-button pf-m-link"
|
||||||
</span>
|
type="button"
|
||||||
<Msg msgKey='setUpNew' params={[credContainerDisplayName]} />
|
onClick={() =>
|
||||||
</button>]}
|
setupAction.execute()
|
||||||
/>
|
}
|
||||||
</DataListAction>}
|
|
||||||
{credContainer.createAction &&
|
|
||||||
<DataListAction
|
|
||||||
aria-labelledby='create'
|
|
||||||
aria-label='create action'
|
|
||||||
id={'setUpAction-' + credContainer.type}
|
|
||||||
className={css(DataListActionVisibility.visibleOnLg, DataListActionVisibility.hidden)}
|
|
||||||
>
|
>
|
||||||
<button id={`${credContainer.type}-set-up`} className="pf-c-button pf-m-link" type="button" onClick={()=> setupAction.execute()}>
|
|
||||||
<span className="pf-c-button__icon">
|
<span className="pf-c-button__icon">
|
||||||
<i className="fas fa-plus-circle" aria-hidden="true"></i>
|
<i
|
||||||
|
className="fas fa-plus-circle"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
</span>
|
</span>
|
||||||
<Msg msgKey='setUpNew' params={[credContainerDisplayName]}/>
|
<Msg
|
||||||
|
msgKey="setUpNew"
|
||||||
|
params={[
|
||||||
|
credContainerDisplayName,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{credContainer.createAction && (
|
||||||
|
<div
|
||||||
|
id={"setUpAction-" + credContainer.type}
|
||||||
|
className="pf-u-display-none pf-u-display-inline-flex-on-lg pf-u-float-right"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id={`${credContainer.type}-set-up`}
|
||||||
|
className="pf-c-button pf-m-link"
|
||||||
|
type="button"
|
||||||
|
onClick={() => setupAction.execute()}
|
||||||
|
>
|
||||||
|
<span className="pf-c-button__icon">
|
||||||
|
<i
|
||||||
|
className="fas fa-plus-circle"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
<Msg
|
||||||
|
msgKey="setUpNew"
|
||||||
|
params={[credContainerDisplayName]}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</DataListAction>}
|
</div>
|
||||||
</DataListItemRow>
|
)}
|
||||||
</DataListItem>
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
type CredRemover = (credentialId: string, userLabel: string) => void;
|
type CredRemover = (credentialId: string, userLabel: string) => void;
|
||||||
|
|
||||||
interface CredentialActionProps {
|
interface CredentialActionProps {
|
||||||
credential: UserCredential;
|
credential: UserCredential;
|
||||||
removeable: boolean;
|
removeable: boolean;
|
||||||
|
@ -383,27 +477,47 @@ class CredentialAction extends React.Component<CredentialActionProps> {
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
if (this.props.updateAction) {
|
if (this.props.updateAction) {
|
||||||
return (
|
return (
|
||||||
<DataListAction aria-labelledby='foo' aria-label='foo action' id={'updateAction-' + this.props.credential.id}>
|
<DataListAction
|
||||||
<Button id={`${SigningInPage.credElementId(this.props.credential.type, this.props.credential.id, 'update')}`} variant='primary'onClick={()=> this.props.updateAction.execute()}><Msg msgKey='update'/></Button>
|
aria-labelledby={Msg.localize('updateCredAriaLabel')}
|
||||||
|
aria-label={Msg.localize('updateCredAriaLabel')}
|
||||||
|
id={"updateAction-" + this.props.credential.id}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
id={`${SigningInPage.credElementId(
|
||||||
|
this.props.credential.type,
|
||||||
|
this.props.credential.id,
|
||||||
|
"update"
|
||||||
|
)}`}
|
||||||
|
onClick={() => this.props.updateAction.execute()}
|
||||||
|
>
|
||||||
|
<Msg msgKey="update" />
|
||||||
|
</Button>
|
||||||
</DataListAction>
|
</DataListAction>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.removeable) {
|
if (this.props.removeable) {
|
||||||
const userLabel: string = this.props.credential.userLabel;
|
const userLabel: string = this.props.credential.userLabel;
|
||||||
return (
|
return (
|
||||||
<DataListAction aria-labelledby='foo' aria-label='foo action' id={'removeAction-' + this.props.credential.id }>
|
<DataListAction
|
||||||
<ContinueCancelModal buttonTitle='remove'
|
aria-label={Msg.localize('removeCredAriaLabel')}
|
||||||
|
aria-labelledby={Msg.localize('removeCredAriaLabel')}
|
||||||
|
id={'removeAction-' + this.props.credential.id }
|
||||||
|
>
|
||||||
|
<ContinueCancelModal
|
||||||
|
buttonTitle='remove'
|
||||||
|
buttonVariant='danger'
|
||||||
buttonId={`${SigningInPage.credElementId(this.props.credential.type, this.props.credential.id, 'remove')}`}
|
buttonId={`${SigningInPage.credElementId(this.props.credential.type, this.props.credential.id, 'remove')}`}
|
||||||
modalTitle={Msg.localize('removeCred', [userLabel])}
|
modalTitle={Msg.localize('removeCred', [userLabel])}
|
||||||
modalMessage={Msg.localize('stopUsingCred', [userLabel])}
|
modalMessage={Msg.localize('stopUsingCred', [userLabel])}
|
||||||
onContinue={() => this.props.credRemover(this.props.credential.id, userLabel)}
|
onContinue={() => this.props.credRemover(this.props.credential.id, userLabel)}
|
||||||
/>
|
/>
|
||||||
</DataListAction>
|
</DataListAction>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<></>)
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Modal, Button, ButtonProps } from '@patternfly/react-core';
|
import { Modal, ModalVariant, Button, ButtonProps } from '@patternfly/react-core';
|
||||||
import {Msg} from './Msg';
|
import {Msg} from './Msg';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +34,6 @@ interface ContinueCancelModalProps {
|
||||||
onContinue: () => void;
|
onContinue: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isLarge?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContinueCancelModalState {
|
interface ContinueCancelModalState {
|
||||||
|
@ -52,8 +51,7 @@ export class ContinueCancelModal extends React.Component<ContinueCancelModalProp
|
||||||
buttonVariant: 'primary',
|
buttonVariant: 'primary',
|
||||||
modalContinueButtonLabel: 'continue',
|
modalContinueButtonLabel: 'continue',
|
||||||
modalCancelButtonLabel: 'doCancel',
|
modalCancelButtonLabel: 'doCancel',
|
||||||
isDisabled: false,
|
isDisabled: false
|
||||||
isSmall: true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public constructor(props: ContinueCancelModalProps) {
|
public constructor(props: ContinueCancelModalProps) {
|
||||||
|
@ -87,15 +85,16 @@ export class ContinueCancelModal extends React.Component<ContinueCancelModalProp
|
||||||
{this.props.render && this.props.render(this.handleModalToggle)}
|
{this.props.render && this.props.render(this.handleModalToggle)}
|
||||||
<Modal
|
<Modal
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
variant={ModalVariant.small}
|
||||||
title={Msg.localize(this.props.modalTitle)}
|
title={Msg.localize(this.props.modalTitle)}
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={this.handleModalToggle}
|
onClose={this.handleModalToggle}
|
||||||
actions={[
|
actions={[
|
||||||
<Button id='modal-cancel' key="cancel" variant="secondary" onClick={this.handleModalToggle}>
|
|
||||||
<Msg msgKey={this.props.modalCancelButtonLabel!}/>
|
|
||||||
</Button>,
|
|
||||||
<Button id='modal-confirm' key="confirm" variant="primary" onClick={this.handleContinue}>
|
<Button id='modal-confirm' key="confirm" variant="primary" onClick={this.handleContinue}>
|
||||||
<Msg msgKey={this.props.modalContinueButtonLabel!}/>
|
<Msg msgKey={this.props.modalContinueButtonLabel!}/>
|
||||||
|
</Button>,
|
||||||
|
<Button id='modal-cancel' key="cancel" variant="secondary" onClick={this.handleModalToggle}>
|
||||||
|
<Msg msgKey={this.props.modalCancelButtonLabel!}/>
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|
|
@ -20,15 +20,14 @@ import {
|
||||||
EmptyStateVariant,
|
EmptyStateVariant,
|
||||||
Title,
|
Title,
|
||||||
EmptyStateIcon,
|
EmptyStateIcon,
|
||||||
TitleLevel,
|
|
||||||
EmptyStateBody,
|
EmptyStateBody,
|
||||||
IconProps,
|
|
||||||
} from '@patternfly/react-core'
|
} from '@patternfly/react-core'
|
||||||
|
|
||||||
import { Msg } from './Msg';
|
import { Msg } from './Msg';
|
||||||
|
import {SVGIconProps} from '@patternfly/react-icons/dist/esm/createIcon';
|
||||||
|
|
||||||
export interface EmptyMessageStateProps {
|
export interface EmptyMessageStateProps {
|
||||||
icon: React.FunctionComponent<IconProps>;
|
icon: React.ComponentType<SVGIconProps>;
|
||||||
messageKey: string;
|
messageKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ export default class EmptyMessageState extends React.Component<EmptyMessageState
|
||||||
return (
|
return (
|
||||||
<EmptyState variant={EmptyStateVariant.full}>
|
<EmptyState variant={EmptyStateVariant.full}>
|
||||||
<EmptyStateIcon icon={this.props.icon} />
|
<EmptyStateIcon icon={this.props.icon} />
|
||||||
<Title headingLevel={TitleLevel.h5} size="lg">
|
<Title headingLevel="h5" size="lg">
|
||||||
<Msg msgKey={this.props.messageKey} />
|
<Msg msgKey={this.props.messageKey} />
|
||||||
</Title>
|
</Title>
|
||||||
<EmptyStateBody>
|
<EmptyStateBody>
|
||||||
|
|
|
@ -10,25 +10,28 @@
|
||||||
"check-types:watch": "npm run check-types -- -w",
|
"check-types:watch": "npm run check-types -- -w",
|
||||||
"lint": "eslint ./app/**/*.ts*",
|
"lint": "eslint ./app/**/*.ts*",
|
||||||
"move-web_modules": "shx mv web_modules ../../../keycloak/common/resources",
|
"move-web_modules": "shx mv web_modules ../../../keycloak/common/resources",
|
||||||
"copy-pf-resources": "npm run move-app-css && npm run copy-base-css && npm run copy-fonts && npm run copy-pficon",
|
"copy-pf-resources": "npm run move-app-css && npm run copy-base-css && npm run copy-fonts && npm run copy-pficon && npm run copy-addons",
|
||||||
"move-app-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx mv app.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles",
|
"move-app-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx mv app.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles",
|
||||||
"copy-base-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx cp node_modules/@patternfly/react-core/dist/styles/base.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles",
|
"copy-base-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx cp node_modules/@patternfly/react-core/dist/styles/base.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles",
|
||||||
"copy-fonts": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont && shx cp node_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont/overpass*.woff2 ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont",
|
"copy-fonts": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts && shx cp -r node_modules/@patternfly/react-core/dist/styles/assets/fonts/* ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts/",
|
||||||
"copy-pficon": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon && shx cp node_modules/@patternfly/react-core/dist/styles/assets/pficon/pficon.woff2 ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon"
|
"copy-pficon": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon && shx cp node_modules/@patternfly/react-core/dist/styles/assets/pficon/pficon.woff2 ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon",
|
||||||
|
"copy-addons": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/patternfly && shx cp node_modules/@patternfly/patternfly/patternfly-addons.css ../../../keycloak/common/resources/web_modules/@patternfly/patternfly"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Stan Silvert",
|
"author": "Stan Silvert",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@patternfly/react-core": "^3.153.3",
|
"@patternfly/patternfly": "^4.125.3",
|
||||||
"@patternfly/react-icons": "^3.15.16",
|
"@patternfly/react-core": "^4.147.0",
|
||||||
"@patternfly/react-styles": "^3.7.14",
|
"@patternfly/react-icons": "^4.11.8",
|
||||||
|
"@patternfly/react-styles": "^4.11.8",
|
||||||
"react": "npm:@pika/react@^16.13.1",
|
"react": "npm:@pika/react@^16.13.1",
|
||||||
"react-dom": "npm:@pika/react-dom@^16.13.1",
|
"react-dom": "npm:@pika/react-dom@^16.13.1",
|
||||||
"react-router-dom": "^4.3.1"
|
"react-router-dom": "^4.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.8.4",
|
"@babel/cli": "^7.8.4",
|
||||||
|
"@babel/compat-data": "^7.9.0",
|
||||||
"@babel/core": "^7.8.7",
|
"@babel/core": "^7.8.7",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/preset-env": "^7.13.15",
|
"@babel/preset-env": "^7.13.15",
|
||||||
|
@ -41,6 +44,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||||
"@typescript-eslint/parser": "^1.4.2",
|
"@typescript-eslint/parser": "^1.4.2",
|
||||||
"babel-eslint": "^9.0.0",
|
"babel-eslint": "^9.0.0",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
"eslint": "^5.15.1",
|
"eslint": "^5.15.1",
|
||||||
"eslint-config-react-app": "^3.0.8",
|
"eslint-config-react-app": "^3.0.8",
|
||||||
"eslint-plugin-flowtype": "^2.50.3",
|
"eslint-plugin-flowtype": "^2.50.3",
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"suppressImplicitAnyIndexErrors": true
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./app/**/*.ts?"
|
"./app/**/*.ts?"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
parent=base
|
parent=base
|
||||||
deprecatedMode=false
|
deprecatedMode=false
|
||||||
|
|
||||||
scripts=welcome-page-scripts.js
|
scripts=welcome-page-scripts.js
|
||||||
|
|
||||||
developmentMode=false
|
developmentMode=false
|
||||||
|
|
||||||
# This is the logo in upper lefthand corner.
|
# This is the logo in upper lefthand corner.
|
||||||
|
|