Jelajahi Sumber

Création du thème APDLI par copie de Keycloak (login) et V2 (account)

garthh 10 bulan lalu
induk
melakukan
7deb2839cd
100 mengubah file dengan 4977 tambahan dan 0 penghapusan
  1. 279 0
      themes/account/index.ftl
  2. 1 0
      themes/account/messages/messages_ca.properties
  3. 1 0
      themes/account/messages/messages_cs.properties
  4. 1 0
      themes/account/messages/messages_da.properties
  5. 122 0
      themes/account/messages/messages_de.properties
  6. 143 0
      themes/account/messages/messages_en.properties
  7. 1 0
      themes/account/messages/messages_es.properties
  8. 42 0
      themes/account/messages/messages_fr.properties
  9. 1 0
      themes/account/messages/messages_hu.properties
  10. 1 0
      themes/account/messages/messages_it.properties
  11. 1 0
      themes/account/messages/messages_ja.properties
  12. 1 0
      themes/account/messages/messages_lt.properties
  13. 1 0
      themes/account/messages/messages_nl.properties
  14. 1 0
      themes/account/messages/messages_no.properties
  15. 1 0
      themes/account/messages/messages_pl.properties
  16. 121 0
      themes/account/messages/messages_pt_BR.properties
  17. 1 0
      themes/account/messages/messages_ru.properties
  18. 1 0
      themes/account/messages/messages_sk.properties
  19. 1 0
      themes/account/messages/messages_sv.properties
  20. 1 0
      themes/account/messages/messages_tr.properties
  21. 1 0
      themes/account/messages/messages_zh_CN.properties
  22. 80 0
      themes/account/resources/App.js
  23. 0 0
      themes/account/resources/App.js.map
  24. 157 0
      themes/account/resources/ContentPages.js
  25. 0 0
      themes/account/resources/ContentPages.js.map
  26. 97 0
      themes/account/resources/Main.js
  27. 0 0
      themes/account/resources/Main.js.map
  28. 51 0
      themes/account/resources/PageNav.js
  29. 0 0
      themes/account/resources/PageNav.js.map
  30. 78 0
      themes/account/resources/PageToolbar.js
  31. 0 0
      themes/account/resources/PageToolbar.js.map
  32. 3 0
      themes/account/resources/account-service/AccountServiceContext.js
  33. 1 0
      themes/account/resources/account-service/AccountServiceContext.js.map
  34. 149 0
      themes/account/resources/account-service/account.service.js
  35. 0 0
      themes/account/resources/account-service/account.service.js.map
  36. 60 0
      themes/account/resources/content.json
  37. 113 0
      themes/account/resources/content/ContentAlert.js
  38. 0 0
      themes/account/resources/content/ContentAlert.js.map
  39. 65 0
      themes/account/resources/content/ContentPage.js
  40. 0 0
      themes/account/resources/content/ContentPage.js.map
  41. 273 0
      themes/account/resources/content/account-page/AccountPage.js
  42. 0 0
      themes/account/resources/content/account-page/AccountPage.js.map
  43. 70 0
      themes/account/resources/content/aia-page/AppInitiatedActionPage.js
  44. 0 0
      themes/account/resources/content/aia-page/AppInitiatedActionPage.js.map
  45. 186 0
      themes/account/resources/content/applications-page/ApplicationsPage.js
  46. 0 0
      themes/account/resources/content/applications-page/ApplicationsPage.js.map
  47. 28 0
      themes/account/resources/content/authenticator-page/AuthenticatorPage.js
  48. 1 0
      themes/account/resources/content/authenticator-page/AuthenticatorPage.js.map
  49. 264 0
      themes/account/resources/content/device-activity-page/DeviceActivityPage.js
  50. 0 0
      themes/account/resources/content/device-activity-page/DeviceActivityPage.js.map
  51. 36 0
      themes/account/resources/content/forbidden-page/ForbiddenPage.js
  52. 1 0
      themes/account/resources/content/forbidden-page/ForbiddenPage.js.map
  53. 272 0
      themes/account/resources/content/linked-accounts-page/LinkedAccountsPage.js
  54. 0 0
      themes/account/resources/content/linked-accounts-page/LinkedAccountsPage.js.map
  55. 40 0
      themes/account/resources/content/my-resources-page/AbstractResourceTable.js
  56. 0 0
      themes/account/resources/content/my-resources-page/AbstractResourceTable.js.map
  57. 120 0
      themes/account/resources/content/my-resources-page/EditTheResource.js
  58. 0 0
      themes/account/resources/content/my-resources-page/EditTheResource.js.map
  59. 282 0
      themes/account/resources/content/my-resources-page/MyResourcesPage.js
  60. 0 0
      themes/account/resources/content/my-resources-page/MyResourcesPage.js.map
  61. 151 0
      themes/account/resources/content/my-resources-page/PermissionRequest.js
  62. 0 0
      themes/account/resources/content/my-resources-page/PermissionRequest.js.map
  63. 100 0
      themes/account/resources/content/my-resources-page/PermissionSelect.js
  64. 0 0
      themes/account/resources/content/my-resources-page/PermissionSelect.js.map
  65. 309 0
      themes/account/resources/content/my-resources-page/ResourcesTable.js
  66. 0 0
      themes/account/resources/content/my-resources-page/ResourcesTable.js.map
  67. 227 0
      themes/account/resources/content/my-resources-page/ShareTheResource.js
  68. 0 0
      themes/account/resources/content/my-resources-page/ShareTheResource.js.map
  69. 103 0
      themes/account/resources/content/my-resources-page/SharedResourcesTable.js
  70. 0 0
      themes/account/resources/content/my-resources-page/SharedResourcesTable.js.map
  71. 16 0
      themes/account/resources/content/my-resources-page/resource-model.js
  72. 1 0
      themes/account/resources/content/my-resources-page/resource-model.js.map
  73. 31 0
      themes/account/resources/content/page-not-found/PageNotFound.js
  74. 1 0
      themes/account/resources/content/page-not-found/PageNotFound.js.map
  75. 331 0
      themes/account/resources/content/signingin-page/SigningInPage.js
  76. 0 0
      themes/account/resources/content/signingin-page/SigningInPage.js.map
  77. 3 0
      themes/account/resources/keycloak-service/KeycloakContext.js
  78. 1 0
      themes/account/resources/keycloak-service/KeycloakContext.js.map
  79. 77 0
      themes/account/resources/keycloak-service/keycloak.service.js
  80. 0 0
      themes/account/resources/keycloak-service/keycloak.service.js.map
  81. TEMPAT SAMPAH
      themes/account/resources/public/favicon.ico
  82. 16 0
      themes/account/resources/public/layout.css
  83. 0 0
      themes/account/resources/public/logo.svg
  84. 33 0
      themes/account/resources/util/AIACommand.js
  85. 1 0
      themes/account/resources/util/AIACommand.js.map
  86. 19 0
      themes/account/resources/util/ParseLink.js
  87. 1 0
      themes/account/resources/util/ParseLink.js.map
  88. 39 0
      themes/account/resources/util/RedirectUri.js
  89. 0 0
      themes/account/resources/util/RedirectUri.js.map
  90. 50 0
      themes/account/resources/util/TimeUtil.js
  91. 0 0
      themes/account/resources/util/TimeUtil.js.map
  92. 89 0
      themes/account/resources/welcome-page-scripts.js
  93. 103 0
      themes/account/resources/widgets/ContinueCancelModal.js
  94. 0 0
      themes/account/resources/widgets/ContinueCancelModal.js.map
  95. 38 0
      themes/account/resources/widgets/EmptyMessageState.js
  96. 0 0
      themes/account/resources/widgets/EmptyMessageState.js.map
  97. 41 0
      themes/account/resources/widgets/LocaleSelectors.js
  98. 0 0
      themes/account/resources/widgets/LocaleSelectors.js.map
  99. 46 0
      themes/account/resources/widgets/Logout.js
  100. 0 0
      themes/account/resources/widgets/Logout.js.map

+ 279 - 0
themes/account/index.ftl

@@ -0,0 +1,279 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>${msg("accountManagementTitle")}</title>
+
+        <meta charset="UTF-8">
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+        <meta name="robots" content="noindex, nofollow">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+
+        <script>
+            <#if properties.developmentMode?has_content && properties.developmentMode == "true">
+            var developmentMode = true;
+            var reactRuntime = 'react.development.js';
+            var reactDOMRuntime = 'react-dom.development.js';
+            var reactRouterRuntime = 'react-router-dom.js';
+            <#else>
+            var developmentMode = false;
+            var reactRuntime = 'react.production.min.js';
+            var reactDOMRuntime = 'react-dom.production.min.js';
+            var reactRouterRuntime = 'react-router-dom.min.js';
+            </#if>
+            var authUrl = '${authUrl}';
+            var baseUrl = '${baseUrl}';
+            var realm = '${realm.name}';
+            var resourceUrl = '${resourceUrl}';
+            var isReactLoading = false;
+
+            <#if properties.logo?has_content>
+            var brandImg = resourceUrl + '${properties.logo}';
+            <#else>
+            var brandImg = resourceUrl + '/public/logo.svg';
+            </#if>
+
+            <#if properties.logoUrl?has_content>
+            var brandUrl = '${properties.logoUrl}';
+            <#else>
+            var brandUrl = baseUrl;
+            </#if>
+
+            var features = {
+                isRegistrationEmailAsUsername : ${realm.registrationEmailAsUsername?c},
+                isEditUserNameAllowed : ${realm.editUsernameAllowed?c},
+                isInternationalizationEnabled : ${realm.isInternationalizationEnabled()?c},
+                isLinkedAccountsEnabled : ${realm.identityFederationEnabled?c},
+                isEventsEnabled : ${isEventsEnabled?c},
+                isMyResourcesEnabled : ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},
+                isTotpConfigured : ${isTotpConfigured?c},
+                deleteAccountAllowed : ${deleteAccountAllowed?c}
+            }
+
+            var availableLocales = [];
+            <#list supportedLocales as locale, label>
+                availableLocales.push({locale : '${locale}', label : '${label}'});
+            </#list>
+
+            <#if referrer??>
+                var referrer = '${referrer}';
+                var referrerName = '${referrerName}';
+                var referrerUri = '${referrer_uri}'.replace('&amp;', '&');
+            </#if>
+
+            <#if msg??>
+                var locale = '${locale}';
+                var l18nMsg = JSON.parse('${msgJSON?no_esc}');
+            <#else>
+                var locale = 'en';
+                var l18Msg = {};
+            </#if>
+        </script>
+
+        <#if properties.favIcon?has_content>
+        <link rel="icon" href="${resourceUrl}${properties.favIcon}" type="image/x-icon"/>
+        <#else>
+        <link rel="icon" href="${resourceUrl}/public/favicon.ico" type="image/x-icon"/>
+        </#if>
+
+        <script src="${authUrl}js/keycloak.js"></script>
+
+        <#if properties.developmentMode?has_content && properties.developmentMode == "true">
+        <!-- Don't use this in production: -->
+        <script src="${resourceUrl}/node_modules/react/umd/react.development.js" crossorigin></script>
+        <script src="${resourceUrl}/node_modules/react-dom/umd/react-dom.development.js" crossorigin></script>
+        <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
+        </#if>
+
+        <#if properties.extensions?has_content>
+            <#list properties.extensions?split(' ') as script>
+                <#if properties.developmentMode?has_content && properties.developmentMode == "true">
+        <script type="text/babel" src="${resourceUrl}/${script}"></script>
+                <#else>
+        <script type="text/javascript" src="${resourceUrl}/${script}"></script>
+                </#if>
+            </#list>
+        </#if>
+
+        <#if properties.scripts?has_content>
+            <#list properties.scripts?split(' ') as script>
+        <script type="text/javascript" src="${resourceUrl}/${script}"></script>
+            </#list>
+        </#if>
+
+        <script>
+            var content = <#include "resources/content.json"/>
+        </script>
+
+        <#if properties.styles?has_content>
+            <#list properties.styles?split(' ') as style>
+            <link href="${resourceUrl}/${style}" rel="stylesheet"/>
+            </#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 href="${resourceUrl}/public/layout.css" rel="stylesheet"/>
+    </head>
+
+    <body>
+
+        <script>
+            var keycloak = Keycloak({
+                authServerUrl: authUrl,
+                realm: realm,
+                clientId: 'account-console'
+            });
+            keycloak.init({onLoad: 'check-sso', pkceMethod: 'S256', promiseType: 'native'}).then((authenticated) => {
+                isReactLoading = true;
+                toggleReact();
+                if (!keycloak.authenticated) {
+                    document.getElementById("landingSignInButton").style.display='inline';
+                    document.getElementById("landingSignInLink").style.display='inline';
+                } else {
+                    document.getElementById("landingSignOutButton").style.display='inline';
+                    document.getElementById("landingSignOutLink").style.display='inline';
+                    document.getElementById("landingLoggedInUser").innerHTML = loggedInUserName('${msg("unknownUser")}', '${msg("fullName")}');
+                }
+
+                loadjs("/Main.js");
+            }).catch(() => {
+                alert('failed to initialize keycloak');
+            });
+        </script>
+
+<div id="main_react_container" style="display:none;height:100%"></div>
+
+<div id="spinner_screen" style="display:block; height:100%">
+    <div style="width: 320px; height: 328px; text-align: center; position: absolute; top:0;	bottom: 0; left: 0;	right: 0; margin: auto;">
+                <#if properties.logo?has_content>
+                <img src="${resourceUrl}${properties.logo}" alt="Logo" class="brand">
+                <#else>
+                <img src="${resourceUrl}/public/logo.svg" alt="Logo" class="brand">
+                </#if>
+                <p>${msg("loadingMessage")}</p>
+                <div >
+                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(255, 255, 255); display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
+                    <path d="M10 50A40 40 0 0 0 90 50A40 42 0 0 1 10 50" fill="#5DBCD2" stroke="none" transform="rotate(16.3145 50 51)">
+                        <animateTransform attributeName="transform" type="rotate" dur="1s" repeatCount="indefinite" keyTimes="0;1" values="0 50 51;360 50 51"></animateTransform>
+                    </path>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="welcomeScreen" style="display:none;height:100%">
+    <div class="pf-c-page" id="page-layout-default-nav">
+      <header role="banner" class="pf-c-page__header">
+        <div class="pf-c-page__header-brand">
+          <#if properties.logoUrl?has_content>
+          <a id="landingLogo" class="pf-c-page__header-brand-link" href="${properties.logoUrl}">
+          <#else>
+          <a id="landingLogo" class="pf-c-page__header-brand-link" href="${baseUrl}">
+          </#if>
+            <#if properties.logo?has_content>
+            <img class="pf-c-brand brand" src="${resourceUrl}${properties.logo}" alt="Logo">
+            <#else>
+            <img class="pf-c-brand brand" src="${resourceUrl}/public/logo.svg" alt="Logo">
+            </#if>
+          </a>
+        </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>
+            </#if>
+
+            <div class="pf-c-page__header-tools-group pf-m-icons">
+              <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 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>
+                    </button>
+                    <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>
+                        </li>
+                        </#if>
+
+                        <li id="landingSignInLink" role="none" style="display:none">
+                            <a onclick="keycloak.login();" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${msg("doLogIn")}</a>
+                        </li>
+                        <li id="landingSignOutLink" role="none" style="display:none">
+                            <a onclick="keycloak.logout();" role="menuitem" tabindex="0" aria-disabled="false" class="pf-c-dropdown__menu-item">${msg("doSignOut")}</a>
+                        </li>
+                    </ul>
+                </div>
+            </div>
+
+            <span id="landingLoggedInUser"></span>
+
+        </div> <!-- end header tools -->
+      </header>
+
+      <main role="main" class="pf-c-page__main">
+        <section class="pf-c-page__main-section pf-m-light">
+          <div class="pf-c-content" id="landingWelcomeMessage">
+            <h1>${msg("accountManagementWelcomeMessage")}</h1>
+          </div>
+        </section>
+        <section class="pf-c-page__main-section">
+          <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>
+                  <div class="pf-c-card__header pf-c-content">
+                      <h2>
+                        <#if item.icon??>
+                          <i class="pf-icon ${item.icon}"></i>&nbsp;
+                        <#elseif item.iconSvg??>
+                          <img src="${item.iconSvg}" alt="icon"/>&nbsp;
+                        </#if>
+                        ${msg(item.label)}
+                      </h2>
+                      <#if item.descriptionLabel??>
+                        <p>${msg(item.descriptionLabel)}</p>
+                      </#if>
+                  </div>
+                  <div class="pf-c-card__body pf-c-content">
+                    <#if item.content??>
+                      <#list item.content as sub>
+                        <div id="landing-${sub.id}">
+                          <a onclick="toggleReact(); window.location.hash='${sub.path}'">${msg(sub.label)}</a>
+                        </div>
+                      </#list>
+                    <#else>
+                      <a id="landing-${item.id}" onclick="toggleReact(); window.location.hash = '${item.path}'">${msg(item.label)}</a>
+                    </#if>
+                  </div>
+                </div>
+              </div>
+            </#list>
+          </div>
+        </section>
+      </main>
+    </div>
+</div>
+
+    <script>
+      const removeHidden = (content) => {
+        content.forEach(c => {
+          if (c.hidden && eval(c.hidden)) {
+            document.getElementById('landing-' + c.id).remove();
+          }
+          if (c.content) removeHidden(c.content);
+        });
+      }
+      removeHidden(content);
+    </script>
+
+    </body>
+</html>

+ 1 - 0
themes/account/messages/messages_ca.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_cs.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_da.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 122 - 0
themes/account/messages/messages_de.properties

@@ -0,0 +1,122 @@
+# Put new messages for Account Console Here
+# Feel free to use any existing messages from the base theme
+pageNotFound=Seite nicht gefunden
+forbidden=Verboten
+needAccessRights=Sie haben keine Zugriffsrechte auf diese Anfrage.  Wenden Sie sich an Ihren Administrator.
+invalidRoute={0} ist keine valide Route.
+actionRequiresIDP=Diese Aktion erfordert eine Umleitung zu Ihrem Identit\u00E4tsanbieter.
+actionNotDefined=Keine Aktion festgelegt
+continue=Fortfahren
+refreshPage=Aktualisiere die Seite
+done=Fertig
+cancel=Abbrechen
+remove=Entfernen
+update=Aktualisieren
+status=Status
+loadingMessage=Accountkonsole l\u00E4dt ...
+unknownUser=Unbekannter Nutzer
+fullName={0} {1}
+
+selectLocale=Sprache ausw\u00E4hlen
+doSignIn=Anmelden
+
+# Device Activity Page
+signedInDevices=Angemeldete Ger\u00E4te
+signedInDevicesExplanation=Melde ein unbekanntes Ger\u00E4t ab.
+signOutWarning=Session abmelden?
+signOutAllDevices=Alle Ger\u00E4te abmelden
+signOutAllDevicesWarning=Diese Aktion meldet alle Ger\u00E4te ab, die sich bei Ihrem Konto angemeldet haben, einschlie\u00DFlich des aktuellen Ger\u00E4ts, das Sie verwenden.
+recentlyUsedDevices=K\u00FCrzlich verwendete Ger\u00E4te
+recentlyUsedDevicesExplanation=Ger\u00E4te die im letzten Monat verwendet wurden, aber derzeit nicht angemeldet sind.
+lastAccess=Letzter Zugriff
+unknownOperatingSystem=Unbekanntes Betriebssystem
+currentDevice=Aktuelles Ger\u00E4t
+currentSession=Aktuelle Sitzung
+signedOutSession=Abgemeldet {0}/{1}
+lastAccessedOn=Zuletzt zugegriffen am
+clients=Clients
+startedAt=Gestartet am
+expiresAt=L\u00E4uft ab am
+ipAddress=IP Adresse
+
+# Resources Page
+resourceName=Ressourcenname
+nextPage=N\u00E4chste
+previousPage=Vorherige
+firstPage=Erste Seite
+resourceSharedWith=Ressource ist freigegeben mit {0}
+and=\ und {0} anderen Nutzern
+add=Hinzuf\u00FCgen
+share=Freigeben
+edit=Editieren
+close=Schlie\u00DFen
+unShare=Alle Freigaben aufheben
+shareSuccess=Ressource erfolgreich freigegeben.
+unShareSuccess=Freigabe erfolgreich aufgehoben.
+updateSuccess=Ressource erfolgreich aktualisiert.
+resourceAlreadyShared=Ressource ist bereits an diesen Nutzer freigegeben.
+resourceNotShared=Diese Ressource ist nicht freigegeben.
+permissionRequests=Zugriffsanfragen
+permissions=Zugriffsrechte
+unShareAllConfirm=Wollen Sie wirklich alle Freigaben aufheben?
+userNotFound=Kein Nutzer mit dem Namen oder E-Mail gefunden {0}
+
+# Linked Accounts Page
+linkedAccountsTitle=Verbundene Konten
+linkedAccountsIntroMessage=Verwalten Sie Anmeldungen \u00FCber Konten von Drittanbietern.
+linkedLoginProviders=Verbundene Login Anbieter
+unlinkedLoginProviders=Getrennte Login Anbieter
+linkedEmpty=Keine verbundenen Anbieter
+unlinkedEmpty=Keine getrennten Anbieter
+socialLogin=Social Login
+systemDefined=Systemdefiniert
+link=Account verbinden
+unLink=Account trennen
+
+# Signing In Page
+signingIn=Anmeldung
+signingInSubMessage=Konfigurieren Sie die Anmeldem\u00F6glichkeiten.
+credentialCreatedAt=Erstellt
+successRemovedMessage={0} wurde entfernt.
+stopUsingCred={0} nicht mehr verwenden?
+removeCred={0} entfernen
+setUpNew={0} einrichten
+notSetUp={0} ist nicht eingerichtet.
+two-factor=Zwei-Faktor Authentifizierung
+passwordless=Kennwortlos
+unknown=Unbekannt
+password-display-name=Passwort
+password-help-text=Mit einem Passwort anmelden.
+password=Mein Passwort
+otp-display-name=Authenticator-Anwendung
+otp-help-text=Geben Sie einen Verifizierungscode aus der Authenticator-Anwendung ein.
+webauthn-display-name=Security-Token
+webauthn-help-text=Verwenden Sie Ihr Security-Token zur Anmeldung.
+webauthn-passwordless-display-name=Security-Token
+webauthn-passwordless-help-text=Verwenden Sie Ihr Security-Token zur kennwortlosen Anmeldung.
+basic-authentication=Standardauthentifizierung
+invalidRequestMessage=Ung\u00FCltige Anfrage
+
+# Applications page
+applicationsPageTitle=Anwendungen
+internalApp=Intern
+thirdPartyApp=Drittanbieter
+offlineAccess=Offline Zugriff
+inUse=In Benutzung
+notInUse=Nicht in Benutzung
+applicationDetails=Anwendungsdetails
+client=Client
+description=Beschreibung
+baseUrl=URL
+accessGrantedOn=Zugriff gew\u00E4hrt am
+removeButton=Zugriff entfernen
+removeModalTitle=Zugriff entfernen
+removeModalMessage=Dadurch wird die aktuell gew\u00E4hrte Zugriffsberechtigung f\u00FCr {0} entfernt. Sie m\u00FCssen den Zugriff erneut gew\u00E4hren, wenn Sie diese Anwendung verwenden m\u00F6chten.
+confirmButton=Best\u00E4tigen
+infoMessage=Indem Sie auf "Zugriff entfernen" klicken, entfernen Sie gew\u00E4hrte Berechtigungen dieser Anwendung. Diese Anwendung wird Ihre Informationen nicht mehr verwenden.
+
+#Delete Account page
+doDelete=L\u00F6schen
+deleteAccountSummary=Wenn Sie Ihr Konto l\u00F6schen, werden alle Ihre Daten gel\u00F6scht und Sie werden sofort abgemeldet.
+deleteAccount=Konto l\u00F6schen
+deleteAccountWarning=Dies ist unwiderruflich. Alle Ihre Daten werden dauerhaft gel\u00F6scht und k\u00F6nnen nicht wiederhergestellt werden.

+ 143 - 0
themes/account/messages/messages_en.properties

@@ -0,0 +1,143 @@
+# Put new messages for Account Console Here
+# Feel free to use any existing messages from the base theme
+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
+continue=Continue
+refreshPage=Refresh the page
+done=Done
+cancel=Cancel
+remove=Remove
+update=Update
+loadingMessage=Account Console loading ...
+unknownUser=Anonymous
+fullName={0} {1}
+
+selectLocale=Select a locale
+doSignIn=Sign In
+
+# Device Activity Page
+signedInDevices=Signed In Devices
+signedInDevicesExplanation=Sign out any device that is unfamiliar.
+signOutWarning=Sign out the session?
+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
+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
+signedOutSession=Signed out {0}/{1}
+lastAccessedOn=Last accessed on
+clients=Clients
+startedAt=Started at
+expiresAt=Expires at
+ipAddress=IP Address
+
+# Resources Page
+resourceName=Resource Name
+nextPage=Next
+previousPage=Previous
+firstPage=First Page
+resourceSharedWith=Resource is shared with {0}
+and=\ and {0} other users
+add=Add
+share=Share
+edit=Edit
+close=Close
+unShare=Unshare all
+shareSuccess=Resource successfully shared.
+unShareSuccess=Resource successfully un-shared.
+updateSuccess=Resource successfully updated.
+resourceAlreadyShared=Resource is already shared with this user.
+resourceNotShared=This resource is not shared.
+permissionRequests=Permission requests
+permissions=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
+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
+
+# Signing In Page
+signingIn=Signing In
+signingInSubMessage=Configure ways to sign in.
+credentialCreatedAt=Created
+successRemovedMessage={0} was removed.
+stopUsingCred=Stop using {0}?
+removeCred=Remove {0}
+setUpNew=Set up {0}
+notSetUp={0} is not set up.
+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
+otp-help-text=Enter a verification code from authenticator application.
+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
+
+# Applications page
+applicationsPageTitle=Applications
+internalApp=Internal
+thirdPartyApp=Third-party
+offlineAccess=Offline Access
+inUse=In use
+notInUse=Not in use
+applicationDetails=Application Details
+client=Client
+description=Description
+baseUrl=URL
+accessGrantedOn=Access granted on
+removeButton=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
+
+#Delete Account page
+doDelete=Delete
+deleteAccountSummary=Deleting your account will erase all your data and log you out immediately.
+deleteAccount=Delete Account
+deleteAccountWarning=This is irreversible. All your data will be permanently destroyed, and irretrievable.
+
+error-invalid-value=''{0}'' has invalid value.
+error-invalid-blank=Please specify value of ''{0}''.
+error-empty=Please specify value of ''{0}''.
+error-invalid-length=''{0}'' must have a length between {1} and {2}.
+error-invalid-length-too-short=''{0}'' must have minimal length of {1}.
+error-invalid-length-too-long=''{0}'' must have maximal length of {2}.
+error-invalid-email=Invalid email address.
+error-invalid-number=''{0}'' is invalid number.
+error-number-out-of-range=''{0}'' must be a number between {1} and {2}.
+error-number-out-of-range-too-small=''{0}'' must have minimal value of {1}.
+error-number-out-of-range-too-big=''{0}'' must have maximal value of {2}.
+error-pattern-no-match=''{0}'' doesn''t match required format.
+error-invalid-uri=''{0}'' is invalid URL.
+error-invalid-uri-scheme=''{0}'' has invalid URL scheme.
+error-invalid-uri-fragment=''{0}'' is invalid URL fragment.
+error-user-attribute-required=Please specify ''{0}''.
+error-invalid-date=''{0}'' is invalid date.
+error-username-invalid-character=''{0}'' contains invalid character.
+error-person-name-invalid-character='{0}' contains invalid character.

+ 1 - 0
themes/account/messages/messages_es.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 42 - 0
themes/account/messages/messages_fr.properties

@@ -0,0 +1,42 @@
+fullName={0} {1}
+loadingMessage=Gestion du compte en cours de chargement ...
+
+# Personal informations page
+selectLocale=Choisissez une langue
+
+# Authentication page
+signingIn=Authentification
+signingInSubMessage=Configurez les m\u00e9thodes d''authentification.
+basic-authentication=Authentification de Base
+password-display-name=Mot de passe
+password-help-text=Authentifiez-vous en saisissant votre mot de passe
+credentialCreatedAt=Cr\u00e9\u00e9 le
+two-factor=Authentification \u00e0 Deux Facteurs
+passwordless=Authentification Sans Mot de Passe
+otp-display-name=Application d''authentification
+otp-help-text=Entrez un code de v\u00e9rification \u00e0 usage unique fourni par l''application d''authentification.
+webauthn-display-name=Cl\u00e9 de S\u00e9curit\u00e9
+webauthn-help-text=Utilisez votre cl\u00e9 de s\u00e9curit\u00e9 pour vous authentifier.
+webauthn-passwordless-display-name=Cl\u00e9 de S\u00e9curit\u00e9
+webauthn-passwordless-help-text=Utilisez votre cl\u00e9 de s\u00e9curit\u00e9 pour une authentification sans mot de passe.
+notSetUp={0} non configur\u00e9(e).
+remove=Supprimer
+refreshPage=Rafra\u00eechir la page
+client_security-admin-console=Console d''administration de la s\u00e9curit\u00e9
+client_account-console=Console de gestion du compte
+
+
+# Device Activity page
+signedInDevices=Appareils Connect\u00e9s
+signedInDevicesExplanation=D\u00e9connectez les appareils que vous ne reconnaissez pas.
+currentSession=Session Courante
+lastAccessedOn=Dernier acc\u00e8s le
+startedAt=D\u00e9marr\u00e9(e) le
+expiresAt=Expire le
+
+# Applications page
+internalApp=Interne
+thirdPartyApp=Tierce
+inUse=Utilis\u00e9(e)
+notInUse=Non utilis\u00e9(e)
+setUpNew=Configurer {0}

+ 1 - 0
themes/account/messages/messages_hu.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_it.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_ja.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_lt.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_nl.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_no.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_pl.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 121 - 0
themes/account/messages/messages_pt_BR.properties

@@ -0,0 +1,121 @@
+# Put new messages for Account Console Here
+# Feel free to use any existing messages from the base theme
+pageNotFound=P\u00e1gina N\u00e3o Encontrada
+forbidden=Proibido
+needAccessRights=Voc\u00ea n\u00e3o tem as permiss\u00f5es de acesso para esta solicita\u00e7\u00e3o. Entre em contato com um administrador.
+invalidRoute={0} n\u00e3o \u00e9 uma rota v\u00e1lida.
+actionRequiresIDP=Esta a\u00e7\u00e3o requer uma redire\u00e7\u00e3o do seu provedor de identidades.
+actionNotDefined=Nenhuma a\u00e7\u00e3o definida
+continue=Continuar
+refreshPage=Atualizar p\u00e1gina
+done=Pronto
+cancel=Cancelar
+remove=Remover
+update=Atualizar
+loadingMessage=Carregando console de conta...
+unknownUser=An\u00f4nimo
+fullName={0} {1}
+
+selectLocale=Selecionar l\u00edngua
+doSignIn=Entrar
+
+# Device Activity Page
+signedInDevices=Dispositivos autenticados
+signedInDevicesExplanation=Saia de qualquer dispositivo que n\u00e3o reconhe\u00e7a.
+signOutWarning=Finalizar todas as sess\u00f5es?
+signOutAllDevices=Finalizar Sess\u00e3o em Todos os Dispositivos
+signOutAllDevicesWarning=Esta a\u00e7\u00e3o ir\u00e1 finalizar a sess\u00e3o de todos os dispositivos logados na sua conta, incluindo o dispositivo que est\u00e1 sendo utilizado atualmente.
+recentlyUsedDevices=Dispositivos Utilizados Recentemente
+recentlyUsedDevicesExplanation=Dispositivos utilizados no \u00faltimo m\u00eas, mas sem sess\u00e3o ativa atualmente.
+lastAccess=\u00daltimo Acesso
+unknownOperatingSystem=Sistema Operacional Desconhecido
+currentDevice=Dispositivo Atual
+currentSession=Sess\u00e3o Atual
+signedOutSession=Deslogado {0}/{1}
+lastAccessedOn=\u00daltimo acesso em
+clients=Clientes
+startedAt=Iniciado em
+expiresAt=Expira em
+ipAddress=Endere\u00e7o IP
+
+# Resources Page
+resourceName=Nome do Recurso
+nextPage=Avan\u00e7ar
+previousPage=Voltar
+firstPage=Primeira P\u00e1gina
+resourceSharedWith=O recurso \u00e9 compartilhado com {0}
+and=\ e {0} outros usu\u00e1rios
+add=Adicionar
+share=Compartilhar
+edit=Editar
+close=Fechar
+unShare=Descompartilhar tudo
+shareSuccess=O recurso foi compartilhado com sucesso.
+unShareSuccess=O recurso foi descompartilhado com sucesso.
+updateSuccess=O recurso foi atualizado com sucesso.
+resourceAlreadyShared=O recurso j\u00e1 foi compartilhado com este usu\u00e1rio.
+resourceNotShared=O recurso n\u00e3o foi compartilhado.
+permissionRequests=Pedidos de permiss\u00e3o
+permissions=Permiss\u00f5es
+unShareAllConfirm=Tem certeza de que quer remover todos os compartilhamentos?
+userNotFound=Usu\u00e1rio com o nome ou e-mail {0} n\u00e3o foi encontrado
+
+# Linked Accounts Page
+linkedAccountsTitle=Contas conectadas
+linkedAccountsIntroMessage=Gerenciar acessos por contas de outras aplica\u00e7\u00f5es.
+linkedLoginProviders=Provedores de Acesso Conectados
+unlinkedLoginProviders=Provedores de Acesso N\u00e3o-Conectados
+linkedEmpty=Nenhum Provedor Conectado
+unlinkedEmpty=Nenhum Provedor N\u00e3o-Conectado
+socialLogin=Login Social
+systemDefined=Definido pelo Sistema
+link=Conectar Conta
+unLink=Desconectar Conta
+
+# Signing In Page
+signingIn=Entrando na Conta
+signingInSubMessage=Configure maneiras de entrar na conta.
+credentialCreatedAt=Criada em
+successRemovedMessage={0} removida com sucesso.
+stopUsingCred=Para de usar {0}?
+removeCred=Remover {0}
+setUpNew=Configurar {0}
+notSetUp={0} n\u00e3o est\u00e1 configurada.
+two-factor=Autentica\u00e7\u00e3o de Dois Fatores
+passwordless=Sem Senha
+unknown=Desconhecida
+password-display-name=Senha
+password-help-text=Entre inserindo a sua senha.
+password=Minha Senha
+otp-display-name=App Autenticador
+otp-help-text=Insira o c\u00f3digo de verifica\u00e7\u00e3o do app autenticador.
+webauthn-display-name=Chave de Seguran\u00e7a
+webauthn-help-text=Use a sua chave de seguran\u00e7a para entrar.
+webauthn-passwordless-display-name=Chave de Seguran\u00e7a
+webauthn-passwordless-help-text=Use a sua chave de seguran\u00e7a para entrar sem senha.
+basic-authentication=Autentica\u00e7\u00e3o B\u00e1sica
+invalidRequestMessage=Solicita\u00e7\u00e3o Inv\u00e1lida
+
+# Applications page
+applicationsPageTitle=Aplica\u00e7\u00f5es
+internalApp=Interna
+thirdPartyApp=De Terceiros
+offlineAccess=Acesso Offline
+inUse=Em uso
+notInUse=N\u00e3o utilizado
+applicationDetails=Detalhes da Aplica\u00e7\u00e3o
+client=Cliente
+description=Descri\u00e7\u00e3o
+baseUrl=URL
+accessGrantedOn=Acesso concedido em
+removeButton=Remover acesso
+removeModalTitle=Remover Acesso
+removeModalMessage=Isto ir\u00e1 remover a permiss\u00e3o atual de acesso concedido para {0}. Voc\u00ea precisar\u00e1 repetir o processo de concess\u00e3o se for utilizar o app novamente.
+confirmButton=Confirmar
+infoMessage=Ao clicar em 'Remover Acesso', voc\u00ea ir\u00e1 remover as permiss\u00f5es concedidas a esta aplica\u00e7\u00e3o. Ela n\u00e3o poder\u00e1 mais utilizar as suas informa\u00e7\u00f5es.
+
+#Delete Account page
+doDelete=Apagar
+deleteAccountSummary=Apagar a sua conta ir\u00e1 remover todos os seus dados e finalizar a sess\u00e3o imediatamente.
+deleteAccount=Apagar Conta
+deleteAccountWarning=Esta a\u00e7\u00e3o \u00e9 irrevers\u00edvel. Todos os seus dados ser\u00e3o apagados permanentemente e n\u00e3o poder\u00e3o ser recuperados.

+ 1 - 0
themes/account/messages/messages_ru.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_sk.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_sv.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_tr.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 1 - 0
themes/account/messages/messages_zh_CN.properties

@@ -0,0 +1 @@
+fullName={0} {1}

+ 80 - 0
themes/account/resources/App.js

@@ -0,0 +1,80 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../common/keycloak/web_modules/react.js";
+import { PageNav } from "./PageNav.js";
+import { PageToolbar } from "./PageToolbar.js";
+import { makeRoutes } from "./ContentPages.js";
+import { Brand, Page, PageHeader, PageSection, PageSidebar } from "../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { KeycloakContext } from "./keycloak-service/KeycloakContext.js";
+;
+export class App extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    this.context = context;
+    toggleReact();
+  }
+
+  render() {
+    toggleReact(); // check login
+
+    if (!this.context.authenticated() && !isWelcomePage()) {
+      this.context.login();
+    }
+
+    const username = React.createElement("span", {
+      style: {
+        marginLeft: '10px'
+      },
+      id: "loggedInUser"
+    }, loggedInUserName());
+    const Header = React.createElement(PageHeader, {
+      logo: React.createElement("a", {
+        id: "brandLink",
+        href: brandUrl
+      }, React.createElement(Brand, {
+        src: brandImg,
+        alt: "Logo",
+        className: "brand"
+      })),
+      toolbar: React.createElement(PageToolbar, null),
+      avatar: username,
+      showNavToggle: true
+    });
+    const Sidebar = React.createElement(PageSidebar, {
+      nav: React.createElement(PageNav, null)
+    });
+    return React.createElement("span", {
+      style: {
+        height: '100%'
+      }
+    }, React.createElement(Page, {
+      header: Header,
+      sidebar: Sidebar,
+      isManagedSidebar: true
+    }, React.createElement(PageSection, null, makeRoutes())));
+  }
+
+}
+
+_defineProperty(App, "contextType", KeycloakContext);
+
+;
+//# sourceMappingURL=App.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/App.js.map


+ 157 - 0
themes/account/resources/ContentPages.js

@@ -0,0 +1,157 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../common/keycloak/web_modules/react.js";
+import { Route, Switch } from "../../common/keycloak/web_modules/react-router-dom.js";
+import { NavItem, NavExpandable } from "../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { Msg } from "./widgets/Msg.js";
+import { PageNotFound } from "./content/page-not-found/PageNotFound.js";
+import { ForbiddenPage } from "./content/forbidden-page/ForbiddenPage.js";
+;
+export function isModulePageDef(item) {
+  return item.modulePath !== undefined;
+}
+export function isExpansion(contentItem) {
+  return contentItem.content !== undefined;
+}
+
+function groupId(group) {
+  return 'grp-' + group;
+}
+
+function itemId(group, item) {
+  return 'grp-' + group + '_itm-' + item;
+}
+
+function isChildOf(parent, child) {
+  for (var item of parent.content) {
+    if (isExpansion(item) && isChildOf(item, child)) return true;
+    if (parent.groupId === child.groupId) return true;
+  }
+
+  return false;
+}
+
+function createNavItems(activePage, contentParam, groupNum) {
+  if (typeof content === 'undefined') return React.createElement(React.Fragment, null);
+  const links = contentParam.map(item => {
+    const navLinkId = `nav-link-${item.id}`;
+
+    if (isExpansion(item)) {
+      return React.createElement(NavExpandable, {
+        id: navLinkId,
+        groupId: item.groupId,
+        key: item.groupId,
+        title: Msg.localize(item.label, item.labelParams),
+        isExpanded: isChildOf(item, activePage)
+      }, createNavItems(activePage, item.content, groupNum + 1));
+    } else {
+      const page = item;
+      return React.createElement(NavItem, {
+        id: navLinkId,
+        groupId: item.groupId,
+        itemId: item.itemId,
+        key: item.itemId,
+        to: '#/' + page.path,
+        isActive: activePage.itemId === item.itemId,
+        type: "button"
+      }, Msg.localize(page.label, page.labelParams));
+    }
+  });
+  return React.createElement(React.Fragment, null, links);
+}
+
+export function makeNavItems(activePage) {
+  console.log({
+    activePage
+  });
+  return createNavItems(activePage, content, 0);
+}
+
+function setIds(contentParam, groupNum) {
+  if (typeof contentParam === 'undefined') return groupNum;
+  let expansionGroupNum = groupNum;
+
+  for (let i = 0; i < contentParam.length; i++) {
+    const item = contentParam[i];
+
+    if (isExpansion(item)) {
+      item.itemId = itemId(groupNum, i);
+      expansionGroupNum = expansionGroupNum + 1;
+      item.groupId = groupId(expansionGroupNum);
+      expansionGroupNum = setIds(item.content, expansionGroupNum);
+      console.log('currentGroup=' + expansionGroupNum);
+    } else {
+      item.groupId = groupId(groupNum);
+      item.itemId = itemId(groupNum, i);
+    }
+  }
+
+  ;
+  return expansionGroupNum;
+}
+
+export function initGroupAndItemIds() {
+  setIds(content, 0);
+  console.log({
+    content
+  });
+} // get rid of Expansions and put all PageDef items into a single array
+
+export function flattenContent(pageDefs) {
+  const flat = [];
+
+  for (let item of pageDefs) {
+    if (isExpansion(item)) {
+      flat.push(...flattenContent(item.content));
+    } else {
+      flat.push(item);
+    }
+  }
+
+  return flat;
+}
+export function makeRoutes() {
+  if (typeof content === 'undefined') return React.createElement("span", null);
+  const pageDefs = flattenContent(content);
+  const routes = pageDefs.map(page => {
+    if (isModulePageDef(page)) {
+      const node = React.createElement(page.module[page.componentName], {
+        'pageDef': page
+      });
+      return React.createElement(Route, {
+        key: page.itemId,
+        path: '/' + page.path,
+        exact: true,
+        render: () => node
+      });
+    } else {
+      const pageDef = page;
+      return React.createElement(Route, {
+        key: page.itemId,
+        path: '/' + page.path,
+        exact: true,
+        component: pageDef.component
+      });
+    }
+  });
+  return React.createElement(Switch, null, routes, React.createElement(Route, {
+    path: "/forbidden",
+    component: ForbiddenPage
+  }), React.createElement(Route, {
+    component: PageNotFound
+  }));
+}
+//# sourceMappingURL=ContentPages.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/ContentPages.js.map


+ 97 - 0
themes/account/resources/Main.js

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../common/keycloak/web_modules/react.js";
+import * as ReactDOM from "../../common/keycloak/web_modules/react-dom.js";
+import { HashRouter } from "../../common/keycloak/web_modules/react-router-dom.js";
+import { App } from "./App.js";
+import { flattenContent, initGroupAndItemIds, isExpansion, isModulePageDef } from "./ContentPages.js";
+import { KeycloakService } from "./keycloak-service/keycloak.service.js";
+import { KeycloakContext } from "./keycloak-service/KeycloakContext.js";
+import { AccountServiceClient } from "./account-service/account.service.js";
+import { AccountServiceContext } from "./account-service/AccountServiceContext.js";
+export class Main extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    isReactLoading = false;
+    toggleReact();
+  }
+
+  render() {
+    const keycloakService = new KeycloakService(keycloak);
+    return React.createElement(HashRouter, null, React.createElement(KeycloakContext.Provider, {
+      value: keycloakService
+    }, React.createElement(AccountServiceContext.Provider, {
+      value: new AccountServiceClient(keycloakService)
+    }, React.createElement(App, null))));
+  }
+
+}
+;
+const e = React.createElement;
+
+function removeHidden(items) {
+  const visible = [];
+
+  for (let item of items) {
+    if (item.hidden && eval(item.hidden)) continue;
+
+    if (isExpansion(item)) {
+      visible.push(item);
+      item.content = removeHidden(item.content);
+
+      if (item.content.length === 0) {
+        visible.pop(); // remove empty expansion
+      }
+    } else {
+      visible.push(item);
+    }
+  }
+
+  return visible;
+}
+
+content = removeHidden(content);
+initGroupAndItemIds();
+
+function loadModule(modulePage) {
+  return new Promise((resolve, reject) => {
+    console.log('loading: ' + resourceUrl + modulePage.modulePath);
+    import(resourceUrl + modulePage.modulePath).then(module => {
+      modulePage.module = module;
+      resolve(modulePage);
+    }).catch(error => {
+      console.warn('Unable to load ' + modulePage.label + ' because ' + error.message);
+      reject(modulePage);
+    });
+  });
+}
+
+;
+const moduleLoaders = [];
+flattenContent(content).forEach(item => {
+  if (isModulePageDef(item)) {
+    moduleLoaders.push(loadModule(item));
+  }
+}); // load content modules and start
+
+Promise.all(moduleLoaders).then(() => {
+  const domContainer = document.querySelector('#main_react_container');
+  ReactDOM.render(e(Main), domContainer);
+});
+//# sourceMappingURL=Main.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/Main.js.map


+ 51 - 0
themes/account/resources/PageNav.js

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../common/keycloak/web_modules/react.js";
+import { withRouter } from "../../common/keycloak/web_modules/react-router-dom.js";
+import { Nav, NavList } from "../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { makeNavItems, flattenContent } from "./ContentPages.js";
+
+class PageNavigation extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  findActiveItem() {
+    const currentPath = this.props.location.pathname;
+    const items = flattenContent(content);
+    const firstItem = items[0];
+
+    for (let item of items) {
+      const itemPath = '/' + item.path;
+
+      if (itemPath === currentPath) {
+        return item;
+      }
+    }
+
+    ;
+    return firstItem;
+  }
+
+  render() {
+    const activeItem = this.findActiveItem();
+    return React.createElement(Nav, null, React.createElement(NavList, null, makeNavItems(activeItem)));
+  }
+
+}
+
+export const PageNav = withRouter(PageNavigation);
+//# sourceMappingURL=PageNav.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/PageNav.js.map


+ 78 - 0
themes/account/resources/PageToolbar.js

@@ -0,0 +1,78 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../common/keycloak/web_modules/react.js";
+import { Dropdown, KebabToggle, Toolbar, ToolbarGroup, ToolbarItem } from "../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { ReferrerDropdownItem } from "./widgets/ReferrerDropdownItem.js";
+import { ReferrerLink } from "./widgets/ReferrerLink.js";
+import { LogoutButton, LogoutDropdownItem } from "./widgets/Logout.js";
+export class PageToolbar extends React.Component {
+  constructor(props) {
+    super(props);
+
+    _defineProperty(this, "hasReferrer", typeof referrerName !== 'undefined');
+
+    _defineProperty(this, "onKebabDropdownToggle", isKebabDropdownOpen => {
+      this.setState({
+        isKebabDropdownOpen
+      });
+    });
+
+    this.state = {
+      isKebabDropdownOpen: false
+    };
+  }
+
+  render() {
+    const kebabDropdownItems = [];
+
+    if (this.hasReferrer) {
+      kebabDropdownItems.push(React.createElement(ReferrerDropdownItem, {
+        key: "referrerDropdownItem"
+      }));
+    }
+
+    kebabDropdownItems.push(React.createElement(LogoutDropdownItem, {
+      key: "LogoutDropdownItem"
+    }));
+    return React.createElement(Toolbar, null, this.hasReferrer && React.createElement(ToolbarGroup, {
+      key: "referrerGroup"
+    }, React.createElement(ToolbarItem, {
+      className: "pf-m-icons",
+      key: "referrer"
+    }, React.createElement(ReferrerLink, null))), React.createElement(ToolbarGroup, {
+      key: "secondGroup"
+    }, React.createElement(ToolbarItem, {
+      className: "pf-m-icons",
+      key: "logout"
+    }, React.createElement(LogoutButton, null)), React.createElement(ToolbarItem, {
+      key: "kebab",
+      className: "pf-m-mobile"
+    }, React.createElement(Dropdown, {
+      isPlain: true,
+      position: "right",
+      toggle: React.createElement(KebabToggle, {
+        id: "mobileKebab",
+        onToggle: this.onKebabDropdownToggle
+      }),
+      isOpen: this.state.isKebabDropdownOpen,
+      dropdownItems: kebabDropdownItems
+    }))));
+  }
+
+}
+//# sourceMappingURL=PageToolbar.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/PageToolbar.js.map


+ 3 - 0
themes/account/resources/account-service/AccountServiceContext.js

@@ -0,0 +1,3 @@
+import * as React from "../../../common/keycloak/web_modules/react.js";
+export const AccountServiceContext = React.createContext(undefined);
+//# sourceMappingURL=AccountServiceContext.js.map

+ 1 - 0
themes/account/resources/account-service/AccountServiceContext.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/app/account-service/AccountServiceContext.tsx"],"names":["React","AccountServiceContext","createContext","undefined"],"mappings":"AAAA,OAAO,KAAKA,KAAZ;AAGA,OAAO,MAAMC,qBAAqB,GAAGD,KAAK,CAACE,aAAN,CAAsDC,SAAtD,CAA9B","sourcesContent":["import * as React from 'react';\nimport { AccountServiceClient } from './account.service';\n\nexport const AccountServiceContext = React.createContext<AccountServiceClient | undefined>(undefined);"],"file":"AccountServiceContext.js"}

+ 149 - 0
themes/account/resources/account-service/account.service.js

@@ -0,0 +1,149 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+import { ContentAlert } from "../content/ContentAlert.js";
+export class AccountServiceError extends Error {
+  constructor(response) {
+    super(response.statusText);
+    this.response = response;
+  }
+
+}
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
+ */
+
+export class AccountServiceClient {
+  constructor(keycloakService) {
+    _defineProperty(this, "kcSvc", void 0);
+
+    _defineProperty(this, "accountUrl", void 0);
+
+    this.kcSvc = keycloakService;
+    this.accountUrl = this.kcSvc.authServerUrl() + 'realms/' + this.kcSvc.realm() + '/account';
+  }
+
+  async doGet(endpoint, config) {
+    return this.doRequest(endpoint, { ...config,
+      method: 'get'
+    });
+  }
+
+  async doDelete(endpoint, config) {
+    return this.doRequest(endpoint, { ...config,
+      method: 'delete'
+    });
+  }
+
+  async doPost(endpoint, body, config) {
+    return this.doRequest(endpoint, { ...config,
+      body: JSON.stringify(body),
+      method: 'post'
+    });
+  }
+
+  async doPut(endpoint, body, config) {
+    return this.doRequest(endpoint, { ...config,
+      body: JSON.stringify(body),
+      method: 'put'
+    });
+  }
+
+  async doRequest(endpoint, config) {
+    const response = await fetch(this.makeUrl(endpoint, config).toString(), await this.makeConfig(config));
+
+    try {
+      response.data = await response.json();
+    } catch (e) {} // ignore.  Might be empty
+
+
+    if (!response.ok) {
+      this.handleError(response);
+      throw new AccountServiceError(response);
+    }
+
+    return response;
+  }
+
+  handleError(response) {
+    if (response !== null && response.status === 401) {
+      if (this.kcSvc.authenticated() && !this.kcSvc.audiencePresent()) {
+        // authenticated and the audience is not present => not allowed
+        window.location.href = baseUrl + '#/forbidden';
+      } else {
+        // session timed out?
+        this.kcSvc.login();
+      }
+    }
+
+    if (response !== null && response.status === 403) {
+      window.location.href = baseUrl + '#/forbidden';
+    }
+
+    if (response !== null && response.data != null) {
+      if (response.data['errors'] != null) {
+        for (let err of response.data['errors']) ContentAlert.danger(err['errorMessage'], err['params']);
+      } else {
+        ContentAlert.danger(`${response.statusText}: ${response.data['errorMessage'] ? response.data['errorMessage'] : ''} ${response.data['error'] ? response.data['error'] : ''}`);
+      }
+
+      ;
+    } else {
+      ContentAlert.danger(response.statusText);
+    }
+  }
+
+  makeUrl(endpoint, config) {
+    if (endpoint.startsWith('http')) return new URL(endpoint);
+    const url = new URL(this.accountUrl + endpoint); // add request params
+
+    if (config && config.hasOwnProperty('params')) {
+      const params = config.params || {};
+      Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
+    }
+
+    return url;
+  }
+
+  makeConfig(config = {}) {
+    return new Promise(resolve => {
+      this.kcSvc.getToken().then(token => {
+        resolve({ ...config,
+          headers: {
+            'Content-Type': 'application/json',
+            ...config.headers,
+            Authorization: 'Bearer ' + token
+          }
+        });
+      }).catch(() => {
+        this.kcSvc.login();
+      });
+    });
+  }
+
+}
+window.addEventListener("unhandledrejection", event => {
+  event.promise.catch(error => {
+    if (error instanceof AccountServiceError) {
+      // We already handled the error. Ignore unhandled rejection.
+      event.preventDefault();
+    }
+  });
+});
+//# sourceMappingURL=account.service.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/account-service/account.service.js.map


+ 60 - 0
themes/account/resources/content.json

@@ -0,0 +1,60 @@
+[
+  {
+    "id": "personal-info",
+    "path": "personal-info",
+    "icon": "pf-icon-user",
+    "label": "personalInfoHtmlTitle",
+    "descriptionLabel": "personalInfoIntroMessage",
+    "modulePath": "/content/account-page/AccountPage.js",
+    "componentName": "AccountPage"
+  },
+  {
+    "id": "security",
+    "icon": "pf-icon-security",
+    "label": "accountSecurityTitle",
+    "descriptionLabel": "accountSecurityIntroMessage",
+    "content": [
+      {
+        "id": "signingin",
+        "path": "security/signingin",
+        "label": "signingIn",
+        "modulePath": "/content/signingin-page/SigningInPage.js",
+        "componentName": "SigningInPage"
+      },
+      {
+        "id": "device-activity",
+        "path": "security/device-activity",
+        "label": "device-activity",
+        "modulePath": "/content/device-activity-page/DeviceActivityPage.js",
+        "componentName": "DeviceActivityPage"
+      },
+      {
+        "id": "linked-accounts",
+        "path": "security/linked-accounts",
+        "label": "linkedAccountsHtmlTitle",
+        "modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js",
+        "componentName": "LinkedAccountsPage",
+        "hidden": "!features.isLinkedAccountsEnabled"
+      }
+    ]
+  },
+  {
+    "id": "applications",
+    "icon": "pf-icon-applications",
+    "path": "applications",
+    "label": "applications",
+    "descriptionLabel": "applicationsIntroMessage",
+    "modulePath": "/content/applications-page/ApplicationsPage.js",
+    "componentName": "ApplicationsPage"
+  },
+  {
+    "id": "resources",
+    "icon": "pf-icon-repository",
+    "path": "resources",
+    "label": "resources",
+    "descriptionLabel": "resourceIntroMessage",
+    "modulePath": "/content/my-resources-page/MyResourcesPage.js",
+    "componentName": "MyResourcesPage",
+    "hidden": "!features.isMyResourcesEnabled"
+  }
+]

+ 113 - 0
themes/account/resources/content/ContentAlert.js

@@ -0,0 +1,113 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { Alert, AlertActionCloseButton, AlertGroup, AlertVariant } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { Msg } from "../widgets/Msg.js";
+export class ContentAlert extends React.Component {
+  constructor(props) {
+    super(props);
+
+    _defineProperty(this, "hideAlert", key => {
+      this.setState({
+        alerts: [...this.state.alerts.filter(el => el.key !== key)]
+      });
+    });
+
+    _defineProperty(this, "getUniqueId", () => new Date().getTime());
+
+    _defineProperty(this, "postAlert", (variant, message, params) => {
+      const alerts = this.state.alerts;
+      const key = this.getUniqueId();
+      alerts.push({
+        key,
+        message: Msg.localize(message, params),
+        variant
+      });
+      this.setState({
+        alerts
+      });
+
+      if (variant !== AlertVariant.danger) {
+        setTimeout(() => this.hideAlert(key), 8000);
+      }
+    });
+
+    this.state = {
+      alerts: []
+    };
+    ContentAlert.instance = this;
+  }
+  /**
+   * @param message A literal text message or localization key.
+   */
+
+
+  static success(message, params) {
+    ContentAlert.instance.postAlert(AlertVariant.success, message, params);
+  }
+  /**
+   * @param message A literal text message or localization key.
+   */
+
+
+  static danger(message, params) {
+    ContentAlert.instance.postAlert(AlertVariant.danger, message, params);
+  }
+  /**
+   * @param message A literal text message or localization key.
+   */
+
+
+  static warning(message, params) {
+    ContentAlert.instance.postAlert(AlertVariant.warning, message, params);
+  }
+  /**
+   * @param message A literal text message or localization key.
+   */
+
+
+  static info(message, params) {
+    ContentAlert.instance.postAlert(AlertVariant.info, message, params);
+  }
+
+  render() {
+    return React.createElement(AlertGroup, {
+      isToast: true,
+      "aria-live": "assertive"
+    }, this.state.alerts.map(({
+      key,
+      variant,
+      message
+    }) => React.createElement(Alert, {
+      "aria-details": message,
+      isLiveRegion: true,
+      variant: variant,
+      title: message,
+      action: React.createElement(AlertActionCloseButton, {
+        title: message,
+        variantLabel: `${variant} alert`,
+        onClose: () => this.hideAlert(key)
+      }),
+      key: key
+    })));
+  }
+
+}
+
+_defineProperty(ContentAlert, "instance", void 0);
+//# sourceMappingURL=ContentAlert.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/ContentAlert.js.map


+ 65 - 0
themes/account/resources/content/ContentPage.js

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { Button, Grid, GridItem, Title, Tooltip } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { RedoIcon } from "../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { Msg } from "../widgets/Msg.js";
+import { ContentAlert } from "./ContentAlert.js";
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
+ */
+export class ContentPage extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return React.createElement(React.Fragment, null, React.createElement(ContentAlert, null), React.createElement("section", {
+      id: "page-heading",
+      className: "pf-c-page__main-section pf-m-light"
+    }, React.createElement(Grid, null, React.createElement(GridItem, {
+      span: 11
+    }, React.createElement(Title, {
+      headingLevel: "h1",
+      size: "3xl"
+    }, React.createElement("strong", null, React.createElement(Msg, {
+      msgKey: this.props.title
+    })))), this.props.onRefresh && React.createElement(GridItem, {
+      span: 1
+    }, React.createElement(Tooltip, {
+      content: React.createElement(Msg, {
+        msgKey: "refreshPage"
+      })
+    }, React.createElement(Button, {
+      "aria-describedby": "refresh page",
+      id: "refresh-page",
+      variant: "plain",
+      onClick: this.props.onRefresh
+    }, React.createElement(RedoIcon, {
+      size: "sm"
+    })))), this.props.introMessage && React.createElement(GridItem, {
+      span: 12
+    }, " ", React.createElement(Msg, {
+      msgKey: this.props.introMessage
+    })))), React.createElement("section", {
+      className: "pf-c-page__main-section pf-m-no-padding-mobile"
+    }, this.props.children));
+  }
+
+}
+;
+//# sourceMappingURL=ContentPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/ContentPage.js.map


+ 273 - 0
themes/account/resources/content/account-page/AccountPage.js

@@ -0,0 +1,273 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { ActionGroup, Button, Form, FormGroup, TextInput, Grid, GridItem, Expandable } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContentPage } from "../ContentPage.js";
+import { ContentAlert } from "../ContentAlert.js";
+import { LocaleSelector } from "../../widgets/LocaleSelectors.js";
+import { KeycloakContext } from "../../keycloak-service/KeycloakContext.js";
+import { AIACommand } from "../../util/AIACommand.js";
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
+ */
+export class AccountPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "isRegistrationEmailAsUsername", features.isRegistrationEmailAsUsername);
+
+    _defineProperty(this, "isEditUserNameAllowed", features.isEditUserNameAllowed);
+
+    _defineProperty(this, "isDeleteAccountAllowed", features.deleteAccountAllowed);
+
+    _defineProperty(this, "DEFAULT_STATE", {
+      errors: {
+        username: '',
+        firstName: '',
+        lastName: '',
+        email: ''
+      },
+      formFields: {
+        username: '',
+        firstName: '',
+        lastName: '',
+        email: '',
+        attributes: {}
+      }
+    });
+
+    _defineProperty(this, "state", this.DEFAULT_STATE);
+
+    _defineProperty(this, "handleCancel", () => {
+      this.fetchPersonalInfo();
+    });
+
+    _defineProperty(this, "handleChange", (value, event) => {
+      const target = event.currentTarget;
+      const name = target.name;
+      this.setState({
+        errors: { ...this.state.errors,
+          [name]: target.validationMessage
+        },
+        formFields: { ...this.state.formFields,
+          [name]: value
+        }
+      });
+    });
+
+    _defineProperty(this, "handleSubmit", event => {
+      event.preventDefault();
+      const form = event.target;
+      const isValid = form.checkValidity();
+
+      if (isValid) {
+        const reqData = { ...this.state.formFields
+        };
+        this.context.doPost("/", reqData).then(() => {
+          ContentAlert.success('accountUpdatedMessage');
+
+          if (locale !== this.state.formFields.attributes.locale[0]) {
+            window.location.reload();
+          }
+        });
+      } else {
+        const formData = new FormData(form);
+        const validationMessages = Array.from(formData.keys()).reduce((acc, key) => {
+          acc[key] = form.elements[key].validationMessage;
+          return acc;
+        }, {});
+        this.setState({
+          errors: { ...validationMessages
+          },
+          formFields: this.state.formFields
+        });
+      }
+    });
+
+    _defineProperty(this, "handleDelete", keycloak => {
+      new AIACommand(keycloak, "delete_account").execute();
+    });
+
+    _defineProperty(this, "UsernameInput", () => React.createElement(TextInput, {
+      isRequired: true,
+      type: "text",
+      id: "user-name",
+      name: "username",
+      maxLength: 254,
+      value: this.state.formFields.username,
+      onChange: this.handleChange,
+      isValid: this.state.errors.username === ''
+    }));
+
+    _defineProperty(this, "RestrictedUsernameInput", () => React.createElement(TextInput, {
+      isDisabled: true,
+      type: "text",
+      id: "user-name",
+      name: "username",
+      value: this.state.formFields.username
+    }));
+
+    this.context = context;
+    this.fetchPersonalInfo();
+  }
+
+  fetchPersonalInfo() {
+    this.context.doGet("/").then(response => {
+      this.setState(this.DEFAULT_STATE);
+      const formFields = response.data;
+
+      if (!formFields.attributes) {
+        formFields.attributes = {
+          locale: [locale]
+        };
+      } else if (!formFields.attributes.locale) {
+        formFields.attributes.locale = [locale];
+      }
+
+      this.setState({ ...{
+          formFields: formFields
+        }
+      });
+    });
+  }
+
+  render() {
+    const fields = this.state.formFields;
+    return React.createElement(ContentPage, {
+      title: "personalInfoHtmlTitle",
+      introMessage: "personalSubMessage"
+    }, React.createElement(Form, {
+      isHorizontal: true,
+      onSubmit: event => this.handleSubmit(event)
+    }, !this.isRegistrationEmailAsUsername && React.createElement(FormGroup, {
+      label: Msg.localize('username'),
+      isRequired: true,
+      fieldId: "user-name",
+      helperTextInvalid: this.state.errors.username,
+      isValid: this.state.errors.username === ''
+    }, this.isEditUserNameAllowed && React.createElement(this.UsernameInput, null), !this.isEditUserNameAllowed && React.createElement(this.RestrictedUsernameInput, null)), React.createElement(FormGroup, {
+      label: Msg.localize('email'),
+      isRequired: true,
+      fieldId: "email-address",
+      helperTextInvalid: this.state.errors.email,
+      isValid: this.state.errors.email === ''
+    }, React.createElement(TextInput, {
+      isRequired: true,
+      type: "email",
+      id: "email-address",
+      name: "email",
+      maxLength: 254,
+      value: fields.email,
+      onChange: this.handleChange,
+      isValid: this.state.errors.email === ''
+    })), React.createElement(FormGroup, {
+      label: Msg.localize('firstName'),
+      isRequired: true,
+      fieldId: "first-name",
+      helperTextInvalid: this.state.errors.firstName,
+      isValid: this.state.errors.firstName === ''
+    }, React.createElement(TextInput, {
+      isRequired: true,
+      type: "text",
+      id: "first-name",
+      name: "firstName",
+      maxLength: 254,
+      value: fields.firstName,
+      onChange: this.handleChange,
+      isValid: this.state.errors.firstName === ''
+    })), React.createElement(FormGroup, {
+      label: Msg.localize('lastName'),
+      isRequired: true,
+      fieldId: "last-name",
+      helperTextInvalid: this.state.errors.lastName,
+      isValid: this.state.errors.lastName === ''
+    }, React.createElement(TextInput, {
+      isRequired: true,
+      type: "text",
+      id: "last-name",
+      name: "lastName",
+      maxLength: 254,
+      value: fields.lastName,
+      onChange: this.handleChange,
+      isValid: this.state.errors.lastName === ''
+    })), features.isInternationalizationEnabled && React.createElement(FormGroup, {
+      label: Msg.localize('selectLocale'),
+      isRequired: true,
+      fieldId: "locale"
+    }, React.createElement(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]
+          }
+        }
+      })
+    })), React.createElement(ActionGroup, null, React.createElement(Button, {
+      type: "submit",
+      id: "save-btn",
+      variant: "primary",
+      isDisabled: Object.values(this.state.errors).filter(e => e !== '').length !== 0
+    }, React.createElement(Msg, {
+      msgKey: "doSave"
+    })), React.createElement(Button, {
+      id: "cancel-btn",
+      variant: "secondary",
+      onClick: this.handleCancel
+    }, React.createElement(Msg, {
+      msgKey: "doCancel"
+    })))), this.isDeleteAccountAllowed && React.createElement("div", {
+      id: "delete-account",
+      style: {
+        marginTop: "30px"
+      }
+    }, React.createElement(Expandable, {
+      toggleText: "Delete Account"
+    }, React.createElement(Grid, {
+      gutter: "sm"
+    }, React.createElement(GridItem, {
+      span: 6
+    }, React.createElement("p", null, React.createElement(Msg, {
+      msgKey: "deleteAccountWarning"
+    }))), React.createElement(GridItem, {
+      span: 4
+    }, React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(Button, {
+      id: "delete-account-btn",
+      variant: "danger",
+      onClick: () => this.handleDelete(keycloak),
+      className: "delete-button"
+    }, React.createElement(Msg, {
+      msgKey: "doDelete"
+    })))), React.createElement(GridItem, {
+      span: 2
+    })))));
+  }
+
+}
+
+_defineProperty(AccountPage, "contextType", AccountServiceContext);
+
+;
+//# sourceMappingURL=AccountPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/account-page/AccountPage.js.map


+ 70 - 0
themes/account/resources/content/aia-page/AppInitiatedActionPage.js

@@ -0,0 +1,70 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { withRouter } from "../../../../common/keycloak/web_modules/react-router-dom.js";
+import { AIACommand } from "../../util/AIACommand.js";
+import { Msg } from "../../widgets/Msg.js";
+import { Title, TitleLevel, Button, EmptyState, EmptyStateVariant, EmptyStateIcon, EmptyStateBody } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { PassportIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { KeycloakContext } from "../../keycloak-service/KeycloakContext.js"; // Note: This class demonstrates two features of the ContentPages framework:
+// 1) The PageDef is available as a React property.
+// 2) You can add additional custom properties to the PageDef.  In this case,
+//    we add a value called kcAction in content.js and access it by extending the
+//    PageDef interface.
+
+/**
+ * @author Stan Silvert
+ */
+class ApplicationInitiatedActionPage extends React.Component {
+  constructor(props) {
+    super(props);
+
+    _defineProperty(this, "handleClick", keycloak => {
+      new AIACommand(keycloak, this.props.pageDef.kcAction).execute();
+    });
+  }
+
+  render() {
+    return React.createElement(EmptyState, {
+      variant: EmptyStateVariant.full
+    }, React.createElement(EmptyStateIcon, {
+      icon: PassportIcon
+    }), React.createElement(Title, {
+      headingLevel: TitleLevel.h5,
+      size: "lg"
+    }, React.createElement(Msg, {
+      msgKey: this.props.pageDef.label,
+      params: this.props.pageDef.labelParams
+    })), React.createElement(EmptyStateBody, null, React.createElement(Msg, {
+      msgKey: "actionRequiresIDP"
+    })), React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(Button, {
+      variant: "primary",
+      onClick: () => this.handleClick(keycloak),
+      target: "_blank"
+    }, React.createElement(Msg, {
+      msgKey: "continue"
+    }))));
+  }
+
+}
+
+; // Note that the class name is not exported above.  To get access to the router,
+// we use withRouter() and export a different name.
+
+export const AppInitiatedActionPage = withRouter(ApplicationInitiatedActionPage);
+//# sourceMappingURL=AppInitiatedActionPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/aia-page/AppInitiatedActionPage.js.map


+ 186 - 0
themes/account/resources/content/applications-page/ApplicationsPage.js

@@ -0,0 +1,186 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { DataList, DataListItem, DataListItemRow, DataListCell, DataListToggle, DataListContent, DataListItemCells, Grid, GridItem, Button } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { InfoAltIcon, CheckIcon, ExternalLinkAltIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { ContentPage } from "../ContentPage.js";
+import { ContinueCancelModal } from "../../widgets/ContinueCancelModal.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Msg } from "../../widgets/Msg.js";
+export class ApplicationsPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "removeConsent", clientId => {
+      this.context.doDelete("/applications/" + clientId + "/consent").then(() => {
+        this.fetchApplications();
+      });
+    });
+
+    _defineProperty(this, "onToggle", row => {
+      const newIsRowOpen = this.state.isRowOpen;
+      newIsRowOpen[row] = !newIsRowOpen[row];
+      this.setState({
+        isRowOpen: newIsRowOpen
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      isRowOpen: [],
+      applications: []
+    };
+    this.fetchApplications();
+  }
+
+  fetchApplications() {
+    this.context.doGet("/applications").then(response => {
+      const applications = response.data || [];
+      this.setState({
+        isRowOpen: new Array(applications.length).fill(false),
+        applications: applications
+      });
+    });
+  }
+
+  elementId(item, application) {
+    return `application-${item}-${application.clientId}`;
+  }
+
+  render() {
+    return React.createElement(ContentPage, {
+      title: Msg.localize('applicationsPageTitle')
+    }, React.createElement(DataList, {
+      id: "applications-list",
+      "aria-label": Msg.localize('applicationsPageTitle'),
+      isCompact: true
+    }, React.createElement(DataListItem, {
+      id: "applications-list-header",
+      "aria-labelledby": "Columns names"
+    }, React.createElement(DataListItemRow, null, "// invisible toggle allows headings to line up properly", React.createElement("span", {
+      style: {
+        visibility: 'hidden'
+      }
+    }, React.createElement(DataListToggle, {
+      isExpanded: false,
+      id: "applications-list-header-invisible-toggle",
+      "aria-controls": "hidden"
+    })), React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "applications-list-client-id-header",
+        width: 2
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "applicationName"
+      }))), React.createElement(DataListCell, {
+        key: "applications-list-app-type-header",
+        width: 2
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "applicationType"
+      }))), React.createElement(DataListCell, {
+        key: "applications-list-status",
+        width: 2
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "status"
+      })))]
+    }))), this.state.applications.map((application, appIndex) => {
+      return React.createElement(DataListItem, {
+        id: this.elementId("client-id", application),
+        key: 'application-' + appIndex,
+        "aria-labelledby": "applications-list",
+        isExpanded: this.state.isRowOpen[appIndex]
+      }, React.createElement(DataListItemRow, null, React.createElement(DataListToggle, {
+        onClick: () => this.onToggle(appIndex),
+        isExpanded: this.state.isRowOpen[appIndex],
+        id: this.elementId('toggle', application),
+        "aria-controls": this.elementId("expandable", application)
+      }), React.createElement(DataListItemCells, {
+        dataListCells: [React.createElement(DataListCell, {
+          id: this.elementId('name', application),
+          width: 2,
+          key: 'app-' + appIndex
+        }, React.createElement(Button, {
+          component: "a",
+          variant: "link",
+          onClick: () => window.open(application.effectiveUrl)
+        }, application.clientName || application.clientId, " ", React.createElement(ExternalLinkAltIcon, null))), React.createElement(DataListCell, {
+          id: this.elementId('internal', application),
+          width: 2,
+          key: 'internal-' + appIndex
+        }, application.userConsentRequired ? Msg.localize('thirdPartyApp') : Msg.localize('internalApp'), application.offlineAccess ? ', ' + Msg.localize('offlineAccess') : ''), React.createElement(DataListCell, {
+          id: this.elementId('status', application),
+          width: 2,
+          key: 'status-' + appIndex
+        }, application.inUse ? Msg.localize('inUse') : Msg.localize('notInUse'))]
+      })), React.createElement(DataListContent, {
+        noPadding: false,
+        "aria-label": Msg.localize('applicationDetails'),
+        id: this.elementId("expandable", application),
+        isHidden: !this.state.isRowOpen[appIndex]
+      }, React.createElement(Grid, {
+        sm: 6,
+        md: 6,
+        lg: 6
+      }, React.createElement("div", {
+        className: "pf-c-content"
+      }, React.createElement(GridItem, null, React.createElement("strong", null, Msg.localize('client') + ': '), " ", application.clientId), application.description && React.createElement(GridItem, null, React.createElement("strong", null, Msg.localize('description') + ': '), " ", application.description), application.effectiveUrl && React.createElement(GridItem, null, React.createElement("strong", null, "URL: "), " ", React.createElement("span", {
+        id: this.elementId('effectiveurl', application)
+      }, application.effectiveUrl.split('"'))), application.consent && React.createElement(React.Fragment, null, React.createElement(GridItem, {
+        span: 12
+      }, React.createElement("strong", null, "Has access to:")), application.consent.grantedScopes.map((scope, scopeIndex) => {
+        return React.createElement(React.Fragment, {
+          key: 'scope-' + scopeIndex
+        }, React.createElement(GridItem, {
+          offset: 1
+        }, React.createElement(CheckIcon, null), " ", scope.name));
+      }), application.tosUri && React.createElement(GridItem, null, React.createElement("strong", null, Msg.localize('termsOfService') + ': '), application.tosUri), application.policyUri && React.createElement(GridItem, null, React.createElement("strong", null, Msg.localize('policy') + ': '), application.policyUri), React.createElement(GridItem, null, React.createElement("strong", null, Msg.localize('accessGrantedOn') + ': '), new Intl.DateTimeFormat(locale, {
+        year: 'numeric',
+        month: 'long',
+        day: 'numeric',
+        hour: 'numeric',
+        minute: 'numeric',
+        second: 'numeric'
+      }).format(application.consent.createDate)))), application.logoUri && React.createElement("div", {
+        className: "pf-c-content"
+      }, React.createElement("img", {
+        src: application.logoUri
+      }))), (application.consent || application.offlineAccess) && React.createElement(Grid, {
+        gutter: "sm"
+      }, React.createElement("hr", null), React.createElement(GridItem, null, React.createElement(React.Fragment, null, React.createElement(ContinueCancelModal, {
+        buttonTitle: Msg.localize('removeButton') // required
+        ,
+        buttonVariant: "secondary" // defaults to 'primary'
+        ,
+        modalTitle: Msg.localize('removeModalTitle') // required
+        ,
+        modalMessage: Msg.localize('removeModalMessage', [application.clientId]),
+        modalContinueButtonLabel: Msg.localize('confirmButton') // defaults to 'Continue'
+        ,
+        onContinue: () => this.removeConsent(application.clientId) // required
+
+      }))), React.createElement(GridItem, null, React.createElement(InfoAltIcon, null), " ", Msg.localize('infoMessage')))));
+    })));
+  }
+
+}
+
+_defineProperty(ApplicationsPage, "contextType", AccountServiceContext);
+
+;
+//# sourceMappingURL=ApplicationsPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/applications-page/ApplicationsPage.js.map


+ 28 - 0
themes/account/resources/content/authenticator-page/AuthenticatorPage.js

@@ -0,0 +1,28 @@
+/* 
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+export class AuthenticatorPage extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return React.createElement("div", null, React.createElement("h2", null, "Hello Authenticator Page"));
+  }
+
+}
+;
+//# sourceMappingURL=AuthenticatorPage.js.map

+ 1 - 0
themes/account/resources/content/authenticator-page/AuthenticatorPage.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../../src/app/content/authenticator-page/AuthenticatorPage.tsx"],"names":["React","AuthenticatorPage","Component","constructor","props","render"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA,OAAO,KAAKA,KAAZ;AAKA,OAAO,MAAMC,iBAAN,SAAgCD,KAAK,CAACE,SAAtC,CAAwE;AAEpEC,EAAAA,WAAW,CAACC,KAAD,EAAgC;AAC9C,UAAMA,KAAN;AACH;;AAEMC,EAAAA,MAAM,GAAoB;AAC7B,WACI,iCACE,2DADF,CADJ;AAKH;;AAZ0E;AAa9E","sourcesContent":["/* \n * Copyright 2018 Red Hat, Inc. and/or its affiliates.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as React from 'react';\n \nexport interface AuthenticatorPageProps {\n}\n \nexport class AuthenticatorPage extends React.Component<AuthenticatorPageProps> {\n    \n    public constructor(props: AuthenticatorPageProps) {\n        super(props);\n    }\n\n    public render(): React.ReactNode {\n        return (\n            <div>\n              <h2>Hello Authenticator Page</h2>\n            </div>\n        );\n    }\n};"],"file":"AuthenticatorPage.js"}

+ 264 - 0
themes/account/resources/content/device-activity-page/DeviceActivityPage.js

@@ -0,0 +1,264 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import TimeUtil from "../../util/TimeUtil.js";
+import { Bullseye, DataList, DataListItem, DataListItemRow, DataListCell, DataListItemCells, Grid, GridItem, Stack, StackItem } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { AmazonIcon, ChromeIcon, EdgeIcon, FirefoxIcon, GlobeIcon, InternetExplorerIcon, OperaIcon, SafariIcon, YandexInternationalIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContinueCancelModal } from "../../widgets/ContinueCancelModal.js";
+import { KeycloakContext } from "../../keycloak-service/KeycloakContext.js";
+import { ContentPage } from "../ContentPage.js";
+import { ContentAlert } from "../ContentAlert.js";
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
+ */
+export class DeviceActivityPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "signOutAll", keycloakService => {
+      this.context.doDelete("/sessions").then(() => {
+        keycloakService.logout();
+      });
+    });
+
+    _defineProperty(this, "signOutSession", (device, session) => {
+      this.context.doDelete("/sessions/" + session.id).then(() => {
+        this.fetchDevices();
+        ContentAlert.success('signedOutSession', [session.browser, device.os]);
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      devices: []
+    };
+    this.fetchDevices();
+  }
+
+  fetchDevices() {
+    this.context.doGet("/sessions/devices").then(response => {
+      console.log({
+        response
+      });
+      let devices = this.moveCurrentToTop(response.data);
+      this.setState({
+        devices: devices
+      });
+    });
+  } // current device and session should display at the top of their respective lists
+
+
+  moveCurrentToTop(devices) {
+    let currentDevice = devices[0];
+    devices.forEach((device, index) => {
+      if (device.current) {
+        currentDevice = device;
+        devices.splice(index, 1);
+        devices.unshift(device);
+      }
+    });
+    currentDevice.sessions.forEach((session, index) => {
+      if (session.current) {
+        const currentSession = currentDevice.sessions.splice(index, 1);
+        currentDevice.sessions.unshift(currentSession[0]);
+      }
+    });
+    return devices;
+  }
+
+  time(time) {
+    return TimeUtil.format(time * 1000);
+  }
+
+  elementId(item, session) {
+    return `session-${session.id.substring(0, 7)}-${item}`;
+  }
+
+  findBrowserIcon(session) {
+    const browserName = session.browser.toLowerCase();
+    if (browserName.includes("chrom")) return React.createElement(ChromeIcon, {
+      id: this.elementId('icon-chrome', session),
+      size: "lg"
+    }); // chrome or chromium
+
+    if (browserName.includes("firefox")) return React.createElement(FirefoxIcon, {
+      id: this.elementId('icon-firefox', session),
+      size: "lg"
+    });
+    if (browserName.includes("edge")) return React.createElement(EdgeIcon, {
+      id: this.elementId('icon-edge', session),
+      size: "lg"
+    });
+    if (browserName.startsWith("ie/")) return React.createElement(InternetExplorerIcon, {
+      id: this.elementId('icon-ie', session),
+      size: "lg"
+    });
+    if (browserName.includes("safari")) return React.createElement(SafariIcon, {
+      id: this.elementId('icon-safari', session),
+      size: "lg"
+    });
+    if (browserName.includes("opera")) return React.createElement(OperaIcon, {
+      id: this.elementId('icon-opera', session),
+      size: "lg"
+    });
+    if (browserName.includes("yandex")) return React.createElement(YandexInternationalIcon, {
+      id: this.elementId('icon-yandex', session),
+      size: "lg"
+    });
+    if (browserName.includes("amazon")) return React.createElement(AmazonIcon, {
+      id: this.elementId('icon-amazon', session),
+      size: "lg"
+    });
+    return React.createElement(GlobeIcon, {
+      id: this.elementId('icon-default', session),
+      size: "lg"
+    });
+  }
+
+  findOS(device) {
+    if (device.os.toLowerCase().includes('unknown')) return Msg.localize('unknownOperatingSystem');
+    return device.os;
+  }
+
+  findOSVersion(device) {
+    if (device.osVersion.toLowerCase().includes('unknown')) return '';
+    return device.osVersion;
+  }
+
+  makeClientsString(clients) {
+    let clientsString = "";
+    clients.forEach((client, index) => {
+      let clientName;
+
+      if (client.hasOwnProperty('clientName') && client.clientName !== undefined && client.clientName !== '') {
+        clientName = Msg.localize(client.clientName);
+      } else {
+        clientName = client.clientId;
+      }
+
+      clientsString += clientName;
+      if (clients.length > index + 1) clientsString += ', ';
+    });
+    return clientsString;
+  }
+
+  isShowSignOutAll(devices) {
+    if (devices.length === 0) return false;
+    if (devices.length > 1) return true;
+    if (devices[0].sessions.length > 1) return true;
+    return false;
+  }
+
+  render() {
+    return React.createElement(ContentPage, {
+      title: "device-activity",
+      onRefresh: this.fetchDevices.bind(this)
+    }, React.createElement(Stack, {
+      gutter: "md"
+    }, React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement(DataList, {
+      "aria-label": Msg.localize('signedInDevices')
+    }, React.createElement(DataListItem, {
+      key: "SignedInDevicesHeader",
+      "aria-labelledby": "signedInDevicesTitle",
+      isExpanded: false
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "signedInDevicesTitle",
+        width: 4
+      }, React.createElement("div", {
+        id: "signedInDevicesTitle",
+        className: "pf-c-content"
+      }, React.createElement("h2", null, React.createElement(Msg, {
+        msgKey: "signedInDevices"
+      })), React.createElement("p", null, React.createElement(Msg, {
+        msgKey: "signedInDevicesExplanation"
+      })))), React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(DataListCell, {
+        key: "signOutAllButton",
+        width: 1
+      }, this.isShowSignOutAll(this.state.devices) && React.createElement(ContinueCancelModal, {
+        buttonTitle: "signOutAllDevices",
+        buttonId: "sign-out-all",
+        modalTitle: "signOutAllDevices",
+        modalMessage: "signOutAllDevicesWarning",
+        onContinue: () => this.signOutAll(keycloak)
+      })))]
+    }))), React.createElement(DataListItem, {
+      "aria-labelledby": "sessions"
+    }, React.createElement(DataListItemRow, null, React.createElement(Grid, {
+      gutter: "sm"
+    }, React.createElement(GridItem, {
+      span: 12
+    }), " ", this.state.devices.map((device, deviceIndex) => {
+      return React.createElement(React.Fragment, null, device.sessions.map((session, sessionIndex) => {
+        return React.createElement(React.Fragment, {
+          key: 'device-' + deviceIndex + '-session-' + sessionIndex
+        }, React.createElement(GridItem, {
+          md: 3
+        }, React.createElement(Stack, null, React.createElement(StackItem, {
+          isFilled: false
+        }, React.createElement(Bullseye, null, this.findBrowserIcon(session))), React.createElement(StackItem, {
+          isFilled: false
+        }, React.createElement(Bullseye, {
+          id: this.elementId('ip', session)
+        }, session.ipAddress)), session.current && React.createElement(StackItem, {
+          isFilled: false
+        }, React.createElement(Bullseye, {
+          id: this.elementId('current-badge', session)
+        }, React.createElement("strong", {
+          className: "pf-c-badge pf-m-read"
+        }, React.createElement(Msg, {
+          msgKey: "currentSession"
+        })))))), React.createElement(GridItem, {
+          md: 9
+        }, !session.browser.toLowerCase().includes('unknown') && React.createElement("p", {
+          id: this.elementId('browser', session)
+        }, React.createElement("strong", null, session.browser, " / ", this.findOS(device), " ", this.findOSVersion(device))), React.createElement("p", {
+          id: this.elementId('last-access', session)
+        }, React.createElement("strong", null, Msg.localize('lastAccessedOn')), " ", this.time(session.lastAccess)), React.createElement("p", {
+          id: this.elementId('clients', session)
+        }, React.createElement("strong", null, Msg.localize('clients')), " ", this.makeClientsString(session.clients)), React.createElement("p", {
+          id: this.elementId('started', session)
+        }, React.createElement("strong", null, Msg.localize('startedAt')), " ", this.time(session.started)), React.createElement("p", {
+          id: this.elementId('expires', session)
+        }, React.createElement("strong", null, Msg.localize('expiresAt')), " ", this.time(session.expires)), !session.current && React.createElement(ContinueCancelModal, {
+          buttonTitle: "doSignOut",
+          buttonId: this.elementId('sign-out', session),
+          modalTitle: "doSignOut",
+          buttonVariant: "secondary",
+          modalMessage: "signOutWarning",
+          onContinue: () => this.signOutSession(device, session)
+        })));
+      }));
+    }), React.createElement(GridItem, {
+      span: 12
+    }), " ")))))));
+  }
+
+}
+
+_defineProperty(DeviceActivityPage, "contextType", AccountServiceContext);
+
+;
+//# sourceMappingURL=DeviceActivityPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/device-activity-page/DeviceActivityPage.js.map


+ 36 - 0
themes/account/resources/content/forbidden-page/ForbiddenPage.js

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { WarningTriangleIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { Msg } from "../../widgets/Msg.js";
+import EmptyMessageState from "../../widgets/EmptyMessageState.js";
+export class ForbiddenPage extends React.Component {
+  constructor() {
+    super({});
+  }
+
+  render() {
+    return React.createElement(EmptyMessageState, {
+      icon: WarningTriangleIcon,
+      messageKey: "forbidden"
+    }, React.createElement(Msg, {
+      msgKey: "needAccessRights"
+    }));
+  }
+
+}
+;
+//# sourceMappingURL=ForbiddenPage.js.map

+ 1 - 0
themes/account/resources/content/forbidden-page/ForbiddenPage.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../../src/app/content/forbidden-page/ForbiddenPage.tsx"],"names":["React","WarningTriangleIcon","Msg","EmptyMessageState","ForbiddenPage","Component","constructor","render"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,KAAKA,KAAZ;AAEA,SAASC,mBAAT;AACA,SAAQC,GAAR;AACA,OAAOC,iBAAP;AAGA,OAAO,MAAMC,aAAN,SAA4BJ,KAAK,CAACK,SAAlC,CAA4C;AAExCC,EAAAA,WAAW,GAAG;AACjB,UAAM,EAAN;AACH;;AAEMC,EAAAA,MAAM,GAAoB;AAC7B,WACI,oBAAC,iBAAD;AAAmB,MAAA,IAAI,EAAEN,mBAAzB;AAA8C,MAAA,UAAU,EAAC;AAAzD,OACI,oBAAC,GAAD;AAAK,MAAA,MAAM,EAAC;AAAZ,MADJ,CADJ;AAKH;;AAZ8C;AAalD","sourcesContent":["/*\n * Copyright 2020 Red Hat, Inc. and/or its affiliates.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport * as React from 'react';\n\nimport { WarningTriangleIcon } from '@patternfly/react-icons';\nimport {Msg} from '../../widgets/Msg';\nimport EmptyMessageState from '../../widgets/EmptyMessageState';\n\n\nexport class ForbiddenPage extends React.Component {\n\n    public constructor() {\n        super({});\n    }\n\n    public render(): React.ReactNode {\n        return (\n            <EmptyMessageState icon={WarningTriangleIcon} messageKey=\"forbidden\">\n                <Msg msgKey=\"needAccessRights\"/>\n            </EmptyMessageState>\n        );\n    }\n};"],"file":"ForbiddenPage.js"}

+ 272 - 0
themes/account/resources/content/linked-accounts-page/LinkedAccountsPage.js

@@ -0,0 +1,272 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { withRouter } from "../../../../common/keycloak/web_modules/react-router-dom.js";
+import { Badge, Button, DataList, DataListAction, DataListItemCells, DataListCell, DataListItemRow, Stack, StackItem, Title, TitleLevel, DataListItem } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { BitbucketIcon, CubeIcon, FacebookIcon, GithubIcon, GitlabIcon, GoogleIcon, InstagramIcon, LinkIcon, LinkedinIcon, MicrosoftIcon, OpenshiftIcon, PaypalIcon, StackOverflowIcon, TwitterIcon, UnlinkIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContentPage } from "../ContentPage.js";
+import { createRedirect } from "../../util/RedirectUri.js";
+
+/**
+ * @author Stan Silvert
+ */
+class LinkedAccountsPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    this.context = context;
+    this.state = {
+      linkedAccounts: [],
+      unLinkedAccounts: []
+    };
+    this.getLinkedAccounts();
+  }
+
+  getLinkedAccounts() {
+    this.context.doGet("/linked-accounts").then(response => {
+      console.log({
+        response
+      });
+      const linkedAccounts = response.data.filter(account => account.connected);
+      const unLinkedAccounts = response.data.filter(account => !account.connected);
+      this.setState({
+        linkedAccounts: linkedAccounts,
+        unLinkedAccounts: unLinkedAccounts
+      });
+    });
+  }
+
+  unLinkAccount(account) {
+    const url = '/linked-accounts/' + account.providerName;
+    this.context.doDelete(url).then(response => {
+      console.log({
+        response
+      });
+      this.getLinkedAccounts();
+    });
+  }
+
+  linkAccount(account) {
+    const url = '/linked-accounts/' + account.providerName;
+    const redirectUri = createRedirect(this.props.location.pathname);
+    this.context.doGet(url, {
+      params: {
+        providerId: account.providerName,
+        redirectUri
+      }
+    }).then(response => {
+      console.log({
+        response
+      });
+      window.location.href = response.data.accountLinkUri;
+    });
+  }
+
+  render() {
+    return React.createElement(ContentPage, {
+      title: Msg.localize('linkedAccountsTitle'),
+      introMessage: Msg.localize('linkedAccountsIntroMessage')
+    }, React.createElement(Stack, {
+      gutter: "md"
+    }, React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement(Title, {
+      headingLevel: TitleLevel.h2,
+      size: "2xl"
+    }, React.createElement(Msg, {
+      msgKey: "linkedLoginProviders"
+    })), React.createElement(DataList, {
+      id: "linked-idps",
+      "aria-label": "foo"
+    }, this.makeRows(this.state.linkedAccounts, true))), React.createElement(StackItem, {
+      isFilled: true
+    }), React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement(Title, {
+      headingLevel: TitleLevel.h2,
+      size: "2xl"
+    }, React.createElement(Msg, {
+      msgKey: "unlinkedLoginProviders"
+    })), React.createElement(DataList, {
+      id: "unlinked-idps",
+      "aria-label": "foo"
+    }, this.makeRows(this.state.unLinkedAccounts, false)))));
+  }
+
+  emptyRow(isLinked) {
+    let isEmptyMessage = '';
+
+    if (isLinked) {
+      isEmptyMessage = Msg.localize('linkedEmpty');
+    } else {
+      isEmptyMessage = Msg.localize('unlinkedEmpty');
+    }
+
+    return React.createElement(DataListItem, {
+      key: "emptyItem",
+      "aria-labelledby": "empty-item"
+    }, React.createElement(DataListItemRow, {
+      key: "emptyRow"
+    }, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "empty"
+      }, React.createElement("strong", null, isEmptyMessage))]
+    })));
+  }
+
+  makeRows(accounts, isLinked) {
+    if (accounts.length === 0) {
+      return this.emptyRow(isLinked);
+    }
+
+    return React.createElement(React.Fragment, null, " ", accounts.map(account => React.createElement(DataListItem, {
+      id: `${account.providerAlias}-idp`,
+      key: account.providerName,
+      "aria-labelledby": "simple-item1"
+    }, React.createElement(DataListItemRow, {
+      key: account.providerName
+    }, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "idp"
+      }, React.createElement(Stack, null, React.createElement(StackItem, {
+        isFilled: true
+      }, this.findIcon(account)), React.createElement(StackItem, {
+        id: `${account.providerAlias}-idp-name`,
+        isFilled: true
+      }, React.createElement("h2", null, React.createElement("strong", null, account.displayName))))), React.createElement(DataListCell, {
+        key: "badge"
+      }, React.createElement(Stack, null, React.createElement(StackItem, {
+        isFilled: true
+      }), React.createElement(StackItem, {
+        id: `${account.providerAlias}-idp-badge`,
+        isFilled: true
+      }, this.badge(account)))), React.createElement(DataListCell, {
+        key: "username"
+      }, React.createElement(Stack, null, React.createElement(StackItem, {
+        isFilled: true
+      }), React.createElement(StackItem, {
+        id: `${account.providerAlias}-idp-username`,
+        isFilled: true
+      }, account.linkedUsername)))]
+    }), React.createElement(DataListAction, {
+      "aria-labelledby": "foo",
+      "aria-label": "foo action",
+      id: "setPasswordAction"
+    }, isLinked && React.createElement(Button, {
+      id: `${account.providerAlias}-idp-unlink`,
+      variant: "link",
+      onClick: () => this.unLinkAccount(account)
+    }, React.createElement(UnlinkIcon, {
+      size: "sm"
+    }), " ", React.createElement(Msg, {
+      msgKey: "unLink"
+    })), !isLinked && React.createElement(Button, {
+      id: `${account.providerAlias}-idp-link`,
+      variant: "link",
+      onClick: () => this.linkAccount(account)
+    }, React.createElement(LinkIcon, {
+      size: "sm"
+    }), " ", React.createElement(Msg, {
+      msgKey: "link"
+    })))))), " ");
+  }
+
+  badge(account) {
+    if (account.social) {
+      return React.createElement(Badge, null, React.createElement(Msg, {
+        msgKey: "socialLogin"
+      }));
+    }
+
+    return React.createElement(Badge, {
+      style: {
+        backgroundColor: "green"
+      }
+    }, React.createElement(Msg, {
+      msgKey: "systemDefined"
+    }));
+  }
+
+  findIcon(account) {
+    const socialIconId = `${account.providerAlias}-idp-icon-social`;
+    if (account.providerName.toLowerCase().includes('github')) return React.createElement(GithubIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('linkedin')) return React.createElement(LinkedinIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('facebook')) return React.createElement(FacebookIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('google')) return React.createElement(GoogleIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('instagram')) return React.createElement(InstagramIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('microsoft')) return React.createElement(MicrosoftIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('bitbucket')) return React.createElement(BitbucketIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('twitter')) return React.createElement(TwitterIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('openshift')) return React.createElement(OpenshiftIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('gitlab')) return React.createElement(GitlabIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('paypal')) return React.createElement(PaypalIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    if (account.providerName.toLowerCase().includes('stackoverflow')) return React.createElement(StackOverflowIcon, {
+      id: socialIconId,
+      size: "xl"
+    });
+    return React.createElement(CubeIcon, {
+      id: `${account.providerAlias}-idp-icon-default`,
+      size: "xl"
+    });
+  }
+
+}
+
+_defineProperty(LinkedAccountsPage, "contextType", AccountServiceContext);
+
+;
+const LinkedAccountsPagewithRouter = withRouter(LinkedAccountsPage);
+export { LinkedAccountsPagewithRouter as LinkedAccountsPage };
+//# sourceMappingURL=LinkedAccountsPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/linked-accounts-page/LinkedAccountsPage.js.map


+ 40 - 0
themes/account/resources/content/my-resources-page/AbstractResourceTable.js

@@ -0,0 +1,40 @@
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { Msg } from "../../widgets/Msg.js";
+export class AbstractResourcesTable extends React.Component {
+  hasPermissions(row) {
+    return this.state.permissions.has(row) && this.state.permissions.get(row).length > 0;
+  }
+
+  firstUser(row) {
+    if (!this.hasPermissions(row)) return 'ERROR!!!!'; // should never happen
+
+    return this.state.permissions.get(row)[0].username;
+  }
+
+  numOthers(row) {
+    if (!this.hasPermissions(row)) return -1; // should never happen
+
+    return this.state.permissions.get(row).length - 1;
+  }
+
+  sharedWithUsersMessage(row) {
+    if (!this.hasPermissions(row)) return React.createElement(React.Fragment, null, React.createElement(Msg, {
+      msgKey: "resourceNotShared"
+    }));
+    return React.createElement(React.Fragment, null, React.createElement(Msg, {
+      msgKey: "resourceSharedWith"
+    }, React.createElement("strong", null, this.firstUser(row))), this.numOthers(row) > 0 && React.createElement(Msg, {
+      msgKey: "and"
+    }, React.createElement("strong", null, this.numOthers(row))), ".");
+  }
+
+  getClientName(client) {
+    if (client.hasOwnProperty('name') && client.name !== null && client.name !== '') {
+      return Msg.localize(client.name);
+    } else {
+      return client.clientId;
+    }
+  }
+
+}
+//# sourceMappingURL=AbstractResourceTable.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/AbstractResourceTable.js.map


+ 120 - 0
themes/account/resources/content/my-resources-page/EditTheResource.js

@@ -0,0 +1,120 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { Button, Modal, Form, FormGroup, TextInput, InputGroup } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { OkIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { Scope } from "./resource-model.js";
+import { Msg } from "../../widgets/Msg.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { ContentAlert } from "../ContentAlert.js";
+import { PermissionSelect } from "./PermissionSelect.js";
+export class EditTheResource extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "handleToggleDialog", () => {
+      if (this.state.isOpen) {
+        this.setState({
+          isOpen: false
+        });
+        this.props.onClose();
+      } else {
+        this.clearState();
+        this.setState({
+          isOpen: true
+        });
+      }
+    });
+
+    _defineProperty(this, "updateChanged", row => {
+      const changed = this.state.changed;
+      changed[row] = !changed[row];
+      this.setState({
+        changed
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      changed: [],
+      isOpen: false
+    };
+  }
+
+  clearState() {
+    this.setState({});
+  }
+
+  async savePermission(permission) {
+    await this.context.doPut(`/resources/${this.props.resource._id}/permissions`, [permission]);
+    ContentAlert.success(Msg.localize('updateSuccess'));
+  }
+
+  render() {
+    return React.createElement(React.Fragment, null, this.props.children(this.handleToggleDialog), React.createElement(Modal, {
+      title: 'Edit the resource - ' + this.props.resource.name,
+      isLarge: true,
+      isOpen: this.state.isOpen,
+      onClose: this.handleToggleDialog,
+      actions: [React.createElement(Button, {
+        key: "done",
+        variant: "link",
+        id: "done",
+        onClick: this.handleToggleDialog
+      }, React.createElement(Msg, {
+        msgKey: "done"
+      }))]
+    }, React.createElement(Form, {
+      isHorizontal: true
+    }, this.props.permissions.map((p, row) => React.createElement(React.Fragment, null, React.createElement(FormGroup, {
+      fieldId: `username-${row}`,
+      label: Msg.localize('User')
+    }, React.createElement(TextInput, {
+      id: `username-${row}`,
+      type: "text",
+      value: p.username,
+      isDisabled: true
+    })), React.createElement(FormGroup, {
+      fieldId: `permissions-${row}`,
+      label: Msg.localize('permissions'),
+      isRequired: true
+    }, React.createElement(InputGroup, null, React.createElement(PermissionSelect, {
+      scopes: this.props.resource.scopes,
+      selected: p.scopes.map(s => new Scope(s)),
+      direction: row === this.props.permissions.length - 1 ? "up" : "down",
+      onSelect: selection => {
+        p.scopes = selection.map(s => s.name);
+        this.updateChanged(row);
+      }
+    }), React.createElement(Button, {
+      id: `save-${row}`,
+      isDisabled: !this.state.changed[row],
+      onClick: () => this.savePermission(p)
+    }, React.createElement(OkIcon, null)))), React.createElement("hr", null))))));
+  }
+
+}
+
+_defineProperty(EditTheResource, "defaultProps", {
+  permissions: []
+});
+
+_defineProperty(EditTheResource, "contextType", AccountServiceContext);
+//# sourceMappingURL=EditTheResource.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/EditTheResource.js.map


+ 282 - 0
themes/account/resources/content/my-resources-page/MyResourcesPage.js

@@ -0,0 +1,282 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import parse from "../../util/ParseLink.js";
+import { Button, Level, LevelItem, Stack, StackItem, Tab, Tabs, TextInput } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Scope } from "./resource-model.js";
+import { ResourcesTable } from "./ResourcesTable.js";
+import { ContentPage } from "../ContentPage.js";
+import { Msg } from "../../widgets/Msg.js";
+import { SharedResourcesTable } from "./SharedResourcesTable.js";
+const MY_RESOURCES_TAB = 0;
+const SHARED_WITH_ME_TAB = 1;
+export class MyResourcesPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "first", 0);
+
+    _defineProperty(this, "max", 5);
+
+    _defineProperty(this, "makeScopeObj", scope => {
+      return new Scope(scope.name, scope.displayName);
+    });
+
+    _defineProperty(this, "fetchPermissionRequests", () => {
+      this.state.myResources.data.forEach(resource => {
+        this.fetchShareRequests(resource);
+      });
+    });
+
+    _defineProperty(this, "fetchPending", async () => {
+      const response = await this.context.doGet(`/resources/pending-requests`);
+      const resources = response.data || [];
+      resources.forEach(pendingRequest => {
+        this.state.sharedWithMe.data.forEach(resource => {
+          if (resource._id === pendingRequest._id) {
+            resource.shareRequests = [{
+              username: 'me',
+              scopes: pendingRequest.scopes
+            }];
+            this.forceUpdate();
+          }
+        });
+      });
+    });
+
+    _defineProperty(this, "handleFilterRequest", value => {
+      this.setState({
+        nameFilter: value
+      });
+      this.fetchFilteredResources({
+        name: value
+      });
+    });
+
+    _defineProperty(this, "handleFirstPageClick", () => {
+      this.fetchInitialResources();
+    });
+
+    _defineProperty(this, "handleNextClick", () => {
+      if (this.isSharedWithMeTab()) {
+        this.fetchResources(this.state.sharedWithMe.nextUrl);
+      } else {
+        this.fetchResources(this.state.myResources.nextUrl);
+      }
+    });
+
+    _defineProperty(this, "handlePreviousClick", () => {
+      if (this.isSharedWithMeTab()) {
+        this.fetchResources(this.state.sharedWithMe.prevUrl);
+      } else {
+        this.fetchResources(this.state.myResources.prevUrl);
+      }
+    });
+
+    _defineProperty(this, "handleTabClick", (event, tabIndex) => {
+      if (this.state.activeTabKey === tabIndex) return;
+      this.setState({
+        nameFilter: '',
+        activeTabKey: tabIndex
+      }, () => {
+        this.fetchInitialResources();
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      activeTabKey: MY_RESOURCES_TAB,
+      nameFilter: '',
+      isModalOpen: false,
+      myResources: {
+        nextUrl: '',
+        prevUrl: '',
+        data: []
+      },
+      sharedWithMe: {
+        nextUrl: '',
+        prevUrl: '',
+        data: []
+      }
+    };
+    this.fetchInitialResources();
+  }
+
+  isSharedWithMeTab() {
+    return this.state.activeTabKey === SHARED_WITH_ME_TAB;
+  }
+
+  hasNext() {
+    if (this.isSharedWithMeTab()) {
+      return this.state.sharedWithMe.nextUrl !== null && this.state.sharedWithMe.nextUrl !== '';
+    } else {
+      return this.state.myResources.nextUrl !== null && this.state.myResources.nextUrl !== '';
+    }
+  }
+
+  hasPrevious() {
+    if (this.isSharedWithMeTab()) {
+      return this.state.sharedWithMe.prevUrl !== null && this.state.sharedWithMe.prevUrl !== '';
+    } else {
+      return this.state.myResources.prevUrl !== null && this.state.myResources.prevUrl !== '';
+    }
+  }
+
+  fetchInitialResources() {
+    if (this.isSharedWithMeTab()) {
+      this.fetchResources("/resources/shared-with-me");
+    } else {
+      this.fetchResources("/resources", {
+        first: this.first,
+        max: this.max
+      });
+    }
+  }
+
+  fetchFilteredResources(params) {
+    if (this.isSharedWithMeTab()) {
+      this.fetchResources("/resources/shared-with-me", params);
+    } else {
+      this.fetchResources("/resources", { ...params,
+        first: this.first,
+        max: this.max
+      });
+    }
+  }
+
+  fetchResources(url, extraParams) {
+    this.context.doGet(url, {
+      params: extraParams
+    }).then(response => {
+      const resources = response.data || [];
+      resources.forEach(resource => resource.shareRequests = []); // serialize the Scope objects from JSON so that toString() will work.
+
+      resources.forEach(resource => resource.scopes = resource.scopes.map(this.makeScopeObj));
+
+      if (this.isSharedWithMeTab()) {
+        this.setState({
+          sharedWithMe: this.parseResourceResponse(response)
+        }, this.fetchPending);
+      } else {
+        this.setState({
+          myResources: this.parseResourceResponse(response)
+        }, this.fetchPermissionRequests);
+      }
+    });
+  }
+
+  fetchShareRequests(resource) {
+    this.context.doGet('/resources/' + resource._id + '/permissions/requests').then(response => {
+      resource.shareRequests = response.data || [];
+
+      if (resource.shareRequests.length > 0) {
+        this.forceUpdate();
+      }
+    });
+  }
+
+  parseResourceResponse(response) {
+    const links = response.headers.get('link') || undefined;
+    const parsed = parse(links);
+    let next = '';
+    let prev = '';
+
+    if (parsed !== null) {
+      if (parsed.next) next = parsed.next;
+      if (parsed.prev) prev = parsed.prev;
+    }
+
+    const resources = response.data || [];
+    return {
+      nextUrl: next,
+      prevUrl: prev,
+      data: resources
+    };
+  }
+
+  makeTab(eventKey, title, resources, sharedResourcesTab) {
+    return React.createElement(Tab, {
+      id: title,
+      eventKey: eventKey,
+      title: Msg.localize(title)
+    }, React.createElement(Stack, {
+      gutter: "md"
+    }, React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement("span", null)), React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement(Level, {
+      gutter: "md"
+    }, React.createElement(LevelItem, null, React.createElement(TextInput, {
+      value: this.state.nameFilter,
+      onChange: this.handleFilterRequest,
+      id: 'filter-' + title,
+      type: "text",
+      placeholder: Msg.localize('filterByName')
+    })))), React.createElement(StackItem, {
+      isFilled: true
+    }, !sharedResourcesTab && React.createElement(ResourcesTable, {
+      resources: resources
+    }), sharedResourcesTab && React.createElement(SharedResourcesTable, {
+      resources: resources
+    }))));
+  }
+
+  render() {
+    return React.createElement(ContentPage, {
+      title: "resources",
+      onRefresh: this.fetchInitialResources.bind(this)
+    }, React.createElement(Tabs, {
+      isFilled: true,
+      activeKey: this.state.activeTabKey,
+      onSelect: this.handleTabClick
+    }, this.makeTab(0, 'myResources', this.state.myResources, false), this.makeTab(1, 'sharedwithMe', this.state.sharedWithMe, true)), React.createElement(Level, {
+      gutter: "md"
+    }, React.createElement(LevelItem, null, this.hasPrevious() && React.createElement(Button, {
+      onClick: this.handlePreviousClick
+    }, "<", React.createElement(Msg, {
+      msgKey: "previousPage"
+    }))), React.createElement(LevelItem, null, this.hasPrevious() && React.createElement(Button, {
+      onClick: this.handleFirstPageClick
+    }, React.createElement(Msg, {
+      msgKey: "firstPage"
+    }))), React.createElement(LevelItem, null, this.hasNext() && React.createElement(Button, {
+      onClick: this.handleNextClick
+    }, React.createElement(Msg, {
+      msgKey: "nextPage"
+    }), ">"))));
+  }
+
+  clearNextPrev() {
+    const newMyResources = this.state.myResources;
+    newMyResources.nextUrl = '';
+    newMyResources.prevUrl = '';
+    this.setState({
+      myResources: newMyResources
+    });
+  }
+
+}
+
+_defineProperty(MyResourcesPage, "contextType", AccountServiceContext);
+
+;
+//# sourceMappingURL=MyResourcesPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/MyResourcesPage.js.map


+ 151 - 0
themes/account/resources/content/my-resources-page/PermissionRequest.js

@@ -0,0 +1,151 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { Button, Modal, Text, Badge, DataListItem, DataList, TextVariants, DataListItemRow, DataListItemCells, DataListCell, Chip, Split, SplitItem } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { UserCheckIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContentAlert } from "../ContentAlert.js";
+export class PermissionRequest extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "handleApprove", async (shareRequest, index) => {
+      this.handle(shareRequest.username, shareRequest.scopes, true);
+      this.props.resource.shareRequests.splice(index, 1);
+    });
+
+    _defineProperty(this, "handleDeny", async (shareRequest, index) => {
+      this.handle(shareRequest.username, shareRequest.scopes);
+      this.props.resource.shareRequests.splice(index, 1);
+    });
+
+    _defineProperty(this, "handle", async (username, scopes, approve = false) => {
+      const id = this.props.resource._id;
+      this.handleToggleDialog();
+      const permissionsRequest = await this.context.doGet(`/resources/${id}/permissions`);
+      const permissions = permissionsRequest.data || [];
+      const foundPermission = permissions.find(p => p.username === username);
+      const userScopes = foundPermission ? foundPermission.scopes : [];
+
+      if (approve) {
+        userScopes.push(...scopes);
+      }
+
+      try {
+        await this.context.doPut(`/resources/${id}/permissions`, [{
+          username: username,
+          scopes: userScopes
+        }]);
+        ContentAlert.success(Msg.localize('shareSuccess'));
+        this.props.onClose();
+      } catch (e) {
+        console.error('Could not update permissions', e.error);
+      }
+    });
+
+    _defineProperty(this, "handleToggleDialog", () => {
+      this.setState({
+        isOpen: !this.state.isOpen
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      isOpen: false
+    };
+  }
+
+  render() {
+    const id = `shareRequest-${this.props.resource.name.replace(/\s/, '-')}`;
+    return React.createElement(React.Fragment, null, React.createElement(Button, {
+      id: id,
+      variant: "link",
+      onClick: this.handleToggleDialog
+    }, React.createElement(UserCheckIcon, {
+      size: "lg"
+    }), React.createElement(Badge, null, this.props.resource.shareRequests.length)), React.createElement(Modal, {
+      id: `modal-${id}`,
+      title: Msg.localize('permissionRequests') + ' - ' + this.props.resource.name,
+      isLarge: true,
+      isOpen: this.state.isOpen,
+      onClose: this.handleToggleDialog,
+      actions: [React.createElement(Button, {
+        id: `close-${id}`,
+        key: "close",
+        variant: "link",
+        onClick: this.handleToggleDialog
+      }, React.createElement(Msg, {
+        msgKey: "close"
+      }))]
+    }, React.createElement(DataList, {
+      "aria-label": Msg.localize('permissionRequests')
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "permissions-name-header",
+        width: 5
+      }, React.createElement("strong", null, "Requestor")), React.createElement(DataListCell, {
+        key: "permissions-requested-header",
+        width: 5
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "permissionRequests"
+      }))), React.createElement(DataListCell, {
+        key: "permission-request-header",
+        width: 5
+      })]
+    })), this.props.resource.shareRequests.map((shareRequest, i) => React.createElement(DataListItem, {
+      key: i,
+      "aria-labelledby": "requestor"
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        id: `requestor${i}`,
+        key: `requestor${i}`
+      }, React.createElement("span", null, shareRequest.firstName, " ", shareRequest.lastName, " ", shareRequest.lastName ? '' : shareRequest.username), React.createElement("br", null), React.createElement(Text, {
+        component: TextVariants.small
+      }, shareRequest.email)), React.createElement(DataListCell, {
+        id: `permissions${i}`,
+        key: `permissions${i}`
+      }, shareRequest.scopes.map((scope, j) => React.createElement(Chip, {
+        key: j,
+        isReadOnly: true
+      }, scope))), React.createElement(DataListCell, {
+        key: `actions${i}`
+      }, React.createElement(Split, {
+        gutter: "sm"
+      }, React.createElement(SplitItem, null, React.createElement(Button, {
+        id: `accept-${i}-${id}`,
+        onClick: () => this.handleApprove(shareRequest, i)
+      }, "Accept")), React.createElement(SplitItem, null, React.createElement(Button, {
+        id: `deny-${i}-${id}`,
+        variant: "danger",
+        onClick: () => this.handleDeny(shareRequest, i)
+      }, "Deny"))))]
+    })))))));
+  }
+
+}
+
+_defineProperty(PermissionRequest, "defaultProps", {
+  permissions: [],
+  row: 0
+});
+
+_defineProperty(PermissionRequest, "contextType", AccountServiceContext);
+//# sourceMappingURL=PermissionRequest.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/PermissionRequest.js.map


+ 100 - 0
themes/account/resources/content/my-resources-page/PermissionSelect.js

@@ -0,0 +1,100 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { Select, SelectOption, SelectVariant } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+
+class ScopeValue {
+  constructor(value) {
+    _defineProperty(this, "value", void 0);
+
+    this.value = value;
+  }
+
+  toString() {
+    return this.value.displayName ? this.value.displayName : this.value.name;
+  }
+
+  compareTo(selectOption) {
+    return selectOption.name === this.value.name;
+  }
+
+}
+
+export class PermissionSelect extends React.Component {
+  constructor(props) {
+    super(props);
+
+    _defineProperty(this, "onSelect", (_event, selection) => {
+      const {
+        selected
+      } = this.state;
+      const {
+        onSelect
+      } = this.props;
+
+      if (selected.includes(selection)) {
+        this.setState(prevState => ({
+          selected: prevState.selected.filter(item => item !== selection)
+        }), () => onSelect(this.state.selected.map(sv => sv.value)));
+      } else {
+        this.setState(prevState => ({
+          selected: [...prevState.selected, selection]
+        }), () => onSelect(this.state.selected.map(sv => sv.value)));
+      }
+    });
+
+    _defineProperty(this, "onToggle", isExpanded => {
+      this.setState({
+        isExpanded
+      });
+    });
+
+    _defineProperty(this, "clearSelection", () => {
+      this.setState({
+        selected: [],
+        isExpanded: false
+      });
+      this.props.onSelect([]);
+    });
+
+    let values = [];
+
+    if (this.props.selected) {
+      values = this.props.selected.map(s => new ScopeValue(s));
+    }
+
+    this.state = {
+      isExpanded: false,
+      selected: values,
+      scopes: this.props.scopes.map((option, index) => React.createElement(SelectOption, {
+        key: index,
+        value: values.find(s => s.compareTo(option)) || new ScopeValue(option)
+      }))
+    };
+  }
+
+  render() {
+    const {
+      isExpanded,
+      selected
+    } = this.state;
+    const titleId = 'permission-id';
+    return React.createElement("div", null, React.createElement("span", {
+      id: titleId,
+      hidden: true
+    }, "Select the permissions"), React.createElement(Select, {
+      direction: this.props.direction || 'down',
+      variant: SelectVariant.typeaheadMulti,
+      ariaLabelTypeAhead: "Select the permissions",
+      onToggle: this.onToggle,
+      onSelect: this.onSelect,
+      onClear: this.clearSelection,
+      selections: selected,
+      isExpanded: isExpanded,
+      ariaLabelledBy: titleId,
+      placeholderText: "Select the permissions"
+    }, this.state.scopes));
+  }
+
+}
+//# sourceMappingURL=PermissionSelect.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/PermissionSelect.js.map


+ 309 - 0
themes/account/resources/content/my-resources-page/ResourcesTable.js

@@ -0,0 +1,309 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { DataList, DataListItem, DataListItemRow, DataListCell, DataListToggle, DataListContent, DataListItemCells, Level, LevelItem, Button, DataListAction, DataListActionVisibility, Dropdown, DropdownPosition, DropdownItem, KebabToggle } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { css } from "../../../../common/keycloak/web_modules/@patternfly/react-styles.js";
+import { Remove2Icon, RepositoryIcon, ShareAltIcon, EditAltIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { PermissionRequest } from "./PermissionRequest.js";
+import { ShareTheResource } from "./ShareTheResource.js";
+import { Msg } from "../../widgets/Msg.js";
+import { AbstractResourcesTable } from "./AbstractResourceTable.js";
+import { EditTheResource } from "./EditTheResource.js";
+import { ContentAlert } from "../ContentAlert.js";
+import EmptyMessageState from "../../widgets/EmptyMessageState.js";
+import { ContinueCancelModal } from "../../widgets/ContinueCancelModal.js";
+export class ResourcesTable extends AbstractResourcesTable {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "onToggle", row => {
+      const newIsRowOpen = this.state.isRowOpen;
+      newIsRowOpen[row] = !newIsRowOpen[row];
+      if (newIsRowOpen[row]) this.fetchPermissions(this.props.resources.data[row], row);
+      this.setState({
+        isRowOpen: newIsRowOpen
+      });
+    });
+
+    _defineProperty(this, "onContextToggle", (row, isOpen) => {
+      if (this.state.isModalActive) return;
+      const data = this.props.resources.data;
+      const contextOpen = this.state.contextOpen;
+      contextOpen[row] = isOpen;
+
+      if (isOpen) {
+        const index = row > data.length ? row - data.length - 1 : row;
+        this.fetchPermissions(data[index], index);
+      }
+
+      this.setState({
+        contextOpen
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      isRowOpen: [],
+      contextOpen: [],
+      isModalActive: false,
+      permissions: new Map()
+    };
+  }
+
+  fetchPermissions(resource, row) {
+    this.context.doGet(`/resources/${resource._id}/permissions`).then(response => {
+      const newPermissions = new Map(this.state.permissions);
+      newPermissions.set(row, response.data || []);
+      this.setState({
+        permissions: newPermissions
+      });
+    });
+  }
+
+  removeShare(resource, row) {
+    const permissions = this.state.permissions.get(row).map(a => ({
+      username: a.username,
+      scopes: []
+    }));
+    return this.context.doPut(`/resources/${resource._id}/permissions`, permissions).then(() => {
+      ContentAlert.success(Msg.localize('unShareSuccess'));
+    });
+  }
+
+  render() {
+    if (this.props.resources.data.length === 0) {
+      return React.createElement(EmptyMessageState, {
+        icon: RepositoryIcon,
+        messageKey: "notHaveAnyResource"
+      });
+    }
+
+    return React.createElement(DataList, {
+      "aria-label": Msg.localize('resources'),
+      id: "resourcesList"
+    }, React.createElement(DataListItem, {
+      key: "resource-header",
+      "aria-labelledby": "resource-header"
+    }, React.createElement(DataListItemRow, null, "// invisible toggle allows headings to line up properly", React.createElement("span", {
+      style: {
+        visibility: 'hidden'
+      }
+    }, React.createElement(DataListToggle, {
+      isExpanded: false,
+      id: "resource-header-invisible-toggle",
+      "aria-controls": "ex-expand1"
+    })), React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "resource-name-header",
+        width: 5
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "resourceName"
+      }))), React.createElement(DataListCell, {
+        key: "application-name-header",
+        width: 5
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "application"
+      }))), React.createElement(DataListCell, {
+        key: "permission-request-header",
+        width: 5
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "permissionRequests"
+      })))]
+    }))), this.props.resources.data.map((resource, row) => React.createElement(DataListItem, {
+      key: 'resource-' + row,
+      "aria-labelledby": resource.name,
+      isExpanded: this.state.isRowOpen[row]
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListToggle, {
+      onClick: () => this.onToggle(row),
+      isExpanded: this.state.isRowOpen[row],
+      id: 'resourceToggle-' + row,
+      "aria-controls": "ex-expand1"
+    }), React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        id: 'resourceName-' + row,
+        key: 'resourceName-' + row,
+        width: 5
+      }, React.createElement(Msg, {
+        msgKey: resource.name
+      })), React.createElement(DataListCell, {
+        id: 'resourceClient-' + row,
+        key: 'resourceClient-' + row,
+        width: 5
+      }, React.createElement("a", {
+        href: resource.client.baseUrl
+      }, this.getClientName(resource.client))), React.createElement(DataListCell, {
+        id: 'resourceRequests-' + row,
+        key: 'permissionRequests-' + row,
+        width: 5
+      }, resource.shareRequests.length > 0 && React.createElement(PermissionRequest, {
+        resource: resource,
+        onClose: () => this.fetchPermissions(resource, row)
+      }))]
+    }), React.createElement(DataListAction, {
+      className: DataListActionVisibility.hiddenOnLg,
+      "aria-labelledby": "check-action-item3 check-action-action3",
+      id: "check-action-action3",
+      "aria-label": "Actions"
+    }, React.createElement(Dropdown, {
+      isPlain: true,
+      position: DropdownPosition.right,
+      onSelect: () => this.setState({
+        isModalActive: true
+      }),
+      toggle: React.createElement(KebabToggle, {
+        onToggle: isOpen => this.onContextToggle(row + this.props.resources.data.length + 1, isOpen)
+      }),
+      isOpen: this.state.contextOpen[row + this.props.resources.data.length + 1],
+      dropdownItems: [React.createElement(ShareTheResource, {
+        resource: resource,
+        permissions: this.state.permissions.get(row),
+        sharedWithUsersMsg: this.sharedWithUsersMessage(row),
+        onClose: () => {
+          this.setState({
+            isModalActive: false
+          }, () => {
+            this.onContextToggle(row + this.props.resources.data.length + 1, false);
+            this.fetchPermissions(resource, row + this.props.resources.data.length + 1);
+          });
+        }
+      }, toggle => React.createElement(DropdownItem, {
+        id: 'mob-share-' + row,
+        key: "mob-share",
+        onClick: toggle
+      }, React.createElement(ShareAltIcon, null), " ", React.createElement(Msg, {
+        msgKey: "share"
+      }))), React.createElement(EditTheResource, {
+        resource: resource,
+        permissions: this.state.permissions.get(row),
+        onClose: () => {
+          this.setState({
+            isModalActive: false
+          }, () => {
+            this.onContextToggle(row + this.props.resources.data.length + 1, false);
+            this.fetchPermissions(resource, row + this.props.resources.data.length + 1);
+          });
+        }
+      }, toggle => React.createElement(DropdownItem, {
+        id: 'mob-edit-' + row,
+        key: "mob-edit",
+        isDisabled: this.numOthers(row) < 0,
+        onClick: toggle
+      }, React.createElement(EditAltIcon, null), " ", React.createElement(Msg, {
+        msgKey: "edit"
+      }))), React.createElement(ContinueCancelModal, {
+        render: toggle => React.createElement(DropdownItem, {
+          id: 'mob-remove-' + row,
+          key: "mob-remove",
+          isDisabled: this.numOthers(row) < 0,
+          onClick: toggle
+        }, React.createElement(Remove2Icon, null), " ", React.createElement(Msg, {
+          msgKey: "unShare"
+        })),
+        modalTitle: "unShare",
+        modalMessage: "unShareAllConfirm",
+        onClose: () => this.setState({
+          isModalActive: false
+        }, () => {
+          this.onContextToggle(row + this.props.resources.data.length + 1, false);
+        }),
+        onContinue: () => this.removeShare(resource, row).then(() => this.fetchPermissions(resource, row + this.props.resources.data.length + 1))
+      })]
+    })), React.createElement(DataListAction, {
+      id: `actions-${row}`,
+      className: css(DataListActionVisibility.visibleOnLg, DataListActionVisibility.hidden),
+      "aria-labelledby": "Row actions",
+      "aria-label": "Actions"
+    }, React.createElement(ShareTheResource, {
+      resource: resource,
+      permissions: this.state.permissions.get(row),
+      sharedWithUsersMsg: this.sharedWithUsersMessage(row),
+      onClose: () => this.fetchPermissions(resource, row)
+    }, toggle => React.createElement(Button, {
+      id: `share-${row}`,
+      variant: "link",
+      onClick: toggle
+    }, React.createElement(ShareAltIcon, null), " ", React.createElement(Msg, {
+      msgKey: "share"
+    }))), React.createElement(Dropdown, {
+      id: `action-menu-${row}`,
+      isPlain: true,
+      position: DropdownPosition.right,
+      toggle: React.createElement(KebabToggle, {
+        onToggle: isOpen => this.onContextToggle(row, isOpen)
+      }),
+      onSelect: () => this.setState({
+        isModalActive: true
+      }),
+      isOpen: this.state.contextOpen[row],
+      dropdownItems: [React.createElement(EditTheResource, {
+        resource: resource,
+        permissions: this.state.permissions.get(row),
+        onClose: () => {
+          this.setState({
+            isModalActive: false
+          }, () => {
+            this.onContextToggle(row, false);
+            this.fetchPermissions(resource, row);
+          });
+        }
+      }, toggle => React.createElement(DropdownItem, {
+        id: 'edit-' + row,
+        key: "edit",
+        component: "button",
+        isDisabled: this.numOthers(row) < 0,
+        onClick: toggle
+      }, React.createElement(EditAltIcon, null), " ", React.createElement(Msg, {
+        msgKey: "edit"
+      }))), React.createElement(ContinueCancelModal, {
+        render: toggle => React.createElement(DropdownItem, {
+          id: 'remove-' + row,
+          key: "remove",
+          component: "button",
+          isDisabled: this.numOthers(row) < 0,
+          onClick: toggle
+        }, React.createElement(Remove2Icon, null), " ", React.createElement(Msg, {
+          msgKey: "unShare"
+        })),
+        modalTitle: "unShare",
+        modalMessage: "unShareAllConfirm",
+        onClose: () => this.setState({
+          isModalActive: false
+        }, () => {
+          this.onContextToggle(row, false);
+        }),
+        onContinue: () => this.removeShare(resource, row).then(() => this.fetchPermissions(resource, row))
+      })]
+    }))), React.createElement(DataListContent, {
+      noPadding: false,
+      "aria-label": "Session Details",
+      id: 'ex-expand' + row,
+      isHidden: !this.state.isRowOpen[row]
+    }, React.createElement(Level, {
+      gutter: "md"
+    }, React.createElement(LevelItem, null, React.createElement("span", null)), React.createElement(LevelItem, {
+      id: 'shared-with-user-message-' + row
+    }, this.sharedWithUsersMessage(row)), React.createElement(LevelItem, null, React.createElement("span", null)))))));
+  }
+
+}
+
+_defineProperty(ResourcesTable, "contextType", AccountServiceContext);
+//# sourceMappingURL=ResourcesTable.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/ResourcesTable.js.map


+ 227 - 0
themes/account/resources/content/my-resources-page/ShareTheResource.js

@@ -0,0 +1,227 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { Button, Chip, ChipGroup, ChipGroupToolbarItem, Form, FormGroup, Gallery, GalleryItem, Modal, Stack, StackItem, TextInput } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContentAlert } from "../ContentAlert.js";
+import { PermissionSelect } from "./PermissionSelect.js";
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
+ */
+export class ShareTheResource extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "handleAddPermission", () => {
+      const rscId = this.props.resource._id;
+      const newPermissions = [];
+
+      for (const permission of this.state.permissionsSelected) {
+        newPermissions.push(permission.name);
+      }
+
+      const permissions = [];
+
+      for (const username of this.state.usernames) {
+        permissions.push({
+          username: username,
+          scopes: newPermissions
+        });
+      }
+
+      this.handleToggleDialog();
+      this.context.doPut(`/resources/${rscId}/permissions`, permissions).then(() => {
+        ContentAlert.success('shareSuccess');
+        this.props.onClose();
+      });
+    });
+
+    _defineProperty(this, "handleToggleDialog", () => {
+      if (this.state.isOpen) {
+        this.setState({
+          isOpen: false
+        });
+        this.props.onClose();
+      } else {
+        this.clearState();
+        this.setState({
+          isOpen: true
+        });
+      }
+    });
+
+    _defineProperty(this, "handleUsernameChange", username => {
+      this.setState({
+        usernameInput: username
+      });
+    });
+
+    _defineProperty(this, "handleAddUsername", async () => {
+      if (this.state.usernameInput !== '' && !this.state.usernames.includes(this.state.usernameInput)) {
+        const response = await this.context.doGet(`/resources/${this.props.resource._id}/user`, {
+          params: {
+            value: this.state.usernameInput
+          }
+        });
+
+        if (response.data && response.data.username) {
+          this.setState({
+            usernameInput: '',
+            usernames: [...this.state.usernames, this.state.usernameInput]
+          });
+        } else {
+          ContentAlert.info('userNotFound', [this.state.usernameInput]);
+        }
+      }
+    });
+
+    _defineProperty(this, "handleEnterKeyInAddField", event => {
+      if (event.key === "Enter") {
+        event.preventDefault();
+        this.handleAddUsername();
+      }
+    });
+
+    _defineProperty(this, "handleDeleteUsername", username => {
+      const newUsernames = this.state.usernames.filter(user => user !== username);
+      this.setState({
+        usernames: newUsernames
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      isOpen: false,
+      permissionsSelected: [],
+      permissionsUnSelected: this.props.resource.scopes,
+      usernames: [],
+      usernameInput: ''
+    };
+  }
+
+  clearState() {
+    this.setState({
+      permissionsSelected: [],
+      permissionsUnSelected: this.props.resource.scopes,
+      usernames: [],
+      usernameInput: ''
+    });
+  }
+
+  isAddDisabled() {
+    return this.state.usernameInput === '' || this.isAlreadyShared();
+  }
+
+  isAlreadyShared() {
+    for (let permission of this.props.permissions) {
+      if (permission.username === this.state.usernameInput) return true;
+    }
+
+    return false;
+  }
+
+  isFormInvalid() {
+    return this.state.usernames.length === 0 || this.state.permissionsSelected.length === 0;
+  }
+
+  render() {
+    return React.createElement(React.Fragment, null, this.props.children(this.handleToggleDialog), React.createElement(Modal, {
+      title: 'Share the resource - ' + this.props.resource.name,
+      isLarge: true,
+      isOpen: this.state.isOpen,
+      onClose: this.handleToggleDialog,
+      actions: [React.createElement(Button, {
+        key: "cancel",
+        variant: "link",
+        onClick: this.handleToggleDialog
+      }, React.createElement(Msg, {
+        msgKey: "cancel"
+      })), React.createElement(Button, {
+        key: "confirm",
+        variant: "primary",
+        id: "done",
+        onClick: this.handleAddPermission,
+        isDisabled: this.isFormInvalid()
+      }, React.createElement(Msg, {
+        msgKey: "done"
+      }))]
+    }, React.createElement(Stack, {
+      gutter: "md"
+    }, React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement(Form, null, React.createElement(FormGroup, {
+      label: "Add users to share your resource with",
+      type: "string",
+      helperTextInvalid: Msg.localize('resourceAlreadyShared'),
+      fieldId: "username",
+      isRequired: true,
+      isValid: !this.isAlreadyShared()
+    }, React.createElement(Gallery, {
+      gutter: "sm"
+    }, React.createElement(GalleryItem, null, React.createElement(TextInput, {
+      value: this.state.usernameInput,
+      isValid: !this.isAlreadyShared(),
+      id: "username",
+      "aria-describedby": "username-helper",
+      placeholder: "Username or email",
+      onChange: this.handleUsernameChange,
+      onKeyPress: this.handleEnterKeyInAddField
+    })), React.createElement(GalleryItem, null, React.createElement(Button, {
+      key: "add-user",
+      variant: "primary",
+      id: "add",
+      onClick: this.handleAddUsername,
+      isDisabled: this.isAddDisabled()
+    }, React.createElement(Msg, {
+      msgKey: "add"
+    })))), React.createElement(ChipGroup, {
+      withToolbar: true
+    }, React.createElement(ChipGroupToolbarItem, {
+      key: "users-selected",
+      categoryName: "Share with "
+    }, this.state.usernames.map(currentChip => React.createElement(Chip, {
+      key: currentChip,
+      onClick: () => this.handleDeleteUsername(currentChip)
+    }, currentChip))))), React.createElement(FormGroup, {
+      label: "",
+      fieldId: "permissions-selected"
+    }, React.createElement(PermissionSelect, {
+      scopes: this.state.permissionsUnSelected,
+      onSelect: selection => this.setState({
+        permissionsSelected: selection
+      }),
+      direction: "up"
+    })))), React.createElement(StackItem, {
+      isFilled: true
+    }, React.createElement("br", null)), React.createElement(StackItem, {
+      isFilled: true
+    }, this.props.sharedWithUsersMsg))));
+  }
+
+}
+
+_defineProperty(ShareTheResource, "defaultProps", {
+  permissions: []
+});
+
+_defineProperty(ShareTheResource, "contextType", AccountServiceContext);
+//# sourceMappingURL=ShareTheResource.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/ShareTheResource.js.map


+ 103 - 0
themes/account/resources/content/my-resources-page/SharedResourcesTable.js

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { DataList, DataListItem, DataListItemRow, DataListCell, DataListItemCells, ChipGroup, ChipGroupToolbarItem, Chip } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { RepositoryIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { Msg } from "../../widgets/Msg.js";
+import { AbstractResourcesTable } from "./AbstractResourceTable.js";
+import EmptyMessageState from "../../widgets/EmptyMessageState.js";
+export class SharedResourcesTable extends AbstractResourcesTable {
+  constructor(props) {
+    super(props);
+    this.state = {
+      permissions: new Map()
+    };
+  }
+
+  render() {
+    if (this.props.resources.data.length === 0) {
+      return React.createElement(EmptyMessageState, {
+        icon: RepositoryIcon,
+        messageKey: "noResourcesSharedWithYou"
+      });
+    }
+
+    return React.createElement(DataList, {
+      "aria-label": Msg.localize('resources'),
+      id: "sharedResourcesList"
+    }, React.createElement(DataListItem, {
+      key: "resource-header",
+      "aria-labelledby": "resource-header"
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: "resource-name-header",
+        width: 2
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "resourceName"
+      }))), React.createElement(DataListCell, {
+        key: "application-name-header",
+        width: 2
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "application"
+      }))), React.createElement(DataListCell, {
+        key: "permission-header",
+        width: 2
+      }), React.createElement(DataListCell, {
+        key: "requests-header",
+        width: 2
+      })]
+    }))), this.props.resources.data.map((resource, row) => React.createElement(DataListItem, {
+      key: 'resource-' + row,
+      "aria-labelledby": resource.name
+    }, React.createElement(DataListItemRow, null, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        key: 'resourceName-' + row,
+        width: 2
+      }, React.createElement(Msg, {
+        msgKey: resource.name
+      })), React.createElement(DataListCell, {
+        key: 'resourceClient-' + row,
+        width: 2
+      }, React.createElement("a", {
+        href: resource.client.baseUrl
+      }, this.getClientName(resource.client))), React.createElement(DataListCell, {
+        key: 'permissions-' + row,
+        width: 2
+      }, resource.scopes.length > 0 && React.createElement(ChipGroup, {
+        withToolbar: true
+      }, React.createElement(ChipGroupToolbarItem, {
+        key: "permissions",
+        categoryName: Msg.localize('permissions')
+      }, resource.scopes.map(scope => React.createElement(Chip, {
+        key: scope.name,
+        isReadOnly: true
+      }, scope.displayName || scope.name))))), React.createElement(DataListCell, {
+        key: 'pending-' + row,
+        width: 2
+      }, resource.shareRequests.length > 0 && React.createElement(ChipGroup, {
+        withToolbar: true
+      }, React.createElement(ChipGroupToolbarItem, {
+        key: "permissions",
+        categoryName: Msg.localize('pending')
+      }, resource.shareRequests[0].scopes.map(scope => React.createElement(Chip, {
+        key: scope.name,
+        isReadOnly: true
+      }, scope.displayName || scope.name)))))]
+    })))));
+  }
+
+}
+//# sourceMappingURL=SharedResourcesTable.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/my-resources-page/SharedResourcesTable.js.map


+ 16 - 0
themes/account/resources/content/my-resources-page/resource-model.js

@@ -0,0 +1,16 @@
+export class Scope {
+  constructor(name, displayName) {
+    this.name = name;
+    this.displayName = displayName;
+  }
+
+  toString() {
+    if (this.hasOwnProperty('displayName') && this.displayName) {
+      return this.displayName;
+    } else {
+      return this.name;
+    }
+  }
+
+}
+//# sourceMappingURL=resource-model.js.map

+ 1 - 0
themes/account/resources/content/my-resources-page/resource-model.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../../src/app/content/my-resources-page/resource-model.ts"],"names":["Scope","constructor","name","displayName","toString","hasOwnProperty"],"mappings":"AAeA,OAAO,MAAMA,KAAN,CAAY;AACVC,EAAAA,WAAW,CAAQC,IAAR,EAA6BC,WAA7B,EAAmD;AAAA,SAA3CD,IAA2C,GAA3CA,IAA2C;AAAA,SAAtBC,WAAsB,GAAtBA,WAAsB;AAAE;;AAEhEC,EAAAA,QAAQ,GAAW;AACtB,QAAI,KAAKC,cAAL,CAAoB,aAApB,KAAuC,KAAKF,WAAhD,EAA8D;AAC1D,aAAO,KAAKA,WAAZ;AACH,KAFD,MAEO;AACH,aAAO,KAAKD,IAAZ;AACH;AACJ;;AATgB","sourcesContent":["export interface Resource {\n  _id: string;\n  name: string;\n  client: Client;\n  scopes: Scope[];\n  uris: string[];\n  shareRequests: Permission[];\n}\n\nexport interface Client {\n  baseUrl: string;\n  clientId: string;\n  name?: string;\n}\n\nexport class Scope {\n  public constructor(public name: string, public displayName?: string) {}\n\n  public toString(): string {\n      if (this.hasOwnProperty('displayName') && (this.displayName)) {\n          return this.displayName;\n      } else {\n          return this.name;\n      }\n  }\n}\n\nexport interface PaginatedResources {\n  nextUrl: string;\n  prevUrl: string;\n  data: Resource[];\n}\n\nexport interface Permission {\n  email?: string;\n  firstName?: string;\n  lastName?: string;\n  scopes: Scope[] | string[];  // this should be Scope[] - fix API\n  username: string;\n}\n"],"file":"resource-model.js"}

+ 31 - 0
themes/account/resources/content/page-not-found/PageNotFound.js

@@ -0,0 +1,31 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { WarningTriangleIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js";
+import { withRouter } from "../../../../common/keycloak/web_modules/react-router-dom.js";
+import { Msg } from "../../widgets/Msg.js";
+import EmptyMessageState from "../../widgets/EmptyMessageState.js";
+
+class PgNotFound extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return React.createElement(EmptyMessageState, {
+      icon: WarningTriangleIcon,
+      messageKey: "pageNotFound"
+    }, React.createElement(Msg, {
+      msgKey: "invalidRoute",
+      params: [this.props.location.pathname]
+    }));
+  }
+
+}
+
+;
+export const PageNotFound = withRouter(PgNotFound);
+//# sourceMappingURL=PageNotFound.js.map

+ 1 - 0
themes/account/resources/content/page-not-found/PageNotFound.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../../src/app/content/page-not-found/PageNotFound.tsx"],"names":["React","WarningTriangleIcon","withRouter","Msg","EmptyMessageState","PgNotFound","Component","constructor","props","render","location","pathname","PageNotFound"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AAEA,OAAO,KAAKA,KAAZ;AAEA,SAASC,mBAAT;AACA,SAAQC,UAAR;AACA,SAAQC,GAAR;AACA,OAAOC,iBAAP;;AAIA,MAAMC,UAAN,SAAyBL,KAAK,CAACM,SAA/B,CAA4D;AAEjDC,EAAAA,WAAW,CAACC,KAAD,EAA2B;AACzC,UAAMA,KAAN;AACH;;AAEMC,EAAAA,MAAM,GAAoB;AAC7B,WACI,oBAAC,iBAAD;AAAmB,MAAA,IAAI,EAAER,mBAAzB;AAA8C,MAAA,UAAU,EAAC;AAAzD,OACI,oBAAC,GAAD;AAAK,MAAA,MAAM,EAAC,cAAZ;AAA2B,MAAA,MAAM,EAAE,CAAC,KAAKO,KAAL,CAAWE,QAAX,CAAoBC,QAArB;AAAnC,MADJ,CADJ;AAKH;;AAZuD;;AAa3D;AAED,OAAO,MAAMC,YAAY,GAAGV,UAAU,CAACG,UAAD,CAA/B","sourcesContent":["/*\n * To change this license header, choose License Headers in Project Properties.\n * To change this template file, choose Tools | Templates\n * and open the template in the editor.\n */\n\nimport * as React from 'react';\n\nimport { WarningTriangleIcon } from '@patternfly/react-icons';\nimport {withRouter, RouteComponentProps} from 'react-router-dom';\nimport {Msg} from '../../widgets/Msg';\nimport EmptyMessageState from '../../widgets/EmptyMessageState';\n\nexport interface PageNotFoundProps extends RouteComponentProps {}\n\nclass PgNotFound extends React.Component<PageNotFoundProps> {\n\n    public constructor(props: PageNotFoundProps) {\n        super(props);\n    }\n\n    public render(): React.ReactNode {\n        return (\n            <EmptyMessageState icon={WarningTriangleIcon} messageKey=\"pageNotFound\">\n                <Msg msgKey=\"invalidRoute\" params={[this.props.location.pathname]} />\n            </EmptyMessageState>\n        );\n    }\n};\n\nexport const PageNotFound = withRouter(PgNotFound);"],"file":"PageNotFound.js"}

+ 331 - 0
themes/account/resources/content/signingin-page/SigningInPage.js

@@ -0,0 +1,331 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../../common/keycloak/web_modules/react.js";
+import { withRouter } from "../../../../common/keycloak/web_modules/react-router-dom.js";
+import { Button, DataList, DataListAction, DataListItemCells, DataListCell, DataListItem, DataListItemRow, Stack, StackItem, Title, TitleLevel, DataListActionVisibility, Dropdown, DropdownPosition, KebabToggle } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { AIACommand } from "../../util/AIACommand.js";
+import TimeUtil from "../../util/TimeUtil.js";
+import { AccountServiceContext } from "../../account-service/AccountServiceContext.js";
+import { ContinueCancelModal } from "../../widgets/ContinueCancelModal.js";
+import { Msg } from "../../widgets/Msg.js";
+import { ContentPage } from "../ContentPage.js";
+import { ContentAlert } from "../ContentAlert.js";
+import { KeycloakContext } from "../../keycloak-service/KeycloakContext.js";
+import { css } from "../../../../common/keycloak/web_modules/@patternfly/react-styles.js";
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
+ */
+class SigningInPage extends React.Component {
+  constructor(props, context) {
+    super(props);
+
+    _defineProperty(this, "context", void 0);
+
+    _defineProperty(this, "handleRemove", (credentialId, userLabel) => {
+      this.context.doDelete("/credentials/" + credentialId).then(() => {
+        this.getCredentialContainers();
+        ContentAlert.success('successRemovedMessage', [userLabel]);
+      });
+    });
+
+    this.context = context;
+    this.state = {
+      credentialContainers: new Map()
+    };
+    this.getCredentialContainers();
+  }
+
+  getCredentialContainers() {
+    this.context.doGet("/credentials").then(response => {
+      const allContainers = new Map();
+      const containers = response.data || [];
+      containers.forEach(container => {
+        let categoryMap = allContainers.get(container.category);
+
+        if (!categoryMap) {
+          categoryMap = new Map();
+          allContainers.set(container.category, categoryMap);
+        }
+
+        categoryMap.set(container.type, container);
+      });
+      this.setState({
+        credentialContainers: allContainers
+      });
+      console.log({
+        allContainers
+      });
+    });
+  }
+
+  static credElementId(credType, credId, item) {
+    return `${credType}-${item}-${credId.substring(0, 8)}`;
+  }
+
+  render() {
+    return React.createElement(ContentPage, {
+      title: "signingIn",
+      introMessage: "signingInSubMessage"
+    }, React.createElement(Stack, {
+      gutter: "md"
+    }, this.renderCategories()));
+  }
+
+  renderCategories() {
+    return React.createElement(React.Fragment, null, " ", Array.from(this.state.credentialContainers.keys()).map(category => React.createElement(StackItem, {
+      key: category,
+      isFilled: true
+    }, React.createElement(Title, {
+      id: `${category}-categ-title`,
+      headingLevel: TitleLevel.h2,
+      size: "2xl"
+    }, React.createElement("strong", null, React.createElement(Msg, {
+      msgKey: category
+    }))), React.createElement(DataList, {
+      "aria-label": "foo"
+    }, this.renderTypes(this.state.credentialContainers.get(category))))));
+  }
+
+  renderTypes(credTypeMap) {
+    return React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(React.Fragment, null, Array.from(credTypeMap.keys()).map((credType, index, typeArray) => [this.renderCredTypeTitle(credTypeMap.get(credType), keycloak), this.renderUserCredentials(credTypeMap, credType, keycloak), this.renderEmptyRow(credTypeMap.get(credType).type, index === typeArray.length - 1)])));
+  }
+
+  renderEmptyRow(type, isLast) {
+    if (isLast) return; // don't put empty row at the end
+
+    return React.createElement(DataListItem, {
+      "aria-labelledby": 'empty-list-item-' + type
+    }, React.createElement(DataListItemRow, {
+      key: 'empty-row-' + type
+    }, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, null)]
+    })));
+  }
+
+  renderUserCredentials(credTypeMap, credType, keycloak) {
+    const credContainer = credTypeMap.get(credType);
+    const userCredentials = credContainer.userCredentials;
+    const removeable = credContainer.removeable;
+    const type = credContainer.type;
+    const displayName = credContainer.displayName;
+
+    if (!userCredentials || userCredentials.length === 0) {
+      const localizedDisplayName = Msg.localize(displayName);
+      return React.createElement(DataListItem, {
+        key: "no-credentials-list-item",
+        "aria-labelledby": "no-credentials-list-item"
+      }, React.createElement(DataListItemRow, {
+        key: "no-credentials-list-item-row"
+      }, React.createElement(DataListItemCells, {
+        dataListCells: [React.createElement(DataListCell, {
+          key: 'no-credentials-cell-0'
+        }), React.createElement("strong", {
+          id: `${type}-not-set-up`,
+          key: 'no-credentials-cell-1'
+        }, React.createElement(Msg, {
+          msgKey: "notSetUp",
+          params: [localizedDisplayName]
+        })), React.createElement(DataListCell, {
+          key: 'no-credentials-cell-2'
+        })]
+      })));
+    }
+
+    userCredentials.forEach(credential => {
+      if (!credential.userLabel) credential.userLabel = Msg.localize(credential.type);
+
+      if (credential.hasOwnProperty('createdDate') && credential.createdDate && credential.createdDate > 0) {
+        credential.strCreatedDate = TimeUtil.format(credential.createdDate);
+      }
+    });
+    let updateAIA;
+
+    if (credContainer.updateAction) {
+      updateAIA = new AIACommand(keycloak, credContainer.updateAction);
+    }
+
+    return React.createElement(React.Fragment, {
+      key: "userCredentials"
+    }, " ", userCredentials.map(credential => React.createElement(DataListItem, {
+      id: `${SigningInPage.credElementId(type, credential.id, 'row')}`,
+      key: 'credential-list-item-' + credential.id,
+      "aria-labelledby": 'credential-list-item-' + credential.userLabel
+    }, React.createElement(DataListItemRow, {
+      key: 'userCredentialRow-' + credential.id
+    }, React.createElement(DataListItemCells, {
+      dataListCells: this.credentialRowCells(credential, type)
+    }), React.createElement(CredentialAction, {
+      credential: credential,
+      removeable: removeable,
+      updateAction: updateAIA,
+      credRemover: this.handleRemove
+    })))));
+  }
+
+  credentialRowCells(credential, type) {
+    const credRowCells = [];
+    credRowCells.push(React.createElement(DataListCell, {
+      id: `${SigningInPage.credElementId(type, credential.id, 'label')}`,
+      key: 'userLabel-' + credential.id
+    }, credential.userLabel));
+
+    if (credential.strCreatedDate) {
+      credRowCells.push(React.createElement(DataListCell, {
+        id: `${SigningInPage.credElementId(type, credential.id, 'created-at')}`,
+        key: 'created-' + credential.id
+      }, React.createElement("strong", null, React.createElement(Msg, {
+        msgKey: "credentialCreatedAt"
+      }), ": "), credential.strCreatedDate));
+      credRowCells.push(React.createElement(DataListCell, {
+        key: 'spacer-' + credential.id
+      }));
+    }
+
+    return credRowCells;
+  }
+
+  renderCredTypeTitle(credContainer, keycloak) {
+    if (!credContainer.hasOwnProperty('helptext') && !credContainer.hasOwnProperty('createAction')) return;
+    let setupAction;
+
+    if (credContainer.createAction) {
+      setupAction = new AIACommand(keycloak, credContainer.createAction);
+    }
+
+    const credContainerDisplayName = Msg.localize(credContainer.displayName);
+    return React.createElement(React.Fragment, {
+      key: 'credTypeTitle-' + credContainer.type
+    }, React.createElement(DataListItem, {
+      "aria-labelledby": 'type-datalistitem-' + credContainer.type
+    }, React.createElement(DataListItemRow, {
+      key: 'credTitleRow-' + credContainer.type
+    }, React.createElement(DataListItemCells, {
+      dataListCells: [React.createElement(DataListCell, {
+        width: 5,
+        key: 'credTypeTitle-' + credContainer.type
+      }, React.createElement(Title, {
+        headingLevel: TitleLevel.h3,
+        size: "2xl"
+      }, React.createElement("strong", {
+        id: `${credContainer.type}-cred-title`
+      }, React.createElement(Msg, {
+        msgKey: credContainer.displayName
+      }))), React.createElement("span", {
+        id: `${credContainer.type}-cred-help`
+      }, credContainer.helptext && React.createElement(Msg, {
+        msgKey: credContainer.helptext
+      })))]
+    }), credContainer.createAction && React.createElement(DataListAction, {
+      "aria-labelledby": "create",
+      "aria-label": "create action",
+      id: 'mob-setUpAction-' + credContainer.type,
+      className: DataListActionVisibility.hiddenOnLg
+    }, React.createElement(Dropdown, {
+      isPlain: true,
+      position: DropdownPosition.right,
+      toggle: React.createElement(KebabToggle, {
+        onToggle: isOpen => {
+          credContainer.open = isOpen;
+          this.setState({
+            credentialContainers: new Map(this.state.credentialContainers)
+          });
+        }
+      }),
+      isOpen: credContainer.open,
+      dropdownItems: [React.createElement("button", {
+        id: `mob-${credContainer.type}-set-up`,
+        className: "pf-c-button pf-m-link",
+        type: "button",
+        onClick: () => setupAction.execute()
+      }, React.createElement("span", {
+        className: "pf-c-button__icon"
+      }, React.createElement("i", {
+        className: "fas fa-plus-circle",
+        "aria-hidden": "true"
+      })), React.createElement(Msg, {
+        msgKey: "setUpNew",
+        params: [credContainerDisplayName]
+      }))]
+    })), credContainer.createAction && React.createElement(DataListAction, {
+      "aria-labelledby": "create",
+      "aria-label": "create action",
+      id: 'setUpAction-' + credContainer.type,
+      className: css(DataListActionVisibility.visibleOnLg, DataListActionVisibility.hidden)
+    }, React.createElement("button", {
+      id: `${credContainer.type}-set-up`,
+      className: "pf-c-button pf-m-link",
+      type: "button",
+      onClick: () => setupAction.execute()
+    }, React.createElement("span", {
+      className: "pf-c-button__icon"
+    }, React.createElement("i", {
+      className: "fas fa-plus-circle",
+      "aria-hidden": "true"
+    })), React.createElement(Msg, {
+      msgKey: "setUpNew",
+      params: [credContainerDisplayName]
+    }))))));
+  }
+
+}
+
+_defineProperty(SigningInPage, "contextType", AccountServiceContext);
+
+;
+;
+
+class CredentialAction extends React.Component {
+  render() {
+    if (this.props.updateAction) {
+      return React.createElement(DataListAction, {
+        "aria-labelledby": "foo",
+        "aria-label": "foo action",
+        id: 'updateAction-' + this.props.credential.id
+      }, React.createElement(Button, {
+        id: `${SigningInPage.credElementId(this.props.credential.type, this.props.credential.id, 'update')}`,
+        variant: "primary",
+        onClick: () => this.props.updateAction.execute()
+      }, React.createElement(Msg, {
+        msgKey: "update"
+      })));
+    }
+
+    if (this.props.removeable) {
+      const userLabel = this.props.credential.userLabel;
+      return React.createElement(DataListAction, {
+        "aria-labelledby": "foo",
+        "aria-label": "foo action",
+        id: 'removeAction-' + this.props.credential.id
+      }, React.createElement(ContinueCancelModal, {
+        buttonTitle: "remove",
+        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)
+      }));
+    }
+
+    return React.createElement(React.Fragment, null);
+  }
+
+}
+
+const SigningInPageWithRouter = withRouter(SigningInPage);
+export { SigningInPageWithRouter as SigningInPage };
+//# sourceMappingURL=SigningInPage.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/content/signingin-page/SigningInPage.js.map


+ 3 - 0
themes/account/resources/keycloak-service/KeycloakContext.js

@@ -0,0 +1,3 @@
+import * as React from "../../../common/keycloak/web_modules/react.js";
+export const KeycloakContext = React.createContext(undefined);
+//# sourceMappingURL=KeycloakContext.js.map

+ 1 - 0
themes/account/resources/keycloak-service/KeycloakContext.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/app/keycloak-service/KeycloakContext.tsx"],"names":["React","KeycloakContext","createContext","undefined"],"mappings":"AAAA,OAAO,KAAKA,KAAZ;AAGA,OAAO,MAAMC,eAAe,GAAGD,KAAK,CAACE,aAAN,CAAiDC,SAAjD,CAAxB","sourcesContent":["import * as React from 'react';\nimport { KeycloakService } from './keycloak.service';\n\nexport const KeycloakContext = React.createContext<KeycloakService | undefined>(undefined);"],"file":"KeycloakContext.js"}

+ 77 - 0
themes/account/resources/keycloak-service/keycloak.service.js

@@ -0,0 +1,77 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export class KeycloakService {
+  constructor(keycloak) {
+    _defineProperty(this, "keycloakAuth", void 0);
+
+    this.keycloakAuth = keycloak;
+  }
+
+  authenticated() {
+    return this.keycloakAuth.authenticated ? this.keycloakAuth.authenticated : false;
+  }
+
+  audiencePresent() {
+    if (this.keycloakAuth.tokenParsed) {
+      const audience = this.keycloakAuth.tokenParsed['aud'];
+      return audience === 'account' || Array.isArray(audience) && audience.indexOf('account') >= 0;
+    }
+
+    return false;
+  }
+
+  login(options) {
+    this.keycloakAuth.login(options);
+  }
+
+  logout(redirectUri = baseUrl) {
+    this.keycloakAuth.logout({
+      redirectUri: redirectUri
+    });
+  }
+
+  account() {
+    this.keycloakAuth.accountManagement();
+  }
+
+  authServerUrl() {
+    const authServerUrl = this.keycloakAuth.authServerUrl;
+    return authServerUrl.charAt(authServerUrl.length - 1) === '/' ? authServerUrl : authServerUrl + '/';
+  }
+
+  realm() {
+    return this.keycloakAuth.realm;
+  }
+
+  getToken() {
+    return new Promise((resolve, reject) => {
+      if (this.keycloakAuth.token) {
+        this.keycloakAuth.updateToken(5).success(() => {
+          resolve(this.keycloakAuth.token);
+        }).error(() => {
+          reject('Failed to refresh token');
+        });
+      } else {
+        reject('Not logged in');
+      }
+    });
+  }
+
+}
+//# sourceMappingURL=keycloak.service.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/keycloak-service/keycloak.service.js.map


TEMPAT SAMPAH
themes/account/resources/public/favicon.ico


+ 16 - 0
themes/account/resources/public/layout.css

@@ -0,0 +1,16 @@
+.brand {
+  height: 35px;
+}
+
+.delete-button {
+  width: 150px;
+  height: 50px;
+}
+
+@media (max-width: 320px) {
+  .delete-button {
+    width: 120px;
+    height: 50px;
+  }
+
+}

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/public/logo.svg


+ 33 - 0
themes/account/resources/util/AIACommand.js

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author Stan Silvert
+ */
+export class AIACommand {
+  constructor(keycloak, action) {
+    this.keycloak = keycloak;
+    this.action = action;
+  }
+
+  execute() {
+    this.keycloak.login({
+      action: this.action
+    });
+  }
+
+}
+//# sourceMappingURL=AIACommand.js.map

+ 1 - 0
themes/account/resources/util/AIACommand.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/app/util/AIACommand.ts"],"names":["AIACommand","constructor","keycloak","action","execute","login"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA;AACA;AACA;AACA,OAAO,MAAMA,UAAN,CAAiB;AAEpBC,EAAAA,WAAW,CAASC,QAAT,EAA4CC,MAA5C,EAA4D;AAAA,SAAnDD,QAAmD,GAAnDA,QAAmD;AAAA,SAAhBC,MAAgB,GAAhBA,MAAgB;AAAE;;AAElEC,EAAAA,OAAO,GAAS;AACnB,SAAKF,QAAL,CAAcG,KAAd,CAAoB;AAChBF,MAAAA,MAAM,EAAE,KAAKA;AADG,KAApB;AAIH;;AATmB","sourcesContent":["/*\n * Copyright 2019 Red Hat, Inc. and/or its affiliates.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {KeycloakService} from '../keycloak-service/keycloak.service';\n\n/**\n * @author Stan Silvert\n */\nexport class AIACommand {\n\n    constructor(private keycloak: KeycloakService, private action: string) {}\n\n    public execute(): void {\n        this.keycloak.login({\n            action: this.action,\n        })\n\n    }\n}"],"file":"AIACommand.js"}

+ 19 - 0
themes/account/resources/util/ParseLink.js

@@ -0,0 +1,19 @@
+function parse(linkHeader) {
+  if (!linkHeader) return {};
+  const links = linkHeader.split(/,\s*</);
+  return links.reduce((acc, link) => {
+    const matcher = link.match(/<?([^>]*)>(.*)/);
+    if (!matcher) return {};
+    const linkUrl = matcher[1];
+    const rel = matcher[2].match(/\s*(.+)\s*=\s*"?([^"]+)"?/);
+
+    if (rel) {
+      acc[rel[2]] = linkUrl;
+    }
+
+    return acc;
+  }, {});
+}
+
+export default parse;
+//# sourceMappingURL=ParseLink.js.map

+ 1 - 0
themes/account/resources/util/ParseLink.js.map

@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/app/util/ParseLink.ts"],"names":["parse","linkHeader","links","split","reduce","acc","link","matcher","match","linkUrl","rel"],"mappings":"AAMA,SAASA,KAAT,CAAeC,UAAf,EAAsD;AACpD,MAAI,CAACA,UAAL,EAAiB,OAAO,EAAP;AACjB,QAAMC,KAAK,GAAGD,UAAU,CAACE,KAAX,CAAiB,OAAjB,CAAd;AACA,SAAOD,KAAK,CAACE,MAAN,CAAoB,CAACC,GAAD,EAAaC,IAAb,KAAqC;AAC9D,UAAMC,OAAO,GAAGD,IAAI,CAACE,KAAL,CAAW,gBAAX,CAAhB;AACA,QAAI,CAACD,OAAL,EAAc,OAAO,EAAP;AACd,UAAME,OAAO,GAAGF,OAAO,CAAC,CAAD,CAAvB;AACA,UAAMG,GAAG,GAAGH,OAAO,CAAC,CAAD,CAAP,CAAWC,KAAX,CAAiB,2BAAjB,CAAZ;;AACA,QAAIE,GAAJ,EAAS;AACLL,MAAAA,GAAG,CAACK,GAAG,CAAC,CAAD,CAAJ,CAAH,GAAcD,OAAd;AACH;;AACD,WAAOJ,GAAP;AACD,GATM,EASJ,EATI,CAAP;AAUD;;AAED,eAAeL,KAAf","sourcesContent":["\nexport interface Links {\n  prev?: string;\n  next?: string;\n}\n\nfunction parse(linkHeader: string | undefined): Links {\n  if (!linkHeader) return {};\n  const links = linkHeader.split(/,\\s*</);\n  return links.reduce<Links>((acc: Links, link: string): Links => {\n    const matcher = link.match(/<?([^>]*)>(.*)/);\n    if (!matcher) return {};\n    const linkUrl = matcher[1];\n    const rel = matcher[2].match(/\\s*(.+)\\s*=\\s*\"?([^\"]+)\"?/);\n    if (rel) {\n        acc[rel[2]] = linkUrl;\n    }\n    return acc;\n  }, {});\n}\n\nexport default parse;"],"file":"ParseLink.js"}

+ 39 - 0
themes/account/resources/util/RedirectUri.js

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Create a redirect uri that can return to this application with referrer and referrer_uri intact.
+ * 
+ * @param currentLocation The ReactRouter location to return to.
+ *  
+ * @author Stan Silvert
+ */
+export const createRedirect = currentLocation => {
+  let redirectUri = baseUrl;
+
+  if (typeof referrer !== 'undefined') {
+    // '_hash_' is a workaround for when uri encoding is not
+    // sufficient to escape the # character properly.
+    // The problem is that both the redirect and the application URL contain a hash.
+    // The browser will consider anything after the first hash to be client-side.  So
+    // it sees the hash in the redirect param and stops.
+    redirectUri += "?referrer=" + referrer + "&referrer_uri=" + referrerUri.replace('#', '_hash_');
+  }
+
+  return encodeURIComponent(redirectUri) + encodeURIComponent("/#" + currentLocation);
+};
+//# sourceMappingURL=RedirectUri.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/util/RedirectUri.js.map


+ 50 - 0
themes/account/resources/util/TimeUtil.js

@@ -0,0 +1,50 @@
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2020 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @author Stan Silvert
+ */
+class TimeUtil {
+  constructor() {
+    _defineProperty(this, "options", {
+      year: 'numeric',
+      month: 'long',
+      day: 'numeric',
+      hour: 'numeric',
+      minute: 'numeric'
+    });
+
+    _defineProperty(this, "formatter", void 0);
+
+    try {
+      this.formatter = new Intl.DateTimeFormat(locale, this.options);
+    } catch (e) {
+      // unknown locale falling back to English
+      this.formatter = new Intl.DateTimeFormat('en', this.options);
+    }
+  }
+
+  format(time) {
+    return this.formatter.format(time);
+  }
+
+}
+
+const TimeUtilInstance = new TimeUtil();
+export default TimeUtilInstance;
+//# sourceMappingURL=TimeUtil.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/util/TimeUtil.js.map


+ 89 - 0
themes/account/resources/welcome-page-scripts.js

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var isWelcomePage = function () {
+    var winHash = window.location.hash;
+    return winHash === '#/';
+};
+
+var toggleReact = function () {
+    var welcomeScreen = document.getElementById("welcomeScreen");
+    var spinnerScreen = document.getElementById("spinner_screen");
+    var reactScreen = document.getElementById("main_react_container");
+
+    if (!isWelcomePage() && !isReactLoading) {
+        if (welcomeScreen) welcomeScreen.style.display = 'none';
+        if (spinnerScreen) spinnerScreen.style.display = 'none';
+        if (reactScreen) reactScreen.style.display = 'block';
+        if (reactScreen) reactScreen.style.height = '100%';
+    } else if (!isWelcomePage() && isReactLoading) {
+        if (welcomeScreen) welcomeScreen.style.display = 'none';
+        if (reactScreen) reactScreen.style.display = 'none';
+        if (spinnerScreen) spinnerScreen.style.display = 'block';
+        if (spinnerScreen) spinnerScreen.style.height = '100%';
+    } else {
+        if (reactScreen) reactScreen.style.display = 'none';
+        if (spinnerScreen) spinnerScreen.style.display = 'none';
+        if (welcomeScreen) welcomeScreen.style.display = 'block';
+        if (welcomeScreen) welcomeScreen.style.height = '100%';
+    }
+};
+
+function loggedInUserName() {
+    let userName = l18nMsg['unknownUser'];
+    if (keycloak.tokenParsed) {
+        const givenName = keycloak.tokenParsed.given_name;
+        const familyName = keycloak.tokenParsed.family_name;
+        const preferredUsername = keycloak.tokenParsed.preferred_username;
+        if (givenName && familyName) {
+            userName = [givenName, familyName].reduce((acc, value, index) =>
+                acc.replace('{{param_'+ index + '}}', value), l18nMsg['fullName']
+            );
+        } else {
+            userName = (givenName || familyName) || preferredUsername || userName;
+        }
+    }
+    return sanitize(userName);
+}
+
+function sanitize(dirtyString) {
+    let element = document.createElement("span");
+    element.textContent = dirtyString;
+    return element.innerHTML;
+}
+
+var toggleMobileDropdown = function () {	
+    var mobileDropdown = document.getElementById("landingMobileDropdown");
+    var mobileKebab = document.getElementById("landingMobileKebab");
+    var mobileKebabButton = document.getElementById("landingMobileKebabButton");
+    if (mobileDropdown.style.display === 'none') {
+        mobileDropdown.style.display = 'block';
+        mobileKebab.classList.add("pf-m-expanded");
+        mobileKebabButton.setAttribute("aria-expanded", "true");
+    } else {
+        mobileDropdown.style.display = 'none';
+        mobileKebab.classList.remove("pf-m-expanded");
+        mobileKebabButton.setAttribute("aria-expanded", "false");
+    }
+}
+
+var loadjs = function (url, loadListener) {
+    const script = document.createElement("script");
+    script.src = resourceUrl + url;
+    script.type = "module";
+    if (loadListener)
+        script.addEventListener("load", loadListener);
+    document.head.appendChild(script);
+};

+ 103 - 0
themes/account/resources/widgets/ContinueCancelModal.js

@@ -0,0 +1,103 @@
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { Modal, Button } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { Msg } from "./Msg.js";
+/**
+ * For any of these properties that are strings, you can
+ * pass in a localization key instead of a static string.
+ */
+
+/**
+ * This class renders a button that provides a continue/cancel modal dialog when clicked.  If the user selects 'Continue'
+ * then the onContinue function is executed.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc.
+ */
+export class ContinueCancelModal extends React.Component {
+  constructor(props) {
+    super(props);
+
+    _defineProperty(this, "handleModalToggle", () => {
+      this.setState(({
+        isModalOpen
+      }) => ({
+        isModalOpen: !isModalOpen
+      }));
+      if (this.props.onClose) this.props.onClose();
+    });
+
+    _defineProperty(this, "handleContinue", () => {
+      this.handleModalToggle();
+      this.props.onContinue();
+    });
+
+    this.state = {
+      isModalOpen: false
+    };
+  }
+
+  render() {
+    const {
+      isModalOpen
+    } = this.state;
+    return React.createElement(React.Fragment, null, !this.props.render && React.createElement(Button, {
+      id: this.props.buttonId,
+      variant: this.props.buttonVariant,
+      onClick: this.handleModalToggle,
+      isDisabled: this.props.isDisabled
+    }, React.createElement(Msg, {
+      msgKey: this.props.buttonTitle
+    })), this.props.render && this.props.render(this.handleModalToggle), React.createElement(Modal, _extends({}, this.props, {
+      title: Msg.localize(this.props.modalTitle),
+      isOpen: isModalOpen,
+      onClose: this.handleModalToggle,
+      actions: [React.createElement(Button, {
+        id: "modal-cancel",
+        key: "cancel",
+        variant: "secondary",
+        onClick: this.handleModalToggle
+      }, React.createElement(Msg, {
+        msgKey: this.props.modalCancelButtonLabel
+      })), React.createElement(Button, {
+        id: "modal-confirm",
+        key: "confirm",
+        variant: "primary",
+        onClick: this.handleContinue
+      }, React.createElement(Msg, {
+        msgKey: this.props.modalContinueButtonLabel
+      }))]
+    }), !this.props.modalMessage && this.props.children, this.props.modalMessage && React.createElement(Msg, {
+      msgKey: this.props.modalMessage
+    })));
+  }
+
+}
+
+_defineProperty(ContinueCancelModal, "defaultProps", {
+  buttonVariant: 'primary',
+  modalContinueButtonLabel: 'continue',
+  modalCancelButtonLabel: 'doCancel',
+  isDisabled: false,
+  isSmall: true
+});
+
+;
+//# sourceMappingURL=ContinueCancelModal.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/widgets/ContinueCancelModal.js.map


+ 38 - 0
themes/account/resources/widgets/EmptyMessageState.js

@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { EmptyState, EmptyStateVariant, Title, EmptyStateIcon, TitleLevel, EmptyStateBody } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { Msg } from "./Msg.js";
+export default class EmptyMessageState extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return React.createElement(EmptyState, {
+      variant: EmptyStateVariant.full
+    }, React.createElement(EmptyStateIcon, {
+      icon: this.props.icon
+    }), React.createElement(Title, {
+      headingLevel: TitleLevel.h5,
+      size: "lg"
+    }, React.createElement(Msg, {
+      msgKey: this.props.messageKey
+    })), React.createElement(EmptyStateBody, null, this.props.children));
+  }
+
+}
+//# sourceMappingURL=EmptyMessageState.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/widgets/EmptyMessageState.js.map


+ 41 - 0
themes/account/resources/widgets/LocaleSelectors.js

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { FormSelect, FormSelectOption } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+import { Msg } from "./Msg.js";
+;
+export class LocaleSelector extends React.Component {
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    return React.createElement(FormSelect, {
+      id: "locale-select",
+      value: this.props.value,
+      onChange: (value, event) => {
+        if (this.props.onChange) this.props.onChange(value, event);
+      },
+      "aria-label": Msg.localize('selectLocale')
+    }, availableLocales.map((locale, index) => React.createElement(FormSelectOption, {
+      key: index,
+      value: locale.locale,
+      label: locale.label
+    })));
+  }
+
+}
+//# sourceMappingURL=LocaleSelectors.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/widgets/LocaleSelectors.js.map


+ 46 - 0
themes/account/resources/widgets/Logout.js

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as React from "../../../common/keycloak/web_modules/react.js";
+import { Msg } from "./Msg.js";
+import { KeycloakContext } from "../keycloak-service/KeycloakContext.js";
+import { Button, DropdownItem } from "../../../common/keycloak/web_modules/@patternfly/react-core.js";
+
+function handleLogout(keycloak) {
+  keycloak.logout();
+}
+
+export class LogoutButton extends React.Component {
+  render() {
+    return React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(Button, {
+      id: "signOutButton",
+      onClick: () => handleLogout(keycloak)
+    }, React.createElement(Msg, {
+      msgKey: "doSignOut"
+    })));
+  }
+
+}
+export class LogoutDropdownItem extends React.Component {
+  render() {
+    return React.createElement(KeycloakContext.Consumer, null, keycloak => React.createElement(DropdownItem, {
+      id: "signOutLink",
+      key: "logout",
+      onClick: () => handleLogout(keycloak)
+    }, Msg.localize('doSignOut')));
+  }
+
+}
+//# sourceMappingURL=Logout.js.map

File diff ditekan karena terlalu besar
+ 0 - 0
themes/account/resources/widgets/Logout.js.map


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini