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")
|
||||
.removeable(true);
|
||||
UserModel user = metadataContext.getUser();
|
||||
if (user != null && !isConfiguredFor(session.getContext().getRealm(), user, getType())) {
|
||||
builder.createAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name());
|
||||
} else {
|
||||
builder.updateAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name());
|
||||
}
|
||||
return builder.build(session);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class AccountManagement extends AuthRealm implements PageWithLogOutAction
|
|||
@FindBy(xpath = "//a[@id='referer']")
|
||||
private WebElement backToRefererLink;
|
||||
|
||||
@FindBy(linkText = "Sign Out")
|
||||
@FindBy(linkText = "Sign out")
|
||||
private WebElement signOutLink;
|
||||
|
||||
@FindBy(linkText = "Account")
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.openqa.selenium.support.FindBy;
|
|||
*/
|
||||
public abstract class AbstractAccountPage extends AbstractPage {
|
||||
|
||||
@FindBy(linkText = "Sign Out")
|
||||
@FindBy(linkText = "Sign out")
|
||||
private WebElement logoutLink;
|
||||
|
||||
@FindBy(id = "kc-current-locale-link")
|
||||
|
|
|
@ -1357,7 +1357,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
|
|||
loginPage.login("realm-admin", "password");
|
||||
Assert.assertTrue(applicationsPage.isCurrent());
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -139,11 +139,11 @@ public class DeviceActivityPage extends AbstractLoggedInPage {
|
|||
}
|
||||
|
||||
public String getStarted() {
|
||||
return getTextFromItem("started").split("Started at ")[1];
|
||||
return getTextFromItem("started").split("Started ")[1];
|
||||
}
|
||||
|
||||
public String getExpires() {
|
||||
return getTextFromItem("expires").split("Expires at ")[1];
|
||||
return getTextFromItem("expires").split("Expires ")[1];
|
||||
}
|
||||
|
||||
public boolean isSignOutDisplayed() {
|
||||
|
|
|
@ -104,11 +104,11 @@ public class LinkedAccountsPage extends AbstractLoggedInPage {
|
|||
}
|
||||
|
||||
public boolean hasSocialLoginBadge() {
|
||||
return getTextFromElement(badgeElement).equals("Social Login");
|
||||
return getTextFromElement(badgeElement).equals("Social login");
|
||||
}
|
||||
|
||||
public boolean hasSystemDefinedBadge() {
|
||||
return getTextFromElement(badgeElement).equals("System Defined");
|
||||
return getTextFromElement(badgeElement).equals("System defined");
|
||||
}
|
||||
|
||||
public boolean hasSocialIcon() {
|
||||
|
|
|
@ -28,6 +28,6 @@ public class PageNotFound extends AbstractLoggedInPage {
|
|||
|
||||
@Override
|
||||
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>
|
||||
*/
|
||||
public class SigningInTest extends BaseAccountPageTest {
|
||||
public static final String PASSWORD_LABEL = "My Password";
|
||||
public static final String PASSWORD_LABEL = "My password";
|
||||
|
||||
@Page
|
||||
private SigningInPage signingInPage;
|
||||
|
@ -91,8 +91,8 @@ public class SigningInTest extends BaseAccountPageTest {
|
|||
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
|
||||
|
||||
assertThat(signingInPage.getCategoriesCount(), is(2));
|
||||
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic Authentication"));
|
||||
assertThat(signingInPage.getCategoryTitle("two-factor"), is("Two-Factor Authentication"));
|
||||
assertThat(signingInPage.getCategoryTitle("basic-authentication"), is("Basic authentication"));
|
||||
assertThat(signingInPage.getCategoryTitle("two-factor"), is("Two-factor authentication"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -160,7 +160,7 @@ public class SigningInTest extends BaseAccountPageTest {
|
|||
|
||||
signingInPage.assertCurrent();
|
||||
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 label2 = "OTP is inconvenient";
|
||||
|
|
|
@ -3,11 +3,16 @@ doCancel=Cancel
|
|||
doLogOutAllSessions=Log out all sessions
|
||||
doRemove=Remove
|
||||
doAdd=Add
|
||||
doSignOut=Sign Out
|
||||
doSignOut=Sign out
|
||||
doLogIn=Log In
|
||||
doLink=Link
|
||||
noAccessMessage=Access not allowed
|
||||
|
||||
personalInfoSidebarTitle=Personal info
|
||||
accountSecuritySidebarTitle=Account security
|
||||
signingInSidebarTitle=Signing in
|
||||
deviceActivitySidebarTitle=Device activity
|
||||
linkedAccountsSidebarTitle=Linked accounts
|
||||
|
||||
editAccountHtmlTitle=Edit Account
|
||||
personalInfoHtmlTitle=Personal Info
|
||||
|
@ -19,7 +24,7 @@ sessionsHtmlTitle=Sessions
|
|||
accountManagementTitle=Keycloak Account Management
|
||||
authenticatorTitle=Authenticator
|
||||
applicationsHtmlTitle=Applications
|
||||
linkedAccountsHtmlTitle=Linked Accounts
|
||||
linkedAccountsHtmlTitle=Linked accounts
|
||||
|
||||
accountManagementWelcomeMessage=Welcome to Keycloak Account Management
|
||||
personalInfoIntroMessage=Manage your basic information
|
||||
|
@ -32,7 +37,7 @@ updatePasswordTitle=Update 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.
|
||||
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
|
||||
email=Email
|
||||
|
@ -94,7 +99,7 @@ role_offline-access=Offline access
|
|||
role_uma_authorization=Obtain permissions
|
||||
client_account=Account
|
||||
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_realm-management=Realm Management
|
||||
client_broker=Broker
|
||||
|
@ -120,7 +125,7 @@ applications=Applications
|
|||
account=Account
|
||||
federatedIdentity=Federated Identity
|
||||
authenticator=Authenticator
|
||||
device-activity=Device Activity
|
||||
device-activity=Device activity
|
||||
sessions=Sessions
|
||||
log=Log
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
<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="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??>
|
||||
<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-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-label-default=Recovery codes
|
||||
|
||||
# WebAuthn
|
||||
webauthn-display-name=Security Key
|
||||
|
|
|
@ -112,8 +112,10 @@
|
|||
</#list>
|
||||
</#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/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"/>
|
||||
</head>
|
||||
|
||||
|
@ -181,18 +183,23 @@
|
|||
</div>
|
||||
<div class="pf-c-page__header-tools">
|
||||
<#if referrer?has_content && referrer_uri?has_content>
|
||||
<div class="pf-c-page__header-tools-group pf-m-icons">
|
||||
<a id="landingReferrerLink" href="${referrer_uri}" id="referrer" tabindex="0"><span class="pf-icon pf-icon-arrow"></span>${msg("backTo",referrerName)}</a>
|
||||
<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}" 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>
|
||||
</#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="landingSignOutButton" tabindex="0" style="display:none" onclick="keycloak.logout();" class="pf-c-button pf-m-primary" type="button">${msg("doSignOut")}</button>
|
||||
</div>
|
||||
|
||||
<!-- 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 -->
|
||||
<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>
|
||||
|
@ -200,7 +207,7 @@
|
|||
<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>
|
||||
<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>
|
||||
</#if>
|
||||
|
||||
|
@ -220,31 +227,35 @@
|
|||
</header>
|
||||
|
||||
<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">
|
||||
<h1>${msg("accountManagementWelcomeMessage")}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<#assign content=theme.apply("content.json")?eval>
|
||||
<#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 class="pf-c-card__header pf-c-content">
|
||||
<h2>
|
||||
<div class="pf-c-card__title pf-c-content">
|
||||
<h2 class="pf-u-display-flex pf-u-w-100 pf-u-flex-direction-column">
|
||||
<#if item.icon??>
|
||||
<i class="pf-icon ${item.icon}"></i>
|
||||
<i class="pf-icon ${item.icon}"></i>
|
||||
<#elseif item.iconSvg??>
|
||||
<img src="${item.iconSvg}" alt="icon"/>
|
||||
<img src="${item.iconSvg}" alt="icon"/>
|
||||
</#if>
|
||||
${msg(item.label)}
|
||||
</h2>
|
||||
<#if item.descriptionLabel??>
|
||||
<p>${msg(item.descriptionLabel)}</p>
|
||||
</#if>
|
||||
</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??>
|
||||
<#list item.content as sub>
|
||||
<div id="landing-${sub.id}">
|
||||
|
@ -257,8 +268,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
# Put new messages for Account Console Here
|
||||
# Feel free to use any existing messages from the base theme
|
||||
pageNotFound=Page Not Found
|
||||
pageNotFound=Page not found
|
||||
forbidden=Forbidden
|
||||
needAccessRights=You do not have access rights to this request. Contact your administrator.
|
||||
invalidRoute={0} is not a valid route.
|
||||
actionRequiresIDP=This action requires redirection to your identity provider.
|
||||
actionNotDefined=No Action defined
|
||||
actionNotDefined=No action defined
|
||||
continue=Continue
|
||||
refreshPage=Refresh the page
|
||||
refresh=Refresh
|
||||
done=Done
|
||||
cancel=Cancel
|
||||
remove=Remove
|
||||
|
@ -15,38 +16,46 @@ update=Update
|
|||
loadingMessage=Account Console loading ...
|
||||
unknownUser=Anonymous
|
||||
fullName={0} {1}
|
||||
allFieldsRequired=All fields are required.
|
||||
|
||||
selectLocale=Select a locale
|
||||
doSignIn=Sign In
|
||||
doSignIn=Sign in
|
||||
|
||||
# Device Activity Page
|
||||
signedInDevices=Signed In Devices
|
||||
signedInDevicesExplanation=Sign out any device that is unfamiliar.
|
||||
backToAdminConsole=Back to admin console
|
||||
accountManagementWelcomeMessage=Welcome to Keycloak account management
|
||||
|
||||
# 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?
|
||||
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.
|
||||
recentlyUsedDevices=Recently Used Devices
|
||||
recentlyUsedDevices=Recently used devices
|
||||
recentlyUsedDevicesExplanation=Devices used in the last month, but not currently logged in.
|
||||
lastAccess=Last Access
|
||||
unknownOperatingSystem=Unknown Operating System
|
||||
currentDevice=Current Device
|
||||
currentSession=Current Session
|
||||
lastAccess=Last access
|
||||
unknownOperatingSystem=Unknown operating system
|
||||
currentDevice=Current device
|
||||
currentSession=Current session
|
||||
signedOutSession=Signed out {0}/{1}
|
||||
lastAccessedOn=Last accessed on
|
||||
lastAccessedOn=Last accessed
|
||||
clients=Clients
|
||||
startedAt=Started at
|
||||
expiresAt=Expires at
|
||||
ipAddress=IP Address
|
||||
started=Started
|
||||
expires=Expires
|
||||
ipAddress=IP address
|
||||
|
||||
# Resources Page
|
||||
resourceName=Resource Name
|
||||
# Resources page
|
||||
resourceName=Resource name
|
||||
nextPage=Next
|
||||
previousPage=Previous
|
||||
firstPage=First Page
|
||||
firstPage=First page
|
||||
resourceSharedWith=Resource is shared with {0}
|
||||
and=\ and {0} other users
|
||||
add=Add
|
||||
share=Share
|
||||
shareWith=Share with
|
||||
edit=Edit
|
||||
close=Close
|
||||
unShare=Unshare all
|
||||
|
@ -57,75 +66,81 @@ resourceAlreadyShared=Resource is already shared with this user.
|
|||
resourceNotShared=This resource is not shared.
|
||||
permissionRequests=Permission requests
|
||||
permissions=Permissions
|
||||
selectPermissions=Select the permissions
|
||||
unShareAllConfirm=Are you sure you want to completely remove all shares?
|
||||
userNotFound=No user found with name or email {0}
|
||||
|
||||
# Linked Accounts Page
|
||||
linkedAccountsTitle=Linked Accounts
|
||||
# Linked accounts page
|
||||
linkedAccountsTitle=Linked accounts
|
||||
linkedAccountsIntroMessage=Manage logins through third-party accounts.
|
||||
linkedLoginProviders=Linked Login Providers
|
||||
unlinkedLoginProviders=Unlinked Login Providers
|
||||
linkedEmpty=No Linked Providers
|
||||
unlinkedEmpty=No Unlinked Providers
|
||||
socialLogin=Social Login
|
||||
systemDefined=System Defined
|
||||
link=Link Account
|
||||
unLink=Unlink Account
|
||||
linkedLoginProviders=Linked login providers
|
||||
unlinkedLoginProviders=Unlinked login providers
|
||||
linkedEmpty=No linked providers
|
||||
unlinkedEmpty=No unlinked providers
|
||||
socialLogin=Social login
|
||||
systemDefined=System defined
|
||||
link=Link account
|
||||
unLink=Unlink account
|
||||
|
||||
# Signing In Page
|
||||
signingIn=Signing In
|
||||
# Signing in page
|
||||
signingIn=Signing in
|
||||
signingInSubMessage=Configure ways to sign in.
|
||||
credentialCreatedAt=Created
|
||||
successRemovedMessage={0} was removed.
|
||||
stopUsingCred=Stop using {0}?
|
||||
changePassword=Change password
|
||||
removeCred=Remove {0}
|
||||
setUpNew=Set up {0}
|
||||
removeCredAriaLabel=Remove credential
|
||||
updateCredAriaLabel=Update credential
|
||||
notSetUp={0} is not set up.
|
||||
two-factor=Two-Factor Authentication
|
||||
two-factor=Two-factor authentication
|
||||
passwordless=Passwordless
|
||||
unknown=Unknown
|
||||
password-display-name=Password
|
||||
password-help-text=Log in by entering your password.
|
||||
password=My Password
|
||||
otp-display-name=Authenticator Application
|
||||
password=My password
|
||||
otp-display-name=authenticator application
|
||||
otp-help-text=Enter a verification code from authenticator application.
|
||||
recovery-authn-code=My Recovery Authentication Codes
|
||||
recovery-authn-codes-display-name=Recovery Authentication Codes
|
||||
recovery-authn-code=My 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-codes-number-used={0} recovery codes used
|
||||
recovery-codes-number-remaining={0} recovery codes remaining
|
||||
recovery-codes-generate-new-codes=Generate new codes to ensure access to your account
|
||||
webauthn-display-name=Security Key
|
||||
webauthn-help-text=Use your security key to sign in.
|
||||
webauthn-passwordless-display-name=Security Key
|
||||
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
|
||||
basic-authentication=Basic Authentication
|
||||
invalidRequestMessage=Invalid Request
|
||||
webauthn-display-name=Security key
|
||||
webauthn-help-text=Use your security key to log in.
|
||||
webauthn-passwordless-display-name=Security key
|
||||
webauthn-passwordless-help-text=Use your security key for passwordless log in.
|
||||
basic-authentication=Basic authentication
|
||||
invalidRequestMessage=Invalid request
|
||||
|
||||
# Applications page
|
||||
applicationsPageTitle=Applications
|
||||
internalApp=Internal
|
||||
thirdPartyApp=Third-party
|
||||
offlineAccess=Offline Access
|
||||
offlineAccess=Offline access
|
||||
inUse=In use
|
||||
notInUse=Not in use
|
||||
applicationDetails=Application Details
|
||||
applicationDetails=Application details
|
||||
client=Client
|
||||
description=Description
|
||||
baseUrl=URL
|
||||
accessGrantedOn=Access granted on
|
||||
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.
|
||||
confirmButton=Confirm
|
||||
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
|
||||
policy=Privacy policy
|
||||
applicationType=Application type
|
||||
status=Status
|
||||
|
||||
#Delete Account page
|
||||
#Delete account page
|
||||
doDelete=Delete
|
||||
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.
|
||||
|
||||
error-invalid-value=''{0}'' has invalid value.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"id": "personal-info",
|
||||
"path": "personal-info",
|
||||
"icon": "pf-icon-user",
|
||||
"label": "personalInfoHtmlTitle",
|
||||
"label": "personalInfoSidebarTitle",
|
||||
"descriptionLabel": "personalInfoIntroMessage",
|
||||
"modulePath": "/content/account-page/AccountPage.js",
|
||||
"componentName": "AccountPage"
|
||||
|
@ -11,27 +11,27 @@
|
|||
{
|
||||
"id": "security",
|
||||
"icon": "pf-icon-security",
|
||||
"label": "accountSecurityTitle",
|
||||
"label": "accountSecuritySidebarTitle",
|
||||
"descriptionLabel": "accountSecurityIntroMessage",
|
||||
"content": [
|
||||
{
|
||||
"id": "signingin",
|
||||
"path": "security/signingin",
|
||||
"label": "signingIn",
|
||||
"label": "signingInSidebarTitle",
|
||||
"modulePath": "/content/signingin-page/SigningInPage.js",
|
||||
"componentName": "SigningInPage"
|
||||
},
|
||||
{
|
||||
"id": "device-activity",
|
||||
"path": "security/device-activity",
|
||||
"label": "device-activity",
|
||||
"label": "deviceActivitySidebarTitle",
|
||||
"modulePath": "/content/device-activity-page/DeviceActivityPage.js",
|
||||
"componentName": "DeviceActivityPage"
|
||||
},
|
||||
{
|
||||
"id": "linked-accounts",
|
||||
"path": "security/linked-accounts",
|
||||
"label": "linkedAccountsHtmlTitle",
|
||||
"label": "linkedAccountsSidebarTitle",
|
||||
"modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js",
|
||||
"componentName": "LinkedAccountsPage",
|
||||
"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 {
|
||||
height: 35px;
|
||||
}
|
||||
|
@ -12,5 +13,114 @@
|
|||
width: 120px;
|
||||
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 {PageNav} from './PageNav';
|
||||
import {PageToolbar} from './PageToolbar';
|
||||
import {PageHeaderTool} from './PageHeaderTool';
|
||||
import {makeRoutes} from './ContentPages';
|
||||
|
||||
import {
|
||||
Brand,
|
||||
Page,
|
||||
PageHeader,
|
||||
PageSection,
|
||||
PageSidebar,
|
||||
PageSidebar
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { KeycloakContext } from './keycloak-service/KeycloakContext';
|
||||
|
@ -61,11 +60,11 @@ export class App extends React.Component<AppProps> {
|
|||
const username = (
|
||||
<span style={{marginLeft: '10px'}} id="loggedInUser">{loggedInUserName()}</span>
|
||||
);
|
||||
|
||||
const Header = (
|
||||
<PageHeader
|
||||
logo={<a id="brandLink" href={brandUrl}><Brand src={brandImg} alt="Logo" className="brand"/></a>}
|
||||
toolbar={<PageToolbar/>}
|
||||
avatar={username}
|
||||
headerTools={<PageHeaderTool/>}
|
||||
showNavToggle
|
||||
/>
|
||||
);
|
||||
|
@ -73,13 +72,9 @@ export class App extends React.Component<AppProps> {
|
|||
const Sidebar = <PageSidebar nav={<PageNav/>} />;
|
||||
|
||||
return (
|
||||
<span style={{ height: '100%'}}>
|
||||
<Page header={Header} sidebar={Sidebar} isManagedSidebar>
|
||||
<PageSection>
|
||||
{makeRoutes()}
|
||||
</PageSection>
|
||||
</Page>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -85,7 +85,8 @@ function createNavItems(activePage: PageDef, contentParam: ContentItem[], groupN
|
|||
groupId={item.groupId}
|
||||
key={item.groupId}
|
||||
title={Msg.localize(item.label, item.labelParams)}
|
||||
isExpanded={isChildOf(item, activePage)}>
|
||||
isExpanded={isChildOf(item, activePage)}
|
||||
>
|
||||
{createNavItems(activePage, item.content, groupNum + 1)}
|
||||
</NavExpandable>
|
||||
} 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 {
|
||||
const kebabDropdownItems = [];
|
||||
if (this.hasReferrer) {
|
||||
kebabDropdownItems.push(
|
||||
<ReferrerDropdownItem key='referrerDropdownItem'/>
|
||||
)
|
||||
}
|
||||
|
||||
kebabDropdownItems.push(<LogoutDropdownItem key='LogoutDropdownItem'/>);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
{this.hasReferrer &&
|
||||
<ToolbarGroup key='referrerGroup'>
|
||||
<ToolbarGroup key='referrerGroup' alignment={{default:"alignRight"}}>
|
||||
<ToolbarItem className="pf-m-icons" key='referrer'>
|
||||
<ReferrerLink/>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
}
|
||||
|
||||
<ToolbarGroup key='secondGroup'>
|
||||
<ToolbarGroup key='secondGroup' alignment={{default:"alignRight"}}>
|
||||
<ToolbarItem className="pf-m-icons" key='logout'>
|
||||
<LogoutButton/>
|
||||
</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>
|
||||
</Toolbar>
|
||||
);
|
||||
|
|
|
@ -97,7 +97,7 @@ export class ContentAlert extends React.Component<ContentAlertProps, ContentAler
|
|||
isLiveRegion
|
||||
variant={variant}
|
||||
title={message}
|
||||
action={
|
||||
actionClose={
|
||||
<AlertActionCloseButton
|
||||
title={message}
|
||||
variantLabel={`${variant} alert`}
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Grid, GridItem, Title, Tooltip} from '@patternfly/react-core';
|
||||
import {RedoIcon} from '@patternfly/react-icons';
|
||||
import {Button, Grid, GridItem, Text, Title, Tooltip, Card, CardBody, Stack, StackItem, PageSection, TextContent, PageSectionVariants, SplitItem, Split} from '@patternfly/react-core';
|
||||
import {RedoIcon, SyncAltIcon} from '@patternfly/react-icons';
|
||||
|
||||
import {Msg} from '../widgets/Msg';
|
||||
import {ContentAlert} from './ContentAlert';
|
||||
|
@ -40,26 +40,40 @@ export class ContentPage extends React.Component<ContentPageProps> {
|
|||
public render(): React.ReactNode {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ContentAlert/>
|
||||
<section id="page-heading" className="pf-c-page__main-section pf-m-light">
|
||||
<Grid>
|
||||
<GridItem span={11}><Title headingLevel='h1' size='3xl'><strong><Msg msgKey={this.props.title}/></strong></Title></GridItem>
|
||||
{this.props.onRefresh &&
|
||||
<GridItem span={1}>
|
||||
<Tooltip content={<Msg msgKey='refreshPage'/>}>
|
||||
<Button aria-describedby="refresh page" id='refresh-page' variant='plain' onClick={this.props.onRefresh}>
|
||||
<RedoIcon size='sm'/>
|
||||
<ContentAlert />
|
||||
|
||||
<PageSection variant={PageSectionVariants.light} className="pf-u-pb-xs">
|
||||
<Split>
|
||||
<SplitItem isFilled>
|
||||
<TextContent>
|
||||
<Title headingLevel="h1" size="2xl" className="pf-u-mb-xl">
|
||||
<Msg msgKey={this.props.title} />
|
||||
</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>
|
||||
</Tooltip>
|
||||
</GridItem>
|
||||
}
|
||||
{this.props.introMessage && <GridItem span={12}> <Msg msgKey={this.props.introMessage}/></GridItem>}
|
||||
</Grid>
|
||||
</section>
|
||||
|
||||
<section className="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
</SplitItem>
|
||||
)}
|
||||
</Split>
|
||||
</PageSection>
|
||||
{this.props.children}
|
||||
</section>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,21 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
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 { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
|
@ -144,27 +158,46 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
public render(): React.ReactNode {
|
||||
const fields: FormFields = this.state.formFields;
|
||||
return (
|
||||
<ContentPage title="personalInfoHtmlTitle"
|
||||
introMessage="personalSubMessage">
|
||||
<Form isHorizontal onSubmit={event => this.handleSubmit(event)}>
|
||||
{!this.isRegistrationEmailAsUsername &&
|
||||
<ContentPage
|
||||
title="personalInfoHtmlTitle"
|
||||
introMessage="personalSubMessage"
|
||||
>
|
||||
<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
|
||||
label={Msg.localize('username')}
|
||||
isRequired
|
||||
label={Msg.localize("username")}
|
||||
fieldId="user-name"
|
||||
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.RestrictedUsernameInput />}
|
||||
{!this.isEditUserNameAllowed && (
|
||||
<this.RestrictedUsernameInput />
|
||||
)}
|
||||
</FormGroup>
|
||||
}
|
||||
)}
|
||||
<FormGroup
|
||||
label={Msg.localize('email')}
|
||||
isRequired
|
||||
label={Msg.localize("email")}
|
||||
fieldId="email-address"
|
||||
helperTextInvalid={this.state.errors.email}
|
||||
isValid={this.state.errors.email === ''}
|
||||
validated={
|
||||
this.state.errors.email !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
|
@ -174,16 +207,22 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
maxLength={254}
|
||||
value={fields.email}
|
||||
onChange={this.handleChange}
|
||||
isValid={this.state.errors.email === ''}
|
||||
>
|
||||
</TextInput>
|
||||
validated={
|
||||
this.state.errors.email !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
></TextInput>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={Msg.localize('firstName')}
|
||||
isRequired
|
||||
label={Msg.localize("firstName")}
|
||||
fieldId="first-name"
|
||||
helperTextInvalid={this.state.errors.firstName}
|
||||
isValid={this.state.errors.firstName === ''}
|
||||
validated={
|
||||
this.state.errors.firstName !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
|
@ -193,16 +232,22 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
maxLength={254}
|
||||
value={fields.firstName}
|
||||
onChange={this.handleChange}
|
||||
isValid={this.state.errors.firstName === ''}
|
||||
>
|
||||
</TextInput>
|
||||
validated={
|
||||
this.state.errors.firstName !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
></TextInput>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={Msg.localize('lastName')}
|
||||
isRequired
|
||||
label={Msg.localize("lastName")}
|
||||
fieldId="last-name"
|
||||
helperTextInvalid={this.state.errors.lastName}
|
||||
isValid={this.state.errors.lastName === ''}
|
||||
validated={
|
||||
this.state.errors.lastName !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
|
@ -212,35 +257,52 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
maxLength={254}
|
||||
value={fields.lastName}
|
||||
onChange={this.handleChange}
|
||||
isValid={this.state.errors.lastName === ''}
|
||||
>
|
||||
</TextInput>
|
||||
validated={
|
||||
this.state.errors.lastName !== ""
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
></TextInput>
|
||||
</FormGroup>
|
||||
{features.isInternationalizationEnabled && <FormGroup
|
||||
label={Msg.localize('selectLocale')}
|
||||
{features.isInternationalizationEnabled && (
|
||||
<FormGroup
|
||||
label={Msg.localize("selectLocale")}
|
||||
isRequired
|
||||
fieldId="locale"
|
||||
>
|
||||
<LocaleSelector id="locale-selector"
|
||||
value={fields.attributes!.locale || ''}
|
||||
onChange={value => this.setState({
|
||||
<LocaleSelector
|
||||
id="locale-selector"
|
||||
value={fields.attributes!.locale || ""}
|
||||
onChange={(value) =>
|
||||
this.setState({
|
||||
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>
|
||||
<Button
|
||||
type="submit"
|
||||
id="save-btn"
|
||||
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" />
|
||||
</Button>
|
||||
<Button
|
||||
id="cancel-btn"
|
||||
variant="secondary"
|
||||
variant="link"
|
||||
onClick={this.handleCancel}
|
||||
>
|
||||
<Msg msgKey="doCancel" />
|
||||
|
@ -248,10 +310,10 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
</ActionGroup>
|
||||
</Form>
|
||||
|
||||
{ this.isDeleteAccountAllowed &&
|
||||
<div id="delete-account" style={{marginTop:"30px"}}>
|
||||
<Expandable toggleText={Msg.localize('deleteAccount')}>
|
||||
<Grid gutter={"sm"}>
|
||||
{this.isDeleteAccountAllowed && (
|
||||
<div id="delete-account" style={{ marginTop: "30px" }}>
|
||||
<ExpandableSection toggleText="Delete Account">
|
||||
<Grid hasGutter>
|
||||
<GridItem span={6}>
|
||||
<p>
|
||||
<Msg msgKey="deleteAccountWarning" />
|
||||
|
@ -259,17 +321,24 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
</GridItem>
|
||||
<GridItem span={4}>
|
||||
<KeycloakContext.Consumer>
|
||||
{ (keycloak: KeycloakService) => (
|
||||
<Button id="delete-account-btn" variant="danger" onClick={() => this.handleDelete(keycloak)} className="delete-button"><Msg msgKey="doDelete" /></Button>
|
||||
{(keycloak: KeycloakService) => (
|
||||
<Button
|
||||
id="delete-account-btn"
|
||||
variant="danger"
|
||||
onClick={() => this.handleDelete(keycloak)}
|
||||
className="delete-button"
|
||||
>
|
||||
<Msg msgKey="doDelete" />
|
||||
</Button>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
</GridItem>
|
||||
<GridItem span={2}>
|
||||
</GridItem>
|
||||
<GridItem span={2}></GridItem>
|
||||
</Grid>
|
||||
|
||||
</Expandable>
|
||||
</div>}
|
||||
</ExpandableSection>
|
||||
</div>
|
||||
)}
|
||||
</PageSection>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
@ -283,14 +352,14 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
|
|||
maxLength={254}
|
||||
value={this.state.formFields.username}
|
||||
onChange={this.handleChange}
|
||||
isValid={this.state.errors.username === ''}
|
||||
validated={this.state.errors.username !== '' ? ValidatedOptions.error : ValidatedOptions.default}
|
||||
>
|
||||
</TextInput>
|
||||
);
|
||||
|
||||
private RestrictedUsernameInput = () => (
|
||||
<TextInput
|
||||
isDisabled
|
||||
isReadOnly
|
||||
type="text"
|
||||
id="user-name"
|
||||
name="username"
|
||||
|
|
|
@ -23,12 +23,12 @@ import {Msg} from '../../widgets/Msg';
|
|||
|
||||
import {
|
||||
Title,
|
||||
TitleLevel,
|
||||
Button,
|
||||
EmptyState,
|
||||
EmptyStateVariant,
|
||||
EmptyStateIcon,
|
||||
EmptyStateBody
|
||||
EmptyStateBody,
|
||||
TitleSizes
|
||||
} from '@patternfly/react-core';
|
||||
import { PassportIcon } from '@patternfly/react-icons';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
|
@ -67,7 +67,7 @@ class ApplicationInitiatedActionPage extends React.Component<AppInitiatedActionP
|
|||
return (
|
||||
<EmptyState variant={EmptyStateVariant.full}>
|
||||
<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}/>
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
|
|
|
@ -24,12 +24,23 @@ import {
|
|||
DataListToggle,
|
||||
DataListContent,
|
||||
DataListItemCells,
|
||||
DescriptionList,
|
||||
DescriptionListTerm,
|
||||
DescriptionListGroup,
|
||||
DescriptionListDescription,
|
||||
Grid,
|
||||
GridItem,
|
||||
Button,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Stack,
|
||||
StackItem,
|
||||
SplitItem,
|
||||
Split,
|
||||
TextContent
|
||||
} 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 { ContinueCancelModal } from '../../widgets/ContinueCancelModal';
|
||||
import { HttpResponse } from '../../account-service/account.service';
|
||||
|
@ -118,12 +129,18 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<ContentPage title={Msg.localize('applicationsPageTitle')}>
|
||||
<DataList id="applications-list" aria-label={Msg.localize('applicationsPageTitle')} isCompact>
|
||||
<ContentPage
|
||||
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">
|
||||
<DataListItemRow>
|
||||
// invisible toggle allows headings to line up properly
|
||||
<span style={{ visibility: 'hidden' }}>
|
||||
<span style={{ visibility: 'hidden', height: 55 }}>
|
||||
<DataListToggle
|
||||
isExpanded={false}
|
||||
id='applications-list-header-invisible-toggle'
|
||||
|
@ -132,13 +149,13 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
</span>
|
||||
<DataListItemCells
|
||||
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>
|
||||
</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>
|
||||
</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>
|
||||
</DataListCell>,
|
||||
]}
|
||||
|
@ -148,7 +165,7 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
{this.state.applications.map((application: Application, appIndex: number) => {
|
||||
return (
|
||||
<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
|
||||
onClick={() => this.onToggle(appIndex)}
|
||||
isExpanded={this.state.isRowOpen[appIndex]}
|
||||
|
@ -156,9 +173,10 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
aria-controls={this.elementId("expandable", application)}
|
||||
/>
|
||||
<DataListItemCells
|
||||
className="pf-u-align-items-center"
|
||||
dataListCells={[
|
||||
<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/>
|
||||
</Button>
|
||||
</DataListCell>,
|
||||
|
@ -172,36 +190,46 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
|
||||
<DataListContent
|
||||
noPadding={false}
|
||||
className="pf-u-pl-35xl"
|
||||
hasNoPadding={false}
|
||||
aria-label={Msg.localize('applicationDetails')}
|
||||
id={this.elementId("expandable", application)}
|
||||
isHidden={!this.state.isRowOpen[appIndex]}
|
||||
>
|
||||
<Grid sm={6} md={6} lg={6}>
|
||||
<div className='pf-c-content'>
|
||||
<GridItem><strong>{Msg.localize('client') + ': '}</strong> {application.clientId}</GridItem>
|
||||
<DescriptionList>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{Msg.localize('client')}</DescriptionListTerm>
|
||||
<DescriptionListDescription>{application.clientId}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
{application.description &&
|
||||
<GridItem><strong>{Msg.localize('description') + ': '}</strong> {application.description}</GridItem>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{Msg.localize('description')}</DescriptionListTerm>
|
||||
<DescriptionListDescription>{application.description}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
}
|
||||
{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 &&
|
||||
<React.Fragment>
|
||||
<GridItem span={12}>
|
||||
<strong>Has access to:</strong>
|
||||
</GridItem>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>Has access to</DescriptionListTerm>
|
||||
{application.consent.grantedScopes.map((scope: GrantedScope, scopeIndex: number) => {
|
||||
return (
|
||||
<React.Fragment key={'scope-' + scopeIndex} >
|
||||
<GridItem offset={1}><CheckIcon /> {scope.name}</GridItem>
|
||||
<DescriptionListDescription><CheckIcon /> {scope.name}</DescriptionListDescription>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
{application.tosUri && <GridItem><strong>{Msg.localize('termsOfService') + ': '}</strong>{application.tosUri}</GridItem>}
|
||||
{application.policyUri && <GridItem><strong>{Msg.localize('policy') + ': '}</strong>{application.policyUri}</GridItem>}
|
||||
<GridItem><strong>{Msg.localize('accessGrantedOn') + ': '}</strong>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{Msg.localize('accessGrantedOn') + ': '}</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{new Intl.DateTimeFormat(locale, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
|
@ -210,14 +238,13 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
}).format(application.consent.createDate)}
|
||||
</GridItem>
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
{application.logoUri && <div className='pf-c-content'><img src={application.logoUri} /></div> }
|
||||
</Grid>
|
||||
</DescriptionList>
|
||||
{(application.consent || application.offlineAccess) &&
|
||||
<Grid gutter='sm'>
|
||||
<Grid hasGutter>
|
||||
<hr />
|
||||
<GridItem>
|
||||
<React.Fragment>
|
||||
|
@ -239,6 +266,8 @@ export class ApplicationsPage extends React.Component<ApplicationsPageProps, App
|
|||
)
|
||||
})}
|
||||
</DataList>
|
||||
</Stack>
|
||||
</PageSection>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,28 +21,30 @@ import { AccountServiceContext } from '../../account-service/AccountServiceConte
|
|||
import TimeUtil from '../../util/TimeUtil';
|
||||
|
||||
import {
|
||||
Bullseye,
|
||||
Button,
|
||||
DataList,
|
||||
DataListItem,
|
||||
DataListItemRow,
|
||||
DataListCell,
|
||||
DataListItemCells,
|
||||
DataListContent,
|
||||
DescriptionList,
|
||||
DescriptionListTerm,
|
||||
DescriptionListDescription,
|
||||
DescriptionListGroup,
|
||||
Grid,
|
||||
GridItem,
|
||||
Stack,
|
||||
StackItem
|
||||
Label,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Title,
|
||||
Tooltip,
|
||||
SplitItem,
|
||||
Split
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import {
|
||||
AmazonIcon,
|
||||
ChromeIcon,
|
||||
EdgeIcon,
|
||||
FirefoxIcon,
|
||||
GlobeIcon,
|
||||
InternetExplorerIcon,
|
||||
OperaIcon,
|
||||
SafariIcon,
|
||||
YandexInternationalIcon,
|
||||
DesktopIcon,
|
||||
MobileAltIcon,
|
||||
SyncAltIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
|
@ -161,22 +163,15 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
return TimeUtil.format(time * 1000);
|
||||
}
|
||||
|
||||
private elementId(item: string, session: Session): string {
|
||||
return `session-${session.id.substring(0,7)}-${item}`;
|
||||
private elementId(item: string, session: Session, element: string='session'): string {
|
||||
return `${element}-${session.id.substring(0,7)}-${item}`;
|
||||
}
|
||||
|
||||
private findBrowserIcon(session: Session): React.ReactNode {
|
||||
const browserName: string = session.browser.toLowerCase();
|
||||
if (browserName.includes("chrom")) return (<ChromeIcon id={this.elementId('icon-chrome', session)} size='lg'/>); // chrome or chromium
|
||||
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'/>);
|
||||
private findDeviceTypeIcon(session: Session, device: Device): React.ReactNode {
|
||||
const deviceType: boolean = device.mobile;
|
||||
if (deviceType === true) return (<MobileAltIcon id={this.elementId('icon-mobile', session, 'device')} />);
|
||||
|
||||
return (<GlobeIcon id={this.elementId('icon-default', session)} size='lg'/>);
|
||||
return (<DesktopIcon id={this.elementId('icon-desktop', session, 'device')} />);
|
||||
}
|
||||
|
||||
private findOS(device: Device): string {
|
||||
|
@ -220,74 +215,62 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
public render(): React.ReactNode {
|
||||
|
||||
return (
|
||||
<ContentPage title="device-activity" onRefresh={this.fetchDevices.bind(this)}>
|
||||
<Stack gutter="md">
|
||||
<StackItem isFilled>
|
||||
<DataList aria-label={Msg.localize('signedInDevices')}>
|
||||
<DataListItem key="SignedInDevicesHeader" aria-labelledby="signedInDevicesTitle" isExpanded={false}>
|
||||
<DataListItemRow>
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell key='signedInDevicesTitle' width={4}>
|
||||
<div id="signedInDevicesTitle" className="pf-c-content">
|
||||
<h2><Msg msgKey="signedInDevices"/></h2>
|
||||
<p>
|
||||
<Msg msgKey="signedInDevicesExplanation"/>
|
||||
</p>
|
||||
</div>
|
||||
</DataListCell>,
|
||||
<ContentPage
|
||||
title="device-activity"
|
||||
introMessage="signedInDevicesExplanation"
|
||||
>
|
||||
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||
<Split hasGutter className="pf-u-mb-lg">
|
||||
<SplitItem isFilled>
|
||||
<div id="signedInDevicesTitle" className="pf-c-content"><Title headingLevel="h2" size="xl"><Msg msgKey="signedInDevices"/></Title></div>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<Tooltip content={<Msg msgKey="refreshPage" />}>
|
||||
<Button
|
||||
aria-describedby="refresh page"
|
||||
id="refresh-page"
|
||||
variant="link"
|
||||
onClick={this.fetchDevices.bind(this)}
|
||||
icon={<SyncAltIcon />}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<KeycloakContext.Consumer>
|
||||
{ (keycloak: KeycloakService) => (
|
||||
<DataListCell key='signOutAllButton' width={1}>
|
||||
{this.isShowSignOutAll(this.state.devices) &&
|
||||
this.isShowSignOutAll(this.state.devices) &&
|
||||
<ContinueCancelModal buttonTitle='signOutAllDevices'
|
||||
buttonId='sign-out-all'
|
||||
modalTitle='signOutAllDevices'
|
||||
modalMessage='signOutAllDevicesWarning'
|
||||
onContinue={() => this.signOutAll(keycloak)}
|
||||
/>
|
||||
}
|
||||
</DataListCell>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
|
||||
<DataListItem aria-labelledby='sessions'>
|
||||
<DataListItemRow>
|
||||
<Grid gutter='sm'>
|
||||
<GridItem span={12} /> {/* <-- top spacing */}
|
||||
</SplitItem>
|
||||
</Split>
|
||||
<DataList className="signed-in-device-list" aria-label={Msg.localize('signedInDevices')}>
|
||||
<DataListItem aria-labelledby='sessions' id='device-activity-sessions'>
|
||||
{this.state.devices.map((device: Device, deviceIndex: number) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{device.sessions.map((session: Session, sessionIndex: number) => {
|
||||
return (
|
||||
<React.Fragment key={'device-' + deviceIndex + '-session-' + sessionIndex}>
|
||||
|
||||
<GridItem md={3}>
|
||||
<Stack>
|
||||
<StackItem isFilled={false}>
|
||||
<Bullseye>{this.findBrowserIcon(session)}</Bullseye>
|
||||
</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>
|
||||
<DataListItemRow>
|
||||
<DataListContent aria-label="device-sessions-content" isHidden={false} className="pf-u-flex-grow-1">
|
||||
<Grid className="signed-in-device-grid" hasGutter>
|
||||
<GridItem className="device-icon" span={1} rowSpan={2}>
|
||||
<span>{this.findDeviceTypeIcon(session, device)}</span>
|
||||
</GridItem>
|
||||
<GridItem md={9}>
|
||||
{!session.browser.toLowerCase().includes('unknown') &&
|
||||
<p id={this.elementId('browser', session)}><strong>{session.browser} / {this.findOS(device)} {this.findOSVersion(device)}</strong></p>}
|
||||
<p id={this.elementId('last-access', session)}><strong>{Msg.localize('lastAccessedOn')}</strong> {this.time(session.lastAccess)}</p>
|
||||
<p id={this.elementId('clients', session)}><strong>{Msg.localize('clients')}</strong> {this.makeClientsString(session.clients)}</p>
|
||||
<p id={this.elementId('started', session)}><strong>{Msg.localize('startedAt')}</strong> {this.time(session.started)}</p>
|
||||
<p id={this.elementId('expires', session)}><strong>{Msg.localize('expiresAt')}</strong> {this.time(session.expires)}</p>
|
||||
<GridItem sm={8} md={9} span={10}>
|
||||
<span id={this.elementId('browser', session)} className="pf-u-mr-md">{this.findOS(device)} {this.findOSVersion(device)} / {session.browser}</span>
|
||||
{session.current &&
|
||||
<Label color="green"><Msg msgKey="currentSession" /></Label>}
|
||||
</GridItem>
|
||||
<GridItem className="pf-u-text-align-right" sm={3} md={2} span={1}>
|
||||
{!session.current &&
|
||||
<ContinueCancelModal buttonTitle='doSignOut'
|
||||
buttonId={this.elementId('sign-out', session)}
|
||||
|
@ -297,23 +280,43 @@ export class DeviceActivityPage extends React.Component<DeviceActivityPageProps,
|
|||
onContinue={() => this.signOutSession(device, session)}
|
||||
/>
|
||||
}
|
||||
|
||||
</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>
|
||||
)
|
||||
})}
|
||||
<GridItem span={12} /> {/* <-- bottom spacing */}
|
||||
</Grid>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
</DataList>
|
||||
</StackItem>
|
||||
|
||||
</Stack>
|
||||
</PageSection>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,35 +18,31 @@ import * as React from 'react';
|
|||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
DataList,
|
||||
DataListAction,
|
||||
DataListItemCells,
|
||||
DataListCell,
|
||||
DataListItemRow,
|
||||
Divider,
|
||||
Label,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Split,
|
||||
SplitItem,
|
||||
Stack,
|
||||
StackItem,
|
||||
Title,
|
||||
TitleLevel,
|
||||
DataListItem,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import {
|
||||
BitbucketIcon,
|
||||
CubeIcon,
|
||||
FacebookIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
GoogleIcon,
|
||||
InstagramIcon,
|
||||
LinkIcon,
|
||||
LinkedinIcon,
|
||||
MicrosoftIcon,
|
||||
OpenshiftIcon,
|
||||
PaypalIcon,
|
||||
StackOverflowIcon,
|
||||
TwitterIcon,
|
||||
UnlinkIcon
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
|
@ -128,25 +124,26 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
|
||||
return (
|
||||
<ContentPage title={Msg.localize('linkedAccountsTitle')} introMessage={Msg.localize('linkedAccountsIntroMessage')}>
|
||||
<Stack gutter='md'>
|
||||
<StackItem isFilled>
|
||||
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
||||
<PageSection isFilled variant={PageSectionVariants.light}>
|
||||
<Stack hasGutter>
|
||||
<StackItem>
|
||||
<Title headingLevel="h2" className="pf-u-mb-lg" size='xl'>
|
||||
<Msg msgKey='linkedLoginProviders'/>
|
||||
</Title>
|
||||
<DataList id="linked-idps" aria-label='foo'>
|
||||
<DataList id="linked-idps" aria-label={Msg.localize('linkedLoginProviders')}>
|
||||
{this.makeRows(this.state.linkedAccounts, true)}
|
||||
</DataList>
|
||||
</StackItem>
|
||||
<StackItem isFilled/>
|
||||
<StackItem isFilled>
|
||||
<Title headingLevel={TitleLevel.h2} size='2xl'>
|
||||
<StackItem>
|
||||
<Title headingLevel="h2" className="pf-u-mt-xl pf-u-mb-lg" size='xl'>
|
||||
<Msg msgKey='unlinkedLoginProviders'/>
|
||||
</Title>
|
||||
<DataList id="unlinked-idps" aria-label='foo'>
|
||||
<DataList id="unlinked-idps" aria-label={Msg.localize('unlinkedLoginProviders')}>
|
||||
{this.makeRows(this.state.unLinkedAccounts, false)}
|
||||
</DataList>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
</PageSection>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
@ -160,10 +157,10 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
}
|
||||
|
||||
return (
|
||||
<DataListItem key='emptyItem' aria-labelledby="empty-item">
|
||||
<DataListItem key='emptyItem' aria-labelledby={Msg.localize('isEmptyMessage')}>
|
||||
<DataListItemRow key='emptyRow'>
|
||||
<DataListItemCells dataListCells={[
|
||||
<DataListCell key='empty'><strong>{isEmptyMessage}</strong></DataListCell>
|
||||
<DataListCell key='empty'>{isEmptyMessage}</DataListCell>
|
||||
]}/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
|
@ -179,15 +176,28 @@ class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, Linked
|
|||
<> {
|
||||
|
||||
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}>
|
||||
<DataListItemCells
|
||||
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='badge'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-badge`} isFilled>{this.badge(account)}</StackItem></Stack></DataListCell>,
|
||||
<DataListCell key='username'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-username`} isFilled>{account.linkedUsername}</StackItem></Stack></DataListCell>,
|
||||
<DataListCell key='idp'>
|
||||
<Split>
|
||||
<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-link`} variant='link' onClick={() => this.linkAccount(account)}><LinkIcon size='sm'/> <Msg msgKey='link'/></Button>}
|
||||
</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) {
|
||||
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 {
|
||||
const socialIconId = `${account.providerAlias}-idp-icon-social`;
|
||||
if (account.providerName.toLowerCase().includes('github')) return (<GithubIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('linkedin')) return (<LinkedinIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('facebook')) return (<FacebookIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('google')) return (<GoogleIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('instagram')) return (<InstagramIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('microsoft')) return (<MicrosoftIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('bitbucket')) return (<BitbucketIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('twitter')) return (<TwitterIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('openshift')) return (<OpenshiftIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('gitlab')) return (<GitlabIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('paypal')) return (<PaypalIcon id={socialIconId} size='xl'/>);
|
||||
if (account.providerName.toLowerCase().includes('stackoverflow')) return (<StackOverflowIcon id={socialIconId} size='xl'/>);
|
||||
|
||||
return (<CubeIcon id={`${account.providerAlias}-idp-icon-default`} size='xl'/>);
|
||||
console.log(account);
|
||||
switch (true) {
|
||||
case account.providerName.toLowerCase().includes('bitbucket'):
|
||||
return <BitbucketIcon id={socialIconId} size='lg'/>;
|
||||
case account.providerName.toLowerCase().includes('openshift'):
|
||||
return <div className="idp-icon-social" id="openshift-idp-icon-social" />;
|
||||
case account.providerName.toLowerCase().includes('gitlab'):
|
||||
return <GitlabIcon id={socialIconId} size='lg'/>;
|
||||
case account.providerName.toLowerCase().includes('paypal'):
|
||||
return <PaypalIcon id={socialIconId} size='lg'/>;
|
||||
case (account.providerName !== '' && account.social):
|
||||
return <div className="idp-icon-social" id={socialIconId}/>;
|
||||
default:
|
||||
return <CubeIcon id={`${account.providerAlias}-idp-icon-default`} size='lg'/>;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -22,11 +22,12 @@ import {
|
|||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
InputGroup
|
||||
InputGroup,
|
||||
ModalVariant
|
||||
} from '@patternfly/react-core';
|
||||
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 { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { ContentAlert } from '../ContentAlert';
|
||||
|
@ -45,7 +46,7 @@ interface EditTheResourceState {
|
|||
}
|
||||
|
||||
export class EditTheResource extends React.Component<EditTheResourceProps, EditTheResourceState> {
|
||||
protected static defaultProps = { permissions: [] };
|
||||
protected static defaultProps:Permissions = { permissions: [] };
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
|
@ -91,7 +92,7 @@ export class EditTheResource extends React.Component<EditTheResourceProps, EditT
|
|||
|
||||
<Modal
|
||||
title={'Edit the resource - ' + this.props.resource.name}
|
||||
isLarge
|
||||
variant={ModalVariant.large}
|
||||
isOpen={this.state.isOpen}
|
||||
onClose={this.handleToggleDialog}
|
||||
actions={[
|
||||
|
|
|
@ -18,7 +18,18 @@ import * as React from 'react';
|
|||
|
||||
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 {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 {
|
||||
return (
|
||||
<Tab id={title} eventKey={eventKey} title={Msg.localize(title)}>
|
||||
<Stack gutter="md">
|
||||
<Stack hasGutter>
|
||||
<StackItem isFilled><span/></StackItem>
|
||||
<StackItem isFilled>
|
||||
<Level gutter='md'>
|
||||
<Level hasGutter>
|
||||
<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>
|
||||
</Level>
|
||||
</StackItem>
|
||||
|
@ -191,12 +202,13 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
|||
public render(): React.ReactNode {
|
||||
return (
|
||||
<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(1, 'sharedwithMe', this.state.sharedWithMe, true)}
|
||||
</Tabs>
|
||||
|
||||
<Level gutter='md'>
|
||||
<Level hasGutter>
|
||||
<LevelItem>
|
||||
{this.hasPrevious() && <Button onClick={this.handlePreviousClick}><<Msg msgKey='previousPage'/></Button>}
|
||||
</LevelItem>
|
||||
|
@ -209,6 +221,7 @@ export class MyResourcesPage extends React.Component<MyResourcesPageProps, MyRes
|
|||
{this.hasNext() && <Button onClick={this.handleNextClick}><Msg msgKey='nextPage'/>></Button>}
|
||||
</LevelItem>
|
||||
</Level>
|
||||
</PageSection>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ import {
|
|||
DataListCell,
|
||||
Chip,
|
||||
Split,
|
||||
SplitItem
|
||||
SplitItem,
|
||||
ModalVariant
|
||||
} from '@patternfly/react-core';
|
||||
import { UserCheckIcon } from '@patternfly/react-icons';
|
||||
|
||||
|
@ -35,7 +36,7 @@ import { HttpResponse } from '../../account-service/account.service';
|
|||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
import { ContentAlert } from '../ContentAlert';
|
||||
import { Resource, Scope, Permission } from './resource-model';
|
||||
import { Resource, Scope, Permission, Permissions } from './resource-model';
|
||||
|
||||
|
||||
interface PermissionRequestProps {
|
||||
|
@ -48,7 +49,7 @@ interface 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;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
|
@ -107,7 +108,7 @@ export class PermissionRequest extends React.Component<PermissionRequestProps, P
|
|||
<Modal
|
||||
id={`modal-${id}`}
|
||||
title={Msg.localize('permissionRequests') + ' - ' + this.props.resource.name}
|
||||
isLarge={true}
|
||||
variant={ModalVariant.large}
|
||||
isOpen={this.state.isOpen}
|
||||
onClose={this.handleToggleDialog}
|
||||
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>)}
|
||||
</DataListCell>,
|
||||
<DataListCell key={`actions${i}`}>
|
||||
<Split gutter="sm">
|
||||
<Split hasGutter>
|
||||
<SplitItem>
|
||||
<Button
|
||||
id={`accept-${i}-${id}`}
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
|||
|
||||
import { Select, SelectOption, SelectVariant, SelectOptionObject } from '@patternfly/react-core';
|
||||
import { Scope } from './resource-model';
|
||||
import { Msg } from '../../widgets/Msg';
|
||||
|
||||
interface PermissionSelectState {
|
||||
selected: ScopeValue[];
|
||||
|
@ -86,19 +87,19 @@ export class PermissionSelect extends React.Component<PermissionSelectProps, Per
|
|||
return (
|
||||
<div>
|
||||
<span id={titleId} hidden>
|
||||
Select the permissions
|
||||
<Msg msgKey='selectPermissions' />
|
||||
</span>
|
||||
<Select
|
||||
direction={this.props.direction || 'down'}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
ariaLabelTypeAhead="Select the permissions"
|
||||
typeAheadAriaLabel={Msg.localize("selectPermissions")}
|
||||
onToggle={this.onToggle}
|
||||
onSelect={this.onSelect}
|
||||
onClear={this.clearSelection}
|
||||
selections={selected}
|
||||
isExpanded={isExpanded}
|
||||
ariaLabelledBy={titleId}
|
||||
placeholderText="Select the permissions"
|
||||
isOpen={isExpanded}
|
||||
aria-labelledby={titleId}
|
||||
placeholderText={Msg.localize("selectPermissions")}
|
||||
>
|
||||
{this.state.scopes}
|
||||
</Select>
|
||||
|
|
|
@ -28,7 +28,6 @@ import {
|
|||
LevelItem,
|
||||
Button,
|
||||
DataListAction,
|
||||
DataListActionVisibility,
|
||||
Dropdown,
|
||||
DropdownPosition,
|
||||
DropdownItem,
|
||||
|
@ -169,7 +168,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
|||
]}
|
||||
/>
|
||||
<DataListAction
|
||||
className={DataListActionVisibility.hiddenOnLg}
|
||||
visibility={{ lg: 'hidden' }}
|
||||
aria-labelledby="check-action-item3 check-action-action3"
|
||||
id="check-action-action3"
|
||||
aria-label="Actions"
|
||||
|
@ -246,7 +245,7 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
|||
</DataListAction>
|
||||
<DataListAction
|
||||
id={`actions-${row}`}
|
||||
className={css(DataListActionVisibility.visibleOnLg, DataListActionVisibility.hidden)}
|
||||
visibility={{ default: 'hidden', lg: 'visible' }}
|
||||
aria-labelledby="Row actions"
|
||||
aria-label="Actions"
|
||||
>
|
||||
|
@ -319,15 +318,14 @@ export class ResourcesTable extends AbstractResourcesTable<CollapsibleResourcesT
|
|||
]}
|
||||
/>
|
||||
</DataListAction>
|
||||
|
||||
</DataListItemRow>
|
||||
<DataListContent
|
||||
noPadding={false}
|
||||
hasNoPadding={false}
|
||||
aria-label="Session Details"
|
||||
id={'ex-expand' + row}
|
||||
isHidden={!this.state.isRowOpen[row]}
|
||||
>
|
||||
<Level gutter='md'>
|
||||
<Level hasGutter>
|
||||
<LevelItem><span /></LevelItem>
|
||||
<LevelItem id={'shared-with-user-message-' + row}>{this.sharedWithUsersMessage(row)}</LevelItem>
|
||||
<LevelItem><span /></LevelItem>
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
Button,
|
||||
Chip,
|
||||
ChipGroup,
|
||||
ChipGroupToolbarItem,
|
||||
Form,
|
||||
FormGroup,
|
||||
Gallery,
|
||||
|
@ -28,7 +27,8 @@ import {
|
|||
Modal,
|
||||
Stack,
|
||||
StackItem,
|
||||
TextInput
|
||||
TextInput,
|
||||
ModalVariant
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { AccountServiceContext } from '../../account-service/AccountServiceContext';
|
||||
|
@ -57,7 +57,7 @@ interface ShareTheResourceState {
|
|||
* @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
|
||||
*/
|
||||
export class ShareTheResource extends React.Component<ShareTheResourceProps, ShareTheResourceState> {
|
||||
protected static defaultProps = {permissions: []};
|
||||
protected static defaultProps:any = {permissions: []};
|
||||
static contextType = AccountServiceContext;
|
||||
context: React.ContextType<typeof AccountServiceContext>;
|
||||
|
||||
|
@ -166,7 +166,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
|
||||
<Modal
|
||||
title={'Share the resource - ' + this.props.resource.name}
|
||||
isLarge={true}
|
||||
variant={ModalVariant.large}
|
||||
isOpen={this.state.isOpen}
|
||||
onClose={this.handleToggleDialog}
|
||||
actions={[
|
||||
|
@ -178,7 +178,7 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
</Button>
|
||||
]}
|
||||
>
|
||||
<Stack gutter='md'>
|
||||
<Stack hasGutter>
|
||||
<StackItem isFilled>
|
||||
<Form>
|
||||
<FormGroup
|
||||
|
@ -187,13 +187,11 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
helperTextInvalid={Msg.localize('resourceAlreadyShared')}
|
||||
fieldId="username"
|
||||
isRequired
|
||||
isValid={!this.isAlreadyShared()}
|
||||
>
|
||||
<Gallery gutter='sm'>
|
||||
<Gallery hasGutter>
|
||||
<GalleryItem>
|
||||
<TextInput
|
||||
value={this.state.usernameInput}
|
||||
isValid={!this.isAlreadyShared()}
|
||||
id="username"
|
||||
aria-describedby="username-helper"
|
||||
placeholder="Username or email"
|
||||
|
@ -208,14 +206,12 @@ export class ShareTheResource extends React.Component<ShareTheResourceProps, Sha
|
|||
</GalleryItem>
|
||||
|
||||
</Gallery>
|
||||
<ChipGroup withToolbar>
|
||||
<ChipGroupToolbarItem key='users-selected' categoryName='Share with '>
|
||||
<ChipGroup categoryName={Msg.localize('shareWith')}>
|
||||
{this.state.usernames.map((currentChip: string) => (
|
||||
<Chip key={currentChip} onClick={() => this.handleDeleteUsername(currentChip)}>
|
||||
{currentChip}
|
||||
</Chip>
|
||||
))}
|
||||
</ChipGroupToolbarItem>
|
||||
</ChipGroup>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
DataListCell,
|
||||
DataListItemCells,
|
||||
ChipGroup,
|
||||
ChipGroupToolbarItem,
|
||||
Chip
|
||||
} from '@patternfly/react-core';
|
||||
import { RepositoryIcon } from '@patternfly/react-icons';
|
||||
|
@ -85,8 +84,7 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
|||
</DataListCell>,
|
||||
<DataListCell key={'permissions-' + row} width={2}>
|
||||
{ resource.scopes.length > 0 &&
|
||||
<ChipGroup withToolbar>
|
||||
<ChipGroupToolbarItem key='permissions' categoryName={Msg.localize('permissions')}>
|
||||
<ChipGroup categoryName={Msg.localize('permissions')}>
|
||||
{
|
||||
resource.scopes.map(scope => (
|
||||
<Chip key={scope.name} isReadOnly>
|
||||
|
@ -94,13 +92,11 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
|||
</Chip>
|
||||
))
|
||||
}
|
||||
</ChipGroupToolbarItem>
|
||||
</ChipGroup>}
|
||||
</DataListCell>,
|
||||
<DataListCell key={'pending-' + row} width={2}>
|
||||
{resource.shareRequests.length > 0 &&
|
||||
<ChipGroup withToolbar>
|
||||
<ChipGroupToolbarItem key='permissions' categoryName={Msg.localize('pending')}>
|
||||
<ChipGroup categoryName={Msg.localize('pending')}>
|
||||
{
|
||||
(resource.shareRequests[0].scopes as Scope[]).map(scope => (
|
||||
<Chip key={scope.name} isReadOnly>
|
||||
|
@ -108,7 +104,6 @@ export class SharedResourcesTable extends AbstractResourcesTable<ResourcesTableS
|
|||
</Chip>
|
||||
))
|
||||
}
|
||||
</ChipGroupToolbarItem>
|
||||
</ChipGroup>
|
||||
}
|
||||
</DataListCell>
|
||||
|
|
|
@ -38,3 +38,8 @@ export interface Permission {
|
|||
scopes: Scope[] | string[]; // this should be Scope[] - fix API
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface Permissions {
|
||||
permissions: Permission[];
|
||||
row?: number;
|
||||
}
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
* 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 {
|
||||
Alert,
|
||||
Button,
|
||||
DataList,
|
||||
DataListAction,
|
||||
|
@ -25,28 +26,34 @@ import {
|
|||
DataListCell,
|
||||
DataListItem,
|
||||
DataListItemRow,
|
||||
Stack,
|
||||
StackItem,
|
||||
EmptyState,
|
||||
EmptyStateVariant,
|
||||
EmptyStateBody,
|
||||
Split,
|
||||
SplitItem,
|
||||
Title,
|
||||
TitleLevel,
|
||||
DataListActionVisibility,
|
||||
Dropdown,
|
||||
DropdownPosition,
|
||||
KebabToggle,
|
||||
} from '@patternfly/react-core';
|
||||
PageSection,
|
||||
PageSectionVariants
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import {AIACommand} from '../../util/AIACommand';
|
||||
import TimeUtil from '../../util/TimeUtil';
|
||||
import {HttpResponse, AccountServiceClient} from '../../account-service/account.service';
|
||||
import {AccountServiceContext} from '../../account-service/AccountServiceContext';
|
||||
import {ContinueCancelModal} from '../../widgets/ContinueCancelModal';
|
||||
import {Features} from '../../widgets/features';
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
import {ContentPage} from '../ContentPage';
|
||||
import {ContentAlert} from '../ContentAlert';
|
||||
import { KeycloakContext } from '../../keycloak-service/KeycloakContext';
|
||||
import { KeycloakService } from '../../keycloak-service/keycloak.service';
|
||||
import { css } from '@patternfly/react-styles';
|
||||
import { AIACommand } from "../../util/AIACommand";
|
||||
import TimeUtil from "../../util/TimeUtil";
|
||||
import {
|
||||
HttpResponse,
|
||||
AccountServiceClient,
|
||||
} from "../../account-service/account.service";
|
||||
import { AccountServiceContext } from "../../account-service/AccountServiceContext";
|
||||
import { ContinueCancelModal } from "../../widgets/ContinueCancelModal";
|
||||
import { Features } from "../../widgets/features";
|
||||
import { Msg } from "../../widgets/Msg";
|
||||
import { ContentPage } from "../ContentPage";
|
||||
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;
|
||||
|
||||
|
@ -55,7 +62,7 @@ interface PasswordDetails {
|
|||
lastUpdate: number;
|
||||
}
|
||||
|
||||
type CredCategory = 'password' | 'two-factor' | 'passwordless';
|
||||
type CredCategory = "password" | "two-factor" | "passwordless";
|
||||
type CredType = string;
|
||||
type CredTypeMap = Map<CredType, CredentialContainer>;
|
||||
type CredContainerMap = Map<CredCategory, CredTypeMap>;
|
||||
|
@ -89,8 +96,7 @@ interface CredentialContainer {
|
|||
open: boolean;
|
||||
}
|
||||
|
||||
interface SigningInPageProps extends RouteComponentProps {
|
||||
}
|
||||
interface SigningInPageProps extends RouteComponentProps {}
|
||||
|
||||
interface SigningInPageState {
|
||||
// Credential containers organized by category then type
|
||||
|
@ -100,28 +106,33 @@ interface SigningInPageState {
|
|||
/**
|
||||
* @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;
|
||||
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);
|
||||
this.context = context;
|
||||
|
||||
this.state = {
|
||||
credentialContainers: new Map(),
|
||||
}
|
||||
};
|
||||
|
||||
this.getCredentialContainers();
|
||||
}
|
||||
|
||||
private getCredentialContainers(): void {
|
||||
this.context!.doGet("/credentials")
|
||||
.then((response: HttpResponse<CredentialContainer[]>) => {
|
||||
|
||||
this.context!.doGet("/credentials").then(
|
||||
(response: HttpResponse<CredentialContainer[]>) => {
|
||||
const allContainers: CredContainerMap = new Map();
|
||||
const containers: CredentialContainer[] = response.data || [];
|
||||
containers.forEach(container => {
|
||||
containers.forEach((container) => {
|
||||
let categoryMap = allContainers.get(container.category);
|
||||
if (!categoryMap) {
|
||||
categoryMap = new Map();
|
||||
|
@ -130,61 +141,81 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
categoryMap.set(container.type, container);
|
||||
});
|
||||
|
||||
this.setState({credentialContainers: allContainers});
|
||||
console.log({allContainers})
|
||||
});
|
||||
this.setState({ credentialContainers: allContainers });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private handleRemove = (credentialId: string, userLabel: string) => {
|
||||
this.context!.doDelete("/credentials/" + credentialId)
|
||||
.then(() => {
|
||||
this.context!.doDelete("/credentials/" + credentialId).then(() => {
|
||||
this.getCredentialContainers();
|
||||
ContentAlert.success('successRemovedMessage', [userLabel]);
|
||||
ContentAlert.success("successRemovedMessage", [userLabel]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static credElementId(credType: CredType, credId: string, item: string): string {
|
||||
return `${credType}-${item}-${credId.substring(0,8)}`;
|
||||
public static credElementId(
|
||||
credType: CredType,
|
||||
credId: string,
|
||||
item: string
|
||||
): string {
|
||||
return `${credType}-${item}-${credId.substring(0, 8)}`;
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<ContentPage title="signingIn"
|
||||
introMessage="signingInSubMessage">
|
||||
<Stack gutter='md'>
|
||||
<ContentPage title="signingIn" introMessage="signingInSubMessage">
|
||||
{this.renderCategories()}
|
||||
</Stack>
|
||||
</ContentPage>
|
||||
);
|
||||
}
|
||||
|
||||
private renderCategories(): React.ReactNode {
|
||||
return (<> {
|
||||
Array.from(this.state.credentialContainers.keys()).map(category => (
|
||||
<StackItem key={category} isFilled>
|
||||
<Title id={`${category}-categ-title`} headingLevel={TitleLevel.h2} size='2xl'>
|
||||
<strong><Msg msgKey={category}/></strong>
|
||||
return Array.from(this.state.credentialContainers.keys()).map(
|
||||
(category) => (
|
||||
<PageSection key={category} variant={PageSectionVariants.light}>
|
||||
<Title
|
||||
id={`${category}-categ-title`}
|
||||
headingLevel="h2"
|
||||
size="xl"
|
||||
>
|
||||
<Msg msgKey={category} />
|
||||
</Title>
|
||||
<DataList aria-label='foo'>
|
||||
{this.renderTypes(this.state.credentialContainers.get(category)!)}
|
||||
</DataList>
|
||||
</StackItem>
|
||||
))
|
||||
|
||||
}</>)
|
||||
{this.renderTypes(category!)}
|
||||
</PageSection>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private renderTypes(credTypeMap: CredTypeMap): React.ReactNode {
|
||||
private renderTypes(category: CredCategory): React.ReactNode {
|
||||
let credTypeMap: CredTypeMap = this.state.credentialContainers.get(
|
||||
category
|
||||
)!;
|
||||
|
||||
return (
|
||||
<KeycloakContext.Consumer>
|
||||
{ keycloak => (
|
||||
<>{
|
||||
Array.from(credTypeMap.keys()).map((credType: CredType, index: number, typeArray: string[]) => ([
|
||||
this.renderCredTypeTitle(credTypeMap.get(credType)!, keycloak!),
|
||||
this.renderUserCredentials(credTypeMap, credType, keycloak!),
|
||||
this.renderEmptyRow(credTypeMap.get(credType)!.type, index === typeArray.length - 1)
|
||||
]))
|
||||
}</>
|
||||
{(keycloak) => (
|
||||
<>
|
||||
{Array.from(
|
||||
credTypeMap.keys()
|
||||
).map(
|
||||
(
|
||||
credType: CredType,
|
||||
index: number,
|
||||
typeArray: string[]
|
||||
) => [
|
||||
this.renderCredTypeTitle(
|
||||
credTypeMap.get(credType)!,
|
||||
keycloak!,
|
||||
category
|
||||
),
|
||||
this.renderUserCredentials(
|
||||
credTypeMap,
|
||||
credType,
|
||||
keycloak!
|
||||
),
|
||||
]
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</KeycloakContext.Consumer>
|
||||
);
|
||||
|
@ -194,15 +225,21 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
if (isLast) return; // don't put empty row at the end
|
||||
|
||||
return (
|
||||
<DataListItem aria-labelledby={'empty-list-item-' + type}>
|
||||
<DataListItemRow key={'empty-row-' + type}>
|
||||
<DataListItemCells dataListCells={[<DataListCell></DataListCell>]}/>
|
||||
<DataListItem aria-labelledby={"empty-list-item-" + type}>
|
||||
<DataListItemRow key={"empty-row-" + type}>
|
||||
<DataListItemCells
|
||||
dataListCells={[<DataListCell></DataListCell>]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</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 userCredentialMetadatas: CredMetadata[] = credContainer.userCredentialMetadatas;
|
||||
const removeable: boolean = credContainer.removeable;
|
||||
|
@ -212,17 +249,22 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
if (!userCredentialMetadatas || userCredentialMetadatas.length === 0) {
|
||||
const localizedDisplayName = Msg.localize(displayName);
|
||||
return (
|
||||
<DataListItem key='no-credentials-list-item' aria-labelledby='no-credentials-list-item'>
|
||||
<DataListItemRow key='no-credentials-list-item-row'>
|
||||
<DataList aria-label={Msg.localize('notSetUp', [localizedDisplayName])} className="pf-u-mb-xl">
|
||||
<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
|
||||
dataListCells={[
|
||||
<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'}/>
|
||||
]}
|
||||
/>
|
||||
]}/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
</DataList>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -239,11 +281,24 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
updateAIA = new AIACommand(keycloak, credContainer.updateAction);
|
||||
}
|
||||
|
||||
let maxWidth = { maxWidth: 689 } as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<React.Fragment key='userCredentialMetadatas'> {
|
||||
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}>
|
||||
<DataListItemRow key={'userCredentialRow-' + credentialMetadata.credential.id}>
|
||||
<DataListItemRow key={'userCredentialRow-' + credentialMetadata.credential.id} className="pf-u-align-items-center">
|
||||
<DataListItemCells dataListCells={this.credentialRowCells(credentialMetadata, type)}/>
|
||||
<CredentialAction
|
||||
credential={credentialMetadata.credential}
|
||||
|
@ -253,125 +308,164 @@ class SigningInPage extends React.Component<SigningInPageProps, SigningInPageSta
|
|||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
</DataList>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</React.Fragment>)
|
||||
} </React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
private credentialRowCells(credMetadata: CredMetadata, type: string): React.ReactNode[] {
|
||||
const credRowCells: React.ReactNode[] = [];
|
||||
const credential = credMetadata.credential;
|
||||
const infoMessage = credMetadata.infoMessage ? JSON.parse(credMetadata.infoMessage) : null;
|
||||
const warningMessageTitle = credMetadata.warningMessageTitle ? JSON.parse(credMetadata.warningMessageTitle) : null;
|
||||
const warningMessageDescription = credMetadata.warningMessageDescription ? JSON.parse(credMetadata.warningMessageDescription) : null;
|
||||
let maxWidth = { "--pf-u-max-width--MaxWidth": "300px" } as React.CSSProperties;
|
||||
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}
|
||||
{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>
|
||||
);
|
||||
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(<DataListCell key={'spacer-' + credential.id}/>);
|
||||
credRowCells.push(
|
||||
<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;
|
||||
}
|
||||
|
||||
private renderCredTypeTitle(credContainer: CredentialContainer, keycloak: KeycloakService): React.ReactNode {
|
||||
if (!credContainer.hasOwnProperty('helptext') && !credContainer.hasOwnProperty('createAction')) return;
|
||||
private renderCredTypeTitle(
|
||||
credContainer: CredentialContainer,
|
||||
keycloak: KeycloakService,
|
||||
category: CredCategory
|
||||
): React.ReactNode {
|
||||
|
||||
if (
|
||||
!credContainer.hasOwnProperty("helptext") &&
|
||||
!credContainer.hasOwnProperty("createAction")
|
||||
)
|
||||
return;
|
||||
|
||||
let setupAction: AIACommand;
|
||||
if (credContainer.createAction) {
|
||||
setupAction = new AIACommand(keycloak, credContainer.createAction);
|
||||
}
|
||||
const credContainerDisplayName: string = Msg.localize(credContainer.displayName);
|
||||
|
||||
const credContainerDisplayName: string = Msg.localize(
|
||||
credContainer.displayName
|
||||
);
|
||||
return (
|
||||
<React.Fragment key={'credTypeTitle-' + credContainer.type}>
|
||||
<DataListItem aria-labelledby={'type-datalistitem-' + credContainer.type}>
|
||||
<DataListItemRow key={'credTitleRow-' + credContainer.type}>
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell width={5} key={'credTypeTitle-' + credContainer.type}>
|
||||
<Title headingLevel={TitleLevel.h3} size='2xl'>
|
||||
<strong id={`${credContainer.type}-cred-title`}><Msg msgKey={credContainer.displayName}/></strong>
|
||||
<React.Fragment key={"credTypeTitle-" + credContainer.type}>
|
||||
<Split className="pf-u-mt-lg pf-u-mb-lg">
|
||||
<SplitItem>
|
||||
<Title
|
||||
headingLevel="h3"
|
||||
size="md"
|
||||
className="pf-u-mb-md"
|
||||
>
|
||||
<span className="cred-title pf-u-display-block" id={`${credContainer.type}-cred-title`}>
|
||||
<Msg msgKey={credContainer.displayName} />
|
||||
</span>
|
||||
</Title>
|
||||
<span id={`${credContainer.type}-cred-help`}>
|
||||
{credContainer.helptext && <Msg msgKey={credContainer.helptext}/>}
|
||||
{credContainer.helptext && (
|
||||
<Msg msgKey={credContainer.helptext} />
|
||||
)}
|
||||
</span>
|
||||
</DataListCell>,
|
||||
</SplitItem>
|
||||
|
||||
]}/>
|
||||
{credContainer.createAction &&
|
||||
<DataListAction
|
||||
aria-labelledby='create'
|
||||
aria-label='create action'
|
||||
id={'mob-setUpAction-' + credContainer.type}
|
||||
className={DataListActionVisibility.hiddenOnLg}
|
||||
<SplitItem isFilled>
|
||||
{credContainer.createAction && (
|
||||
<div
|
||||
id={"mob-setUpAction-" + credContainer.type}
|
||||
className="pf-u-display-none-on-lg pf-u-float-right"
|
||||
>
|
||||
<Dropdown
|
||||
isPlain
|
||||
position={DropdownPosition.right}
|
||||
toggle={<KebabToggle onToggle={isOpen => {
|
||||
toggle={
|
||||
<KebabToggle
|
||||
onToggle={(isOpen) => {
|
||||
credContainer.open = isOpen;
|
||||
this.setState({ credentialContainers: new Map(this.state.credentialContainers) });
|
||||
}} />}
|
||||
this.setState({
|
||||
credentialContainers: new Map(
|
||||
this.state.credentialContainers
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isOpen={credContainer.open}
|
||||
dropdownItems={[
|
||||
<button id={`mob-${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>]}
|
||||
/>
|
||||
</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">
|
||||
<i className="fas fa-plus-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
<Msg msgKey='setUpNew' params={[credContainerDisplayName]}/>
|
||||
</button>
|
||||
</DataListAction>}
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
</React.Fragment>
|
||||
)
|
||||
<button
|
||||
id={`mob-${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>,
|
||||
]}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
)}
|
||||
</SplitItem>
|
||||
</Split>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type CredRemover = (credentialId: string, userLabel: string) => void;
|
||||
|
||||
interface CredentialActionProps {
|
||||
credential: UserCredential;
|
||||
removeable: boolean;
|
||||
|
@ -383,29 +477,49 @@ class CredentialAction extends React.Component<CredentialActionProps> {
|
|||
render(): React.ReactNode {
|
||||
if (this.props.updateAction) {
|
||||
return (
|
||||
<DataListAction aria-labelledby='foo' aria-label='foo action' id={'updateAction-' + this.props.credential.id}>
|
||||
<Button id={`${SigningInPage.credElementId(this.props.credential.type, this.props.credential.id, 'update')}`} variant='primary'onClick={()=> this.props.updateAction.execute()}><Msg msgKey='update'/></Button>
|
||||
<DataListAction
|
||||
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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.removeable) {
|
||||
const userLabel: string = this.props.credential.userLabel;
|
||||
return (
|
||||
<DataListAction aria-labelledby='foo' aria-label='foo action' id={'removeAction-' + this.props.credential.id }>
|
||||
<ContinueCancelModal buttonTitle='remove'
|
||||
<DataListAction
|
||||
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')}`}
|
||||
modalTitle={Msg.localize('removeCred', [userLabel])}
|
||||
modalMessage={Msg.localize('stopUsingCred', [userLabel])}
|
||||
onContinue={() => this.props.credRemover(this.props.credential.id, userLabel)}
|
||||
/>
|
||||
</DataListAction>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (<></>)
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
const SigningInPageWithRouter = withRouter(SigningInPage);
|
||||
export { SigningInPageWithRouter as SigningInPage};
|
||||
export { SigningInPageWithRouter as SigningInPage };
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,6 @@ interface ContinueCancelModalProps {
|
|||
onContinue: () => void;
|
||||
onClose?: () => void;
|
||||
isDisabled?: boolean;
|
||||
isLarge?: boolean;
|
||||
}
|
||||
|
||||
interface ContinueCancelModalState {
|
||||
|
@ -52,8 +51,7 @@ export class ContinueCancelModal extends React.Component<ContinueCancelModalProp
|
|||
buttonVariant: 'primary',
|
||||
modalContinueButtonLabel: 'continue',
|
||||
modalCancelButtonLabel: 'doCancel',
|
||||
isDisabled: false,
|
||||
isSmall: true
|
||||
isDisabled: false
|
||||
};
|
||||
|
||||
public constructor(props: ContinueCancelModalProps) {
|
||||
|
@ -87,15 +85,16 @@ export class ContinueCancelModal extends React.Component<ContinueCancelModalProp
|
|||
{this.props.render && this.props.render(this.handleModalToggle)}
|
||||
<Modal
|
||||
{...this.props}
|
||||
variant={ModalVariant.small}
|
||||
title={Msg.localize(this.props.modalTitle)}
|
||||
isOpen={isModalOpen}
|
||||
onClose={this.handleModalToggle}
|
||||
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}>
|
||||
<Msg msgKey={this.props.modalContinueButtonLabel!}/>
|
||||
</Button>,
|
||||
<Button id='modal-cancel' key="cancel" variant="secondary" onClick={this.handleModalToggle}>
|
||||
<Msg msgKey={this.props.modalCancelButtonLabel!}/>
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
|
|
|
@ -20,15 +20,14 @@ import {
|
|||
EmptyStateVariant,
|
||||
Title,
|
||||
EmptyStateIcon,
|
||||
TitleLevel,
|
||||
EmptyStateBody,
|
||||
IconProps,
|
||||
} from '@patternfly/react-core'
|
||||
|
||||
import { Msg } from './Msg';
|
||||
import {SVGIconProps} from '@patternfly/react-icons/dist/esm/createIcon';
|
||||
|
||||
export interface EmptyMessageStateProps {
|
||||
icon: React.FunctionComponent<IconProps>;
|
||||
icon: React.ComponentType<SVGIconProps>;
|
||||
messageKey: string;
|
||||
}
|
||||
|
||||
|
@ -41,7 +40,7 @@ export default class EmptyMessageState extends React.Component<EmptyMessageState
|
|||
return (
|
||||
<EmptyState variant={EmptyStateVariant.full}>
|
||||
<EmptyStateIcon icon={this.props.icon} />
|
||||
<Title headingLevel={TitleLevel.h5} size="lg">
|
||||
<Title headingLevel="h5" size="lg">
|
||||
<Msg msgKey={this.props.messageKey} />
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
|
|
|
@ -10,25 +10,28 @@
|
|||
"check-types:watch": "npm run check-types -- -w",
|
||||
"lint": "eslint ./app/**/*.ts*",
|
||||
"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",
|
||||
"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-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-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-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": [],
|
||||
"author": "Stan Silvert",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@patternfly/react-core": "^3.153.3",
|
||||
"@patternfly/react-icons": "^3.15.16",
|
||||
"@patternfly/react-styles": "^3.7.14",
|
||||
"@patternfly/patternfly": "^4.125.3",
|
||||
"@patternfly/react-core": "^4.147.0",
|
||||
"@patternfly/react-icons": "^4.11.8",
|
||||
"@patternfly/react-styles": "^4.11.8",
|
||||
"react": "npm:@pika/react@^16.13.1",
|
||||
"react-dom": "npm:@pika/react-dom@^16.13.1",
|
||||
"react-router-dom": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.8.4",
|
||||
"@babel/compat-data": "^7.9.0",
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
|
@ -41,6 +44,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||
"@typescript-eslint/parser": "^1.4.2",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"eslint": "^5.15.1",
|
||||
"eslint-config-react-app": "^3.0.8",
|
||||
"eslint-plugin-flowtype": "^2.50.3",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"jsx": "react",
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"./app/**/*.ts?"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
parent=base
|
||||
deprecatedMode=false
|
||||
|
||||
scripts=welcome-page-scripts.js
|
||||
|
||||
developmentMode=false
|
||||
|
||||
# This is the logo in upper lefthand corner.
|
||||
|
|