Browse Source

Merge branch 'ng-helpdesk' of github.com:pwm-project/pwm into ng-helpdesk

Joseph White 7 years ago
parent
commit
900a963ff4
100 changed files with 1599 additions and 2344 deletions
  1. BIN
      client/images/avatars/1.jpg
  2. BIN
      client/images/avatars/10.jpg
  3. BIN
      client/images/avatars/11.jpg
  4. BIN
      client/images/avatars/12.jpg
  5. BIN
      client/images/avatars/14.jpg
  6. BIN
      client/images/avatars/15.jpg
  7. BIN
      client/images/avatars/16.jpg
  8. BIN
      client/images/avatars/17.jpg
  9. BIN
      client/images/avatars/18.jpg
  10. BIN
      client/images/avatars/19.jpg
  11. BIN
      client/images/avatars/2.jpg
  12. BIN
      client/images/avatars/20.jpg
  13. BIN
      client/images/avatars/21.jpg
  14. BIN
      client/images/avatars/3.jpg
  15. BIN
      client/images/avatars/4.jpg
  16. BIN
      client/images/avatars/5.jpg
  17. BIN
      client/images/avatars/7.jpg
  18. BIN
      client/images/avatars/8.jpg
  19. BIN
      client/images/avatars/9.jpg
  20. 0 23
      client/images/icons/m_check_thick.svg
  21. 0 23
      client/images/icons/m_close_thick.svg
  22. 0 23
      client/images/icons/m_configure_thin.svg
  23. 0 23
      client/images/icons/m_down_thick.svg
  24. 0 23
      client/images/icons/m_lock_thin.svg
  25. 0 23
      client/images/icons/m_orgchart_thin.svg
  26. 0 23
      client/images/icons/m_password_thin.svg
  27. 0 23
      client/images/icons/m_reload_refresh_thin.svg
  28. 0 23
      client/images/icons/m_search_thick.svg
  29. 0 23
      client/images/icons/m_unlock_thin.svg
  30. 0 23
      client/images/icons/m_up_thick.svg
  31. 0 23
      client/images/icons/m_view-list_thin.svg
  32. 0 23
      client/images/icons/m_view-tile_thin.svg
  33. 4 0
      client/index.html
  34. 24 239
      client/package-lock.json
  35. 11 8
      client/package.json
  36. 30 28
      client/src/changepassword/autogen-change-password.component.html
  37. 1 1
      client/src/changepassword/autogen-change-password.component.scss
  38. 3 4
      client/src/changepassword/autogen-change-password.controller.ts
  39. 20 16
      client/src/changepassword/random-change-password.component.html
  40. 2 3
      client/src/changepassword/random-change-password.controller.ts
  41. 27 23
      client/src/changepassword/success-change-password.component.html
  42. 2 3
      client/src/changepassword/success-change-password.controller.ts
  43. 62 57
      client/src/changepassword/type-change-password.component.html
  44. 3 11
      client/src/changepassword/type-change-password.component.scss
  45. 3 4
      client/src/changepassword/type-change-password.controller.ts
  46. 1 1
      client/src/helpdesk/date.filters.ts
  47. 38 33
      client/src/helpdesk/helpdesk-detail-dialog.template.html
  48. 41 59
      client/src/helpdesk/helpdesk-detail.component.html
  49. 46 88
      client/src/helpdesk/helpdesk-detail.component.scss
  50. 34 20
      client/src/helpdesk/helpdesk-detail.component.ts
  51. 222 0
      client/src/helpdesk/helpdesk-search-base.component.ts
  52. 63 0
      client/src/helpdesk/helpdesk-search-cards.component.html
  53. 126 0
      client/src/helpdesk/helpdesk-search-cards.component.ts
  54. 85 0
      client/src/helpdesk/helpdesk-search-table.component.html
  55. 115 0
      client/src/helpdesk/helpdesk-search-table.component.ts
  56. 0 65
      client/src/helpdesk/helpdesk-search.component.html
  57. 19 161
      client/src/helpdesk/helpdesk-search.component.scss
  58. 0 234
      client/src/helpdesk/helpdesk-search.component.ts
  59. 8 5
      client/src/helpdesk/helpdesk.module.ts
  60. 0 0
      client/src/helpdesk/helpdesk.scss
  61. 3 4
      client/src/helpdesk/main.dev.ts
  62. 3 4
      client/src/helpdesk/main.ts
  63. 1 1
      client/src/helpdesk/recent-verifications-dialog.controller.ts
  64. 41 39
      client/src/helpdesk/recent-verifications-dialog.template.html
  65. 9 3
      client/src/helpdesk/routes.ts
  66. 2 3
      client/src/helpdesk/verifications-dialog.controller.ts
  67. 70 64
      client/src/helpdesk/verifications-dialog.template.html
  68. 0 15
      client/src/icons.json
  69. 2 3
      client/src/main.dev.ts
  70. 2 4
      client/src/main.ts
  71. 35 31
      client/src/peoplesearch/orgchart-search.component.html
  72. 0 4
      client/src/peoplesearch/orgchart-search.component.scss
  73. 6 2
      client/src/peoplesearch/orgchart-search.component.ts
  74. 1 1
      client/src/peoplesearch/orgchart.component.html
  75. 88 61
      client/src/peoplesearch/orgchart.component.scss
  76. 6 12
      client/src/peoplesearch/peoplesearch-base.component.ts
  77. 23 28
      client/src/peoplesearch/peoplesearch-cards.component.html
  78. 52 36
      client/src/peoplesearch/peoplesearch-table.component.html
  79. 8 1
      client/src/peoplesearch/peoplesearch-table.component.scss
  80. 22 1
      client/src/peoplesearch/peoplesearch-table.component.ts
  81. 3 2
      client/src/peoplesearch/peoplesearch.module.ts
  82. 26 20
      client/src/peoplesearch/peoplesearch.scss
  83. 15 14
      client/src/peoplesearch/person-card.component.html
  84. 24 15
      client/src/peoplesearch/person-card.component.ts
  85. 38 23
      client/src/peoplesearch/person-details-dialog.component.html
  86. 64 93
      client/src/peoplesearch/person-details-dialog.component.scss
  87. 8 0
      client/src/peoplesearch/person-details-dialog.component.ts
  88. 1 1
      client/src/services/base-config.service.dev.ts
  89. 1 1
      client/src/services/helpdesk-config.service.dev.ts
  90. 1 1
      client/src/services/helpdesk-config.service.ts
  91. 2 2
      client/src/services/helpdesk.service.dev.ts
  92. 9 7
      client/src/services/helpdesk.service.ts
  93. 2 1
      client/src/services/local-storage.service.ts
  94. 20 22
      client/src/services/people.data.json
  95. 1 1
      client/src/services/peoplesearch-config.service.ts
  96. 20 1
      client/src/services/pwm.service.ts
  97. 0 93
      client/src/ux/app-bar.component.scss
  98. 0 46
      client/src/ux/app-bar.component.ts
  99. 0 86
      client/src/ux/auto-complete.component.scss
  100. 0 236
      client/src/ux/auto-complete.component.ts

BIN
client/images/avatars/1.jpg


BIN
client/images/avatars/10.jpg


BIN
client/images/avatars/11.jpg


BIN
client/images/avatars/12.jpg


BIN
client/images/avatars/14.jpg


BIN
client/images/avatars/15.jpg


BIN
client/images/avatars/16.jpg


BIN
client/images/avatars/17.jpg


BIN
client/images/avatars/18.jpg


BIN
client/images/avatars/19.jpg


BIN
client/images/avatars/2.jpg


BIN
client/images/avatars/20.jpg


BIN
client/images/avatars/21.jpg


BIN
client/images/avatars/3.jpg


BIN
client/images/avatars/4.jpg


BIN
client/images/avatars/5.jpg


BIN
client/images/avatars/7.jpg


BIN
client/images/avatars/8.jpg


BIN
client/images/avatars/9.jpg


+ 0 - 23
client/images/icons/m_check_thick.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><polygon points="27.28 57.2 7.5 37.41 10.32 34.59 27.28 51.54 64.36 14.45 67.19 17.28 27.28 57.2" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_close_thick.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><polygon points="60.45 14.55 57.62 11.72 36.08 33.26 14.61 11.78 11.78 14.61 33.26 36.08 11.62 57.72 14.45 60.55 36.08 38.91 57.88 60.7 60.7 57.88 38.91 36.08 60.45 14.55" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_configure_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M36,46.32A10.37,10.37,0,1,1,46.45,36,10.4,10.4,0,0,1,36,46.32Zm0-17.38a7,7,0,1,0,7.07,7A7.05,7.05,0,0,0,36,28.93Z" fill="gray"/><path d="M54.43,39.26a3.35,3.35,0,0,0-2.52,2.18,16.94,16.94,0,0,1-.75,1.8,3.32,3.32,0,0,0,.23,3.32L55,51.6l-3.24,3.22-5.08-3.6A3.38,3.38,0,0,0,43.37,51a17.06,17.06,0,0,1-1.81.75,3.35,3.35,0,0,0-2.19,2.5l-1,6H33.75l-1-6a3.35,3.35,0,0,0-2.19-2.5A17.16,17.16,0,0,1,28.72,51a3.37,3.37,0,0,0-3.33.23L20.33,54.8l-3.24-3.22,3.59-5a3.34,3.34,0,0,0,.23-3.31,16.86,16.86,0,0,1-.75-1.8,3.36,3.36,0,0,0-2.52-2.18l-6.46-1V33.68l6.46-1a3.36,3.36,0,0,0,2.52-2.18,16.62,16.62,0,0,1,.75-1.8,3.32,3.32,0,0,0-.23-3.31L17,20.25,20.25,17l5.14,3.66a3.38,3.38,0,0,0,3.33.23,17,17,0,0,1,1.81-.75,3.35,3.35,0,0,0,2.19-2.5l1-6.33h4.58l1,6.33a3.35,3.35,0,0,0,2.2,2.5,16.79,16.79,0,0,1,1.81.75,3.38,3.38,0,0,0,3.33-.23L51.85,17l3.24,3.22-3.7,5.13a3.34,3.34,0,0,0-.23,3.31,16.84,16.84,0,0,1,.76,1.8,3.35,3.35,0,0,0,2.52,2.18l6,1v4.55Zm6.79-9.08L55.87,29.1a21,21,0,0,0-.94-2.24l3-4.53a3.47,3.47,0,0,0-.44-4.39l-3.37-3.35a3.51,3.51,0,0,0-4.42-.44l-4.56,3a21.22,21.22,0,0,0-2.26-.93L41.85,10.9a3.49,3.49,0,0,0-3.43-2.8H33.66a3.5,3.5,0,0,0-3.43,2.8l-1.07,5.34a21.23,21.23,0,0,0-2.26.93l-4.56-3a3.51,3.51,0,0,0-4.42.44l-3.37,3.35a3.47,3.47,0,0,0-.44,4.39l3,4.53a20.8,20.8,0,0,0-.94,2.25l-5.37,1.07A3.49,3.49,0,0,0,8,33.59v4.73a3.49,3.49,0,0,0,2.81,3.41l5.36,1.07A21,21,0,0,0,17.15,45l-3,4.53A3.47,3.47,0,0,0,14.56,54l3.37,3.35a3.51,3.51,0,0,0,4.42.44l4.56-3a20.7,20.7,0,0,0,2.26.93L30.23,61a3.5,3.5,0,0,0,3.43,2.8h4.76A3.5,3.5,0,0,0,41.85,61l1.08-5.33a21.24,21.24,0,0,0,2.26-.93l4.56,3a3.51,3.51,0,0,0,4.42-.44L57.53,54A3.47,3.47,0,0,0,58,49.58l-3-4.53a20.64,20.64,0,0,0,.94-2.25l5.36-1.07A3.49,3.49,0,0,0,64,38.32V33.59A3.49,3.49,0,0,0,61.23,30.17Z" fill="gray" fill-rule="evenodd"/></svg>

+ 0 - 23
client/images/icons/m_down_thick.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><polygon points="35.85 53.38 7.68 25.2 10.5 22.38 35.85 47.72 61.41 22.15 64.24 24.98 35.85 53.38" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_lock_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M59.23,34.43a38,38,0,0,0-8-3.38V22.19a15.17,15.17,0,0,0-30.34,0v8.86A38.07,38.07,0,0,0,13,34.37a1.5,1.5,0,0,0-.76,1.3V57.93a1.5,1.5,0,0,0,.76,1.3c6.16,3.51,14.35,5.45,23.07,5.45s17-2,23.18-5.51a1.5,1.5,0,0,0,.75-1.3V35.73A1.5,1.5,0,0,0,59.23,34.43ZM23.88,22.19a12.17,12.17,0,1,1,24.34,0v8.08a55.87,55.87,0,0,0-24.34,0V22.19ZM57,57c-5.64,3-13,4.69-20.93,4.69S20.85,60,15.23,57V36.56c5.63-3,13-4.64,20.82-4.64S51.34,33.59,57,36.62V57Z" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_orgchart_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>m_brand</title><path d="M65,43.46H55.4V35.75a1,1,0,0,0-1-1H37.48V28.36H47.92a1.5,1.5,0,0,0,1.5-1.5V8.91a1.5,1.5,0,0,0-1.5-1.5H25.18a1.5,1.5,0,0,0-1.5,1.5V26.86a1.5,1.5,0,0,0,1.5,1.5H35.48v6.39H18.55a1,1,0,0,0-1,1v7.71H7.93A1.5,1.5,0,0,0,6.43,45V62.9a1.5,1.5,0,0,0,1.5,1.5H29.17a1.5,1.5,0,0,0,1.5-1.5V45a1.5,1.5,0,0,0-1.5-1.5H19.55V36.75H53.4v6.71H43.78a1.5,1.5,0,0,0-1.5,1.5V62.9a1.5,1.5,0,0,0,1.5,1.5H65a1.5,1.5,0,0,0,1.5-1.5V45A1.5,1.5,0,0,0,65,43.46Zm-38.34-33H46.42V25.36H26.68V10.41Zm1,51H9.43V46.46H27.67V61.4Zm35.85,0H45.28V46.46H63.52V61.4Z" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_password_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M62.7,57.32H28a21.38,21.38,0,0,1,0-42.75H62.7v3H28c-9.75,0-18,8.41-18,18.37s8.24,18.37,18,18.37H62.7v3Z" fill="gray"/><circle cx="27.2" cy="35.95" r="4.66" fill="gray"/><circle cx="58.34" cy="35.95" r="4.66" fill="gray"/><circle cx="42.69" cy="35.95" r="4.66" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_reload_refresh_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>m_014_reload_refresh_thin</title><path d="M52.91,20.76a1.5,1.5,0,0,0-2.21,2,22.24,22.24,0,1,1-11.36-6.65l-6.51,6.51A1.52,1.52,0,0,0,35,24.76l9.12-9.12a1.5,1.5,0,0,0,0-2.12l-9-9A1.5,1.5,0,0,0,33,6.64L39.36,13a25.26,25.26,0,1,0,13.55,7.74Z" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_search_thick.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M64.5,61.57,46.4,42.7a20.11,20.11,0,1,0-2.88,2.77l18.1,18.87ZM15.16,29.77a16,16,0,1,1,16,16A16,16,0,0,1,15.16,29.77Z" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_unlock_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M59.23,34.43c-6.17-3.55-14.41-5.51-23.18-5.51a54.71,54.71,0,0,0-12.17,1.34V22.18a12.17,12.17,0,0,1,24.34,0h3a15.17,15.17,0,0,0-30.34,0V31A38.09,38.09,0,0,0,13,34.36a1.5,1.5,0,0,0-.76,1.3V57.92a1.5,1.5,0,0,0,.76,1.3c6.16,3.51,14.35,5.45,23.07,5.45s17-2,23.18-5.51a1.5,1.5,0,0,0,.75-1.3V35.73A1.5,1.5,0,0,0,59.23,34.43ZM57,57c-5.64,3-13,4.69-20.93,4.69S20.85,60,15.23,57V36.55c5.63-3,13-4.64,20.82-4.64S51.34,33.58,57,36.61V57Z" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_up_thick.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><polygon points="61.58 51.96 36.01 26.4 10.67 51.74 7.84 48.91 36.01 20.74 64.41 49.13 61.58 51.96" fill="gray"/></svg>

+ 0 - 23
client/images/icons/m_view-list_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_special</title><rect x="13.31" y="9.52" width="45.01" height="3" fill="gray"/><rect x="13.31" y="34.31" width="45.01" height="3" fill="gray"/><rect x="13.31" y="21.92" width="45.01" height="3" fill="gray"/><rect x="13.31" y="46.7" width="45.01" height="3" fill="gray"/><rect x="13.31" y="59.1" width="45.01" height="3" fill="gray"/><rect x="0.36" y="0.36" width="71.28" height="71.28" fill="none"/></svg>

+ 0 - 23
client/images/icons/m_view-tile_thin.svg

@@ -1,23 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2018 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><title>1-icons_expanded</title><path d="M31.35,32.85H8.76a1.5,1.5,0,0,1-1.5-1.5V8.91a1.5,1.5,0,0,1,1.5-1.5H31.35a1.5,1.5,0,0,1,1.5,1.5V31.35A1.5,1.5,0,0,1,31.35,32.85Zm-21.09-3H29.85V10.41H10.26V29.85Z" fill="gray"/><path d="M63,32.85H40.41a1.5,1.5,0,0,1-1.5-1.5V8.91a1.5,1.5,0,0,1,1.5-1.5H63a1.5,1.5,0,0,1,1.5,1.5V31.35A1.5,1.5,0,0,1,63,32.85Zm-21.09-3H61.5V10.41H41.91V29.85Z" fill="gray"/><path d="M31.35,64.5H8.76A1.5,1.5,0,0,1,7.26,63V40.56a1.5,1.5,0,0,1,1.5-1.5H31.35a1.5,1.5,0,0,1,1.5,1.5V63A1.5,1.5,0,0,1,31.35,64.5Zm-21.09-3H29.85V42.06H10.26V61.5Z" fill="gray"/><path d="M63,64.5H40.41a1.5,1.5,0,0,1-1.5-1.5V40.56a1.5,1.5,0,0,1,1.5-1.5H63a1.5,1.5,0,0,1,1.5,1.5V63A1.5,1.5,0,0,1,63,64.5Zm-21.09-3H61.5V42.06H41.91V61.5Z" fill="gray"/></svg>

+ 4 - 0
client/index.html

@@ -27,6 +27,8 @@
     <meta name="viewport" content="initial-scale=1, maximum-scale=1">
     <title>PWM Development</title>
 
+    <link rel="stylesheet" href="vendor/ias-icons.css">
+    <link rel="stylesheet" href="vendor/ux-ias.css">
     <style>
         html, body {
             margin: 0;
@@ -39,7 +41,9 @@
 <ui-view>Loading...</ui-view>
 
 <script src="vendor/angular.js"></script>
+<script src="vendor/angular-aria.js"></script>
 <script src="vendor/angular-translate.js"></script>
 <script src="vendor/angular-ui-router.js"></script>
+<script src="vendor/ng-ias.js"></script>
 </body>
 </html>

+ 24 - 239
client/package-lock.json

@@ -4,6 +4,24 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@microfocus/ias-icons": {
+      "version": "1.0.0-alpha",
+      "resolved": "https://registry.npmjs.org/@microfocus/ias-icons/-/ias-icons-1.0.0-alpha.tgz",
+      "integrity": "sha1-2gCMwmbf/xr1CacQN3eNGxEM0wM=",
+      "dev": true
+    },
+    "@microfocus/ng-ias": {
+      "version": "1.0.0-alpha1",
+      "resolved": "https://registry.npmjs.org/@microfocus/ng-ias/-/ng-ias-1.0.0-alpha1.tgz",
+      "integrity": "sha1-RTipUA+bYWLWYsIC2/7dGQYmobU=",
+      "dev": true
+    },
+    "@microfocus/ux-ias": {
+      "version": "1.0.0-alpha",
+      "resolved": "https://registry.npmjs.org/@microfocus/ux-ias/-/ux-ias-1.0.0-alpha.tgz",
+      "integrity": "sha1-8gWWdXoz4oyOYRQ/uy2eA8DIBaE=",
+      "dev": true
+    },
     "@types/angular": {
       "version": "1.6.42",
       "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.42.tgz",
@@ -221,6 +239,12 @@
       "integrity": "sha512-6igWH2GIsxV+J38wNWCh8oyjaZsrIPIDO35twloIUyjlF2Yit6UyLAWujHP05ma/LFxTsx4NtYibRoMNBXPR1A==",
       "dev": true
     },
+    "angular-aria": {
+      "version": "1.6.9",
+      "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.6.9.tgz",
+      "integrity": "sha512-I2SR17Ux0o0R9KD3DRzjR5NNX3pUSKvtWYLFCLg22qvR5+736olCQSyptNIvKsvjALwfXBw1Irdlq0jyohDn+Q==",
+      "dev": true
+    },
     "angular-mocks": {
       "version": "1.6.9",
       "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.6.9.tgz",
@@ -444,13 +468,6 @@
         }
       }
     },
-    "async": {
-      "version": "0.2.10",
-      "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
-      "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=",
-      "dev": true,
-      "optional": true
-    },
     "async-each": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
@@ -2354,12 +2371,6 @@
         "source-map": "0.5.7"
       }
     },
-    "cubic2quad": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/cubic2quad/-/cubic2quad-1.1.1.tgz",
-      "integrity": "sha1-abGcYaP1tB7PLx1fro+wNBWqixU=",
-      "dev": true
-    },
     "currently-unhandled": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -3466,17 +3477,6 @@
         "debug": "2.6.7"
       }
     },
-    "fontgen-loader": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/fontgen-loader/-/fontgen-loader-0.2.1.tgz",
-      "integrity": "sha1-uO1tmnmNWwVbgPHiHbSwQXC28FE=",
-      "dev": true,
-      "requires": {
-        "glob": "6.0.4",
-        "loader-utils": "0.2.17",
-        "webfonts-generator": "0.3.5"
-      }
-    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -4725,16 +4725,6 @@
       "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
       "dev": true
     },
-    "handlebars": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-2.0.0.tgz",
-      "integrity": "sha1-bp1/hRSjRn+l6fgswVjs/B1ax28=",
-      "dev": true,
-      "requires": {
-        "optimist": "0.3.7",
-        "uglify-js": "2.3.6"
-      }
-    },
     "har-schema": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
@@ -7066,12 +7056,6 @@
       "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
       "dev": true
     },
-    "microbuffer": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/microbuffer/-/microbuffer-1.0.0.tgz",
-      "integrity": "sha1-izgy7UDIfVH0e7I0kTppinVtGdI=",
-      "dev": true
-    },
     "micromatch": {
       "version": "2.3.11",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
@@ -7342,15 +7326,6 @@
         "xml-char-classes": "1.0.0"
       }
     },
-    "neatequal": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/neatequal/-/neatequal-1.0.0.tgz",
-      "integrity": "sha1-LuEhG8n6bkxVcV/SELsFYC6xrjs=",
-      "dev": true,
-      "requires": {
-        "varstream": "0.3.2"
-      }
-    },
     "negotiator": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@@ -7907,15 +7882,6 @@
         "is-wsl": "1.1.0"
       }
     },
-    "optimist": {
-      "version": "0.3.7",
-      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
-      "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=",
-      "dev": true,
-      "requires": {
-        "wordwrap": "0.0.3"
-      }
-    },
     "optionator": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
@@ -10897,18 +10863,6 @@
         "strip-ansi": "3.0.1"
       }
     },
-    "string.fromcodepoint": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
-      "integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM=",
-      "dev": true
-    },
-    "string.prototype.codepointat": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz",
-      "integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg=",
-      "dev": true
-    },
     "string_decoder": {
       "version": "0.10.31",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
@@ -11031,66 +10985,6 @@
         "has-flag": "1.0.0"
       }
     },
-    "svg-pathdata": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-1.0.4.tgz",
-      "integrity": "sha1-emgTQqrH7/2NUq+6eZmRDJ2juVk=",
-      "dev": true,
-      "requires": {
-        "readable-stream": "2.0.6"
-      },
-      "dependencies": {
-        "isarray": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
-          "dev": true
-        },
-        "readable-stream": {
-          "version": "2.0.6",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
-          "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
-          "dev": true,
-          "requires": {
-            "core-util-is": "1.0.2",
-            "inherits": "2.0.3",
-            "isarray": "1.0.0",
-            "process-nextick-args": "1.0.7",
-            "string_decoder": "0.10.31",
-            "util-deprecate": "1.0.2"
-          }
-        }
-      }
-    },
-    "svg2ttf": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/svg2ttf/-/svg2ttf-4.1.0.tgz",
-      "integrity": "sha1-ggIuVovQPBq7Zo/djRXwCJVqDhA=",
-      "dev": true,
-      "requires": {
-        "argparse": "1.0.9",
-        "cubic2quad": "1.1.1",
-        "lodash": "4.17.4",
-        "microbuffer": "1.0.0",
-        "svgpath": "2.2.1",
-        "xmldom": "0.1.27"
-      }
-    },
-    "svgicons2svgfont": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-5.0.2.tgz",
-      "integrity": "sha1-BRGCPGSRvhp9VDKS4pqK5ietBAY=",
-      "dev": true,
-      "requires": {
-        "commander": "2.11.0",
-        "neatequal": "1.0.0",
-        "readable-stream": "2.3.3",
-        "sax": "1.2.4",
-        "string.fromcodepoint": "0.2.1",
-        "string.prototype.codepointat": "0.2.0",
-        "svg-pathdata": "1.0.4"
-      }
-    },
     "svgo": {
       "version": "0.7.2",
       "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
@@ -11106,12 +11000,6 @@
         "whet.extend": "0.9.9"
       }
     },
-    "svgpath": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.2.1.tgz",
-      "integrity": "sha1-CDS7Z8iadkcrK9BswQH6e1F7Iiw=",
-      "dev": true
-    },
     "syntax-error": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.3.0.tgz",
@@ -11791,27 +11679,6 @@
         "tslib": "1.9.0"
       }
     },
-    "ttf2eot": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-2.0.0.tgz",
-      "integrity": "sha1-jmM3pYWr0WCKDISVirSDzmn2ZUs=",
-      "dev": true,
-      "requires": {
-        "argparse": "1.0.9",
-        "microbuffer": "1.0.0"
-      }
-    },
-    "ttf2woff": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/ttf2woff/-/ttf2woff-2.0.1.tgz",
-      "integrity": "sha1-hxgyJAAksJ25VwkEx8GSi4BXyWk=",
-      "dev": true,
-      "requires": {
-        "argparse": "1.0.9",
-        "microbuffer": "1.0.0",
-        "pako": "1.0.5"
-      }
-    },
     "tty-browserify": {
       "version": "0.0.0",
       "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
@@ -11865,30 +11732,6 @@
       "integrity": "sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw==",
       "dev": true
     },
-    "uglify-js": {
-      "version": "2.3.6",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz",
-      "integrity": "sha1-+gmEdwtCi3qbKoBY9GNV0U/vIRo=",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "async": "0.2.10",
-        "optimist": "0.3.7",
-        "source-map": "0.1.43"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.1.43",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
-          "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "amdefine": "1.0.1"
-          }
-        }
-      }
-    },
     "uglify-to-browserify": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
@@ -11926,12 +11769,6 @@
       "integrity": "sha1-iuVW4RAR9jwllnCKiDclnwGz1g4=",
       "dev": true
     },
-    "underscore": {
-      "version": "1.8.3",
-      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
-      "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
-      "dev": true
-    },
     "union-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
@@ -12091,12 +11928,6 @@
       "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
       "dev": true
     },
-    "url-join": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz",
-      "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=",
-      "dev": true
-    },
     "url-loader": {
       "version": "0.6.2",
       "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-0.6.2.tgz",
@@ -12307,29 +12138,6 @@
         "spdx-expression-parse": "1.0.4"
       }
     },
-    "varstream": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/varstream/-/varstream-0.3.2.tgz",
-      "integrity": "sha1-GKxklHZfP/GjWtmkvgU77BiKXeE=",
-      "dev": true,
-      "requires": {
-        "readable-stream": "1.1.14"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "1.1.14",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
-          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
-          "dev": true,
-          "requires": {
-            "core-util-is": "1.0.2",
-            "inherits": "2.0.3",
-            "isarray": "0.0.1",
-            "string_decoder": "0.10.31"
-          }
-        }
-      }
-    },
     "vary": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
@@ -12385,23 +12193,6 @@
         "minimalistic-assert": "1.0.0"
       }
     },
-    "webfonts-generator": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/webfonts-generator/-/webfonts-generator-0.3.5.tgz",
-      "integrity": "sha1-4t7/t4ZEhOn1qTpYYHp2dSxp2ig=",
-      "dev": true,
-      "requires": {
-        "handlebars": "2.0.0",
-        "mkdirp": "0.5.1",
-        "q": "1.5.0",
-        "svg2ttf": "4.1.0",
-        "svgicons2svgfont": "5.0.2",
-        "ttf2eot": "2.0.0",
-        "ttf2woff": "2.0.1",
-        "underscore": "1.8.3",
-        "url-join": "1.1.0"
-      }
-    },
     "webpack": {
       "version": "3.10.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz",
@@ -13644,12 +13435,6 @@
       "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=",
       "dev": true
     },
-    "xmldom": {
-      "version": "0.1.27",
-      "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
-      "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=",
-      "dev": true
-    },
     "xregexp": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",

+ 11 - 8
client/package.json

@@ -8,17 +8,20 @@
         "npm": ">=3.9"
     },
     "scripts": {
-        "build": "webpack --config=webpack.build.js",
+        "build": "webpack --config=webpack.build.js --env.NODE_ENV=production",
         "clean": "rimraf dist/",
-        "test": "karma start",
-        "test-single-run": "karma start --singleRun --no-auto-watch",
-        "start": "webpack-dev-server --config=webpack.dev.js --colors",
-        "sync": "webpack --config=webpack.build.js --output-path=../server/target/pwm-1.8.0-SNAPSHOT/public/resources/webjars/pwm-client --watch --colors"
+        "test": "karma start --env.NODE_ENV=test",
+        "test-single-run": "karma start --env.NODE_ENV=test --singleRun --no-auto-watch",
+        "start": "webpack-dev-server --config=webpack.dev.js --env.NODE_ENV=dev --colors",
+        "sync": "webpack --config=webpack.build.js --env.NODE_ENV=production --output-path=../server/target/pwm-1.8.0-SNAPSHOT/public/resources/webjars/pwm-client --watch --colors"
     },
     "author": "",
     "license": "ISC",
     "dependencies": {},
     "devDependencies": {
+        "@microfocus/ias-icons": "1.0.0-alpha",
+        "@microfocus/ng-ias": "1.0.0-alpha1",
+        "@microfocus/ux-ias": "1.0.0-alpha",
         "@types/angular": "1.6.42",
         "@types/angular-mocks": "1.5.11",
         "@types/angular-translate": "2.15.1",
@@ -27,18 +30,18 @@
         "@types/node": "9.4.2",
         "@uirouter/angularjs": "1.0.14",
         "angular": "1.6.9",
+        "angular-aria": "1.6.9",
         "angular-mocks": "1.6.9",
         "angular-translate": "2.17.0",
         "autoprefixer": "7.2.5",
         "copy-webpack-plugin": "4.4.1",
         "css-loader": "0.28.9",
         "file-loader": "1.1.6",
-        "fontgen-loader": "0.2.1",
         "html-loader": "0.5.5",
         "html-webpack-plugin": "2.30.1",
         "ignore-loader": "0.1.2",
         "jasmine": "3.0.0",
-        "jasmine-core": "^2.99.1",
+        "jasmine-core": "2.99.1",
         "jshint": "2.9.5",
         "jshint-loader": "0.8.4",
         "json-loader": "0.5.7",
@@ -62,7 +65,7 @@
         "style-loader": "0.20.1",
         "ts-loader": "3.5.0",
         "ts-mockito": "2.2.9",
-        "tslint": "^5.9.1",
+        "tslint": "5.9.1",
         "tslint-loader": "3.5.3",
         "typescript": "2.7.1",
         "url-loader": "0.6.2",

+ 30 - 28
client/src/changepassword/autogen-change-password.component.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,34 +20,36 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="autogen-change-password-dialog">
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="'Title_RandomPasswords' | translate"></div>
-    </div>
-    <div class="ias-dialog-body">
-        <p ng-bind="'Display_PasswordGeneration' | translate"></p>
-        <table>
-            <tbody>
-            <tr ng-repeat="i in [0,2,4,6,8,10,12,14,16,18]">
-                <td ng-repeat="j in [i, i+1]">
-                    <div ng-bind="$ctrl.passwordSuggestions[j]" ng-click="$ctrl.onChoosePasswordSuggestion(j)">
-                    </div>
-                </td>
-            </tr>
-            </tbody>
-        </table>
-    </div>
-    <div class="ias-actions">
-        <mf-button ng-click="$ctrl.populatePasswordSuggestions()"
-                   ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}
-        </mf-button>
-        <mf-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</mf-button>
-    </div>
+<div class="ias-dialog autogen-change-password-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="'Title_RandomPasswords' | translate"></div>
+        </div>
+        <div class="ias-dialog-content">
+            <p ng-bind="'Display_PasswordGeneration' | translate"></p>
+            <table>
+                <tbody>
+                <tr ng-repeat="i in [0,2,4,6,8,10,12,14,16,18]">
+                    <td ng-repeat="j in [i, i+1]">
+                        <div ng-bind="$ctrl.passwordSuggestions[j]" ng-click="$ctrl.onChoosePasswordSuggestion(j)">
+                        </div>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <div class="ias-actions">
+            <ias-button ng-click="$ctrl.populatePasswordSuggestions()"
+                        ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}
+            </ias-button>
+            <ias-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</ias-button>
+        </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="cancel()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 1 - 1
client/src/changepassword/autogen-change-password.component.scss

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 3 - 4
client/src/changepassword/autogen-change-password.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@
 
 import {IHelpDeskService, IRandomPasswordResponse, ISuccessResponse} from '../services/helpdesk.service';
 import {IPromise, IQService} from 'angular';
-import DialogService from '../ux/ias-dialog.service';
 import {IChangePasswordSuccess} from './success-change-password.controller';
 
 const RANDOM_MAPPING_SIZE = 20;
@@ -37,9 +36,9 @@ export default class AutogenChangePasswordController {
     static $inject = [ '$q', 'HelpDeskService', 'IasDialogService', 'personUserKey' ];
     constructor(private $q: IQService,
                 private HelpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private personUserKey: string) {
-        this.passwordSuggestions = Array(20).fill('');
+        this.passwordSuggestions = Array<string>(20).fill('');
         this.populatePasswordSuggestions();
     }
 

+ 20 - 16
client/src/changepassword/random-change-password.component.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,22 +20,26 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="random-change-password-dialog">
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ': ' + $ctrl.personUsername"></div>
-    </div>
-    <div class="ias-dialog-body">
-        <p ng-bind="'Display_SetRandomPasswordPrompt' | translate"></p>
-    </div>
-    <div class="ias-actions">
-        <mf-button ng-click="$ctrl.confirmSetRandomPassword()">{{ 'Button_OK' | translate }}</mf-button>
-        <mf-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</mf-button>
-    </div>
+<div class="ias-dialog random-change-password-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ': ' + $ctrl.personUsername"></div>
+        </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+        <div class="ias-dialog-content">
+            <p ng-bind="'Display_SetRandomPasswordPrompt' | translate"></p>
+        </div>
+
+        <div class="ias-actions">
+            <ias-button ng-click="$ctrl.confirmSetRandomPassword()">{{ 'Button_OK' | translate }}</ias-button>
+            <ias-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</ias-button>
+        </div>
+
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="cancel()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 2 - 3
client/src/changepassword/random-change-password.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,7 +22,6 @@
 
 
 import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
-import DialogService from '../ux/ias-dialog.service';
 import {IChangePasswordSuccess} from './success-change-password.controller';
 
 export default class RandomChangePasswordController {
@@ -35,7 +34,7 @@ export default class RandomChangePasswordController {
         'translateFilter'
     ];
     constructor(private HelpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private personUsername: string,
                 private personUserKey: string,
                 private translateFilter: (id: string) => string) {

+ 27 - 23
client/src/changepassword/success-change-password.component.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,29 +20,33 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="success-change-password-dialog">
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
-    </div>
-    <div class="ias-dialog-body">
-        <p ng-bind="$ctrl.successMessage"></p>
-        <span ng-bind="'Field_NewPassword' | translate"></span>
-        <mf-button ng-click="$ctrl.togglePasswordMasked()" ng-if="$ctrl.maskPasswords">
-            {{ 'Button_Show' | translate }}
-        </mf-button>
-        <input ng-model="$ctrl.password" ng-hide="$ctrl.passwordMasked" readonly type="text">
-    </div>
-    <div class="ias-actions">
-        <mf-button ng-click="cancel()">{{ 'Button_OK' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.clearAnswers()"
-                   ng-if="$ctrl.clearResponsesSetting==='ask'">{{ 'Button_ClearResponses' | translate }}
-        </mf-button>
-    </div>
+<div class="ias-dialog success-change-password-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
+        </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+        <div class="ias-dialog-content">
+            <p ng-bind="$ctrl.successMessage"></p>
+            <span ng-bind="'Field_NewPassword' | translate"></span>
+            <ias-button ng-click="$ctrl.togglePasswordMasked()" ng-if="$ctrl.maskPasswords">
+                {{ 'Button_Show' | translate }}
+            </ias-button>
+            <input ng-model="$ctrl.password" ng-hide="$ctrl.passwordMasked" readonly type="text">
+        </div>
+
+        <div class="ias-actions">
+            <ias-button ng-click="cancel()">{{ 'Button_OK' | translate }}</ias-button>
+            <ias-button ng-click="$ctrl.clearAnswers()"
+                       ng-if="$ctrl.clearResponsesSetting==='ask'">{{ 'Button_ClearResponses' | translate }}
+            </ias-button>
+        </div>
+
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="cancel()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 2 - 3
client/src/changepassword/success-change-password.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@
 import {IHelpDeskService } from '../services/helpdesk.service';
 import {IQService} from 'angular';
 import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
-import DialogService from '../ux/ias-dialog.service';
 
 export interface IChangePasswordSuccess {
     password: string;
@@ -52,7 +51,7 @@ export default class SuccessChangePasswordController {
                 changePasswordSuccessData: IChangePasswordSuccess,
                 private configService: IHelpDeskConfigService,
                 private HelpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private personUsername: string,
                 private personUserKey: string,
                 private translateFilter: (id: string) => string) {

+ 62 - 57
client/src/changepassword/type-change-password.component.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,64 +20,69 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="type-change-password-dialog">
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
-    </div>
-    <div class="ias-dialog-body">
-        <p ng-bind="$ctrl.message"></p>
+<div class="ias-dialog type-change-password-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
+        </div>
+        <div class="ias-dialog-content">
+            <p ng-bind="$ctrl.message"></p>
 
-        <table>
-        <tbody>
-            <tr>
-                <td>
-                    <input ng-model="$ctrl.password1" ng-hide="$ctrl.passwordMasked" type="text">
-                    <input ng-model="$ctrl.password1" ng-show="$ctrl.passwordMasked" type="password">
-                    <mf-icon-button
-                        icon="password_thin"
-                        id="password-icon1"
-                        ng-attr-title="{{ 'Button_Show' | translate }}"
-                        ng-click="$ctrl.togglePassword1Masked()"
-                        ng-if="$ctrl.maskPasswords"
-                        ng-show="!!$ctrl.password1"></mf-icon-button>
-                </td>
-                <td>
-                    <div ng-bind="$ctrl.strength" ng-if="$ctrl.showStrengthMeter" ng-show="!!$ctrl.password1">
-                    </div>
-                </td>
-            </tr>
-            <tr>
-                <td>
-                    <input ng-model="$ctrl.password2" ng-hide="$ctrl.passwordMasked" type="text">
-                    <input ng-model="$ctrl.password2" ng-show="$ctrl.passwordMasked" type="password">
-                    <mf-icon-button
-                        icon="password_thin"
-                        id="password-icon2"
-                        ng-attr-title="{{ 'Button_Show' | translate }}"
-                        ng-click="$ctrl.togglePassword2Masked()"
-                        ng-if="$ctrl.maskPasswords"
-                        ng-show="!!$ctrl.password2"></mf-icon-button>
-                </td>
-                <td>
-                    <span ng-show="$ctrl.matchStatus==='MATCH'">check</span>
-                    <span ng-show="$ctrl.matchStatus==='NO_MATCH'">X</span>
-                </td>
-            </tr>
-        </tbody>
-        </table>
-    </div>
-    <div class="ias-actions">
-        <mf-button ng-click="$ctrl.chooseTypedPassword()"
-                   ng-disabled="!$ctrl.passwordAcceptable">{{ 'Button_ChangePassword' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.onClickRandomPasswords()"
-                   ng-if="$ctrl.passwordUiMode === 'BOTH'">{{ 'Title_RandomPasswords' | translate }}
-        </mf-button>
-    </div>
+            <table>
+                <tbody>
+                <tr>
+                    <td>
+                        <input ng-model="$ctrl.password1" ng-hide="$ctrl.passwordMasked" type="text">
+                        <input ng-model="$ctrl.password1" ng-show="$ctrl.passwordMasked" type="password">
+                        <ias-button class="ias-icon-button"
+                                    id="password-icon1"
+                                    ng-attr-title="{{ 'Button_Show' | translate }}"
+                                    ng-click="$ctrl.togglePassword1Masked()"
+                                    ng-if="$ctrl.maskPasswords"
+                                    ng-show="!!$ctrl.password1">
+                            <ias-icon icon="password_thin"></ias-icon>
+                        </ias-button>
+                    </td>
+                    <td>
+                        <div ng-bind="$ctrl.strength" ng-if="$ctrl.showStrengthMeter" ng-show="!!$ctrl.password1">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <input ng-model="$ctrl.password2" ng-hide="$ctrl.passwordMasked" type="text">
+                        <input ng-model="$ctrl.password2" ng-show="$ctrl.passwordMasked" type="password">
+                        <ias-button class="ias-icon-button"
+                                    id="password-icon2"
+                                    ng-attr-title="{{ 'Button_Show' | translate }}"
+                                    ng-click="$ctrl.togglePassword2Masked()"
+                                    ng-if="$ctrl.maskPasswords"
+                                    ng-show="!!$ctrl.password2">
+                            <ias-icon icon="password_thin"></ias-icon>
+                        </ias-button>
+                    </td>
+                    <td>
+                        <span ng-show="$ctrl.matchStatus==='MATCH'">check</span>
+                        <span ng-show="$ctrl.matchStatus==='NO_MATCH'">X</span>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </div>
+        <div class="ias-actions">
+            <ias-button ng-click="$ctrl.chooseTypedPassword()"
+                        ng-disabled="!$ctrl.passwordAcceptable">{{ 'Button_ChangePassword' | translate }}
+            </ias-button>
+            <ias-button ng-click="$ctrl.onClickRandomPasswords()"
+                        ng-if="$ctrl.passwordUiMode === 'BOTH'">{{ 'Title_RandomPasswords' | translate }}
+            </ias-button>
+        </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="cancel()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 3 - 11
client/src/changepassword/type-change-password.component.scss

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,9 +22,9 @@
 
 ias-dialog {
   &.type-change-password-dialog {
-    .ias-dialog-body {
+    .ias-dialog-content {
       table {
-        input, mf-icon-button {
+        input {
           vertical-align: middle
         }
 
@@ -32,14 +32,6 @@ ias-dialog {
           margin: 7px;
         }
 
-        mf-icon-button {
-          display: inline-block;
-
-          mf-icon {
-            font-size: 20px;
-          }
-        }
-
         td {
           min-width: 250px;
         }

+ 3 - 4
client/src/changepassword/type-change-password.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@
 import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
 import {IQService, IScope, IWindowService} from 'angular';
 import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
-import DialogService from '../ux/ias-dialog.service';
 import {IChangePasswordSuccess} from './success-change-password.controller';
 import {IPasswordService, IValidatePasswordData} from '../services/password.service';
 
@@ -62,7 +61,7 @@ export default class TypeChangePasswordController {
                 private $window: IWindowService,
                 private configService: IHelpDeskConfigService,
                 private HelpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private passwordService: IPasswordService,
                 private personUsername: string,
                 private personUserKey: string,
@@ -70,7 +69,7 @@ export default class TypeChangePasswordController {
         this.password1 = '';
         this.password2 = '';
         this.passwordAcceptable = true;
-        this.passwordSuggestions = Array(20).fill('');
+        this.passwordSuggestions = Array<string>(20).fill('');
         this.matchStatus = EMPTY_MATCH_STATUS;
         this.message = translateFilter('Display_PasswordPrompt');
         this.showStrengthMeter = HelpDeskService.showStrengthMeter;

+ 1 - 1
client/src/helpdesk/date.filters.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 38 - 33
client/src/helpdesk/helpdesk-detail-dialog.template.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,43 +20,48 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog>
-    <div ng-switch="status">
-        <div class="ias-actions" ng-switch-default>
-            <div class="WaitDialogBlank"></div>
-        </div>
+<div class="ias-dialog" ng-switch="status">
+    <div class="ias-dialog-container" ng-switch-default>
+        <div class="WaitDialogBlank"></div>
+    </div>
 
-        <div ng-switch-when="confirm">
-            <div class="ias-dialog-header">
-                <div class="ias-title" ng-bind="title"></div>
-            </div>
-            <div class="ias-dialog-body">
-                <p ng-bind="text"></p>
-                <p ng-bind="secondaryText" ng-if="!!secondaryText"></p>
-            </div>
-            <div class="ias-actions">
-                <mf-button ng-click="confirm()">{{ 'Button_OK' | translate }}</mf-button>
-                <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
-            </div>
+    <div class="ias-dialog-container" ng-switch-when="confirm">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="title"></div>
         </div>
-
-        <div ng-switch-when="success">
-            <div class="ias-dialog-header">
-                <div class="ias-title" ng-bind="title"></div>
-            </div>
-            <div class="ias-dialog-body">
-                <p ng-bind="text"></p>
-            </div>
-            <div class="ias-actions">
-                <mf-button ng-click="close()">{{ 'Button_OK' | translate }}</mf-button>
-            </div>
+        <div class="ias-dialog-content">
+            <p ng-bind="text"></p>
+            <p ng-bind="secondaryText" ng-if="!!secondaryText"></p>
+        </div>
+        <div class="ias-actions">
+            <ias-button ng-click="confirm()">{{ 'Button_OK' | translate }}</ias-button>
+            <ias-button ng-click="close()">{{ 'Button_Cancel' | translate }}</ias-button>
         </div>
+
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
+                    id="close-icon"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                    ng-click="close()">
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
     </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+    <div class="ias-dialog-container" ng-switch-when="success">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="title"></div>
+        </div>
+        <div class="ias-dialog-content">
+            <p ng-bind="text"></p>
+        </div>
+        <div class="ias-actions">
+            <ias-button ng-click="close()">{{ 'Button_OK' | translate }}</ias-button>
+        </div>
+
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="close()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 41 - 59
client/src/helpdesk/helpdesk-detail.component.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,35 +20,17 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<mf-app-bar>
-    <div id="page-content-title" class="page-content-title" translate="Title_HelpDesk">Help Desk</div>
-    <mf-icon-button
-        icon="password_thin"
-        ng-attr-title="{{ 'Button_ChangePassword' | translate }}"
-        ng-click=""
-        id="password-icon"></mf-icon-button>
-    <mf-icon-button
-        icon="unlock_thin"
-        ng-attr-title="{{ 'Button_Unlock' | translate }}"
-        ng-click=""
-        id="unlock-icon"></mf-icon-button>
-    <mf-icon-button
-        icon="reload_refresh_thin"
-        ng-attr-title="{{ 'Display_CaptchaRefresh' | translate }}"
-        ng-click=""
-        id="reload-refresh-icon"></mf-icon-button>
-</mf-app-bar>
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_HelpDesk">Help Desk</h2>
+</div>
 
-
-
-<person-card person="$ctrl.personCard" show-image="$ctrl.photosEnabled">
-</person-card>
+<person-card person="$ctrl.personCard" show-image="$ctrl.photosEnabled"></person-card>
 
 <div class="help-desk-content">
-    <div class="person-details-content">
-        <mf-tabset>
-            <mf-tab id="Field_Profile" label="Profile">
-                <table>
+    <div>
+        <ias-tabset>
+            <ias-tab id="Field_Profile" label="Profile">
+                <table class="details-table">
                     <tbody>
                     <tr ng-repeat="item in $ctrl.person.profileData">
                         <td ng-bind="item.label"></td>
@@ -58,9 +40,9 @@
                     </tr>
                     </tbody>
                 </table>
-            </mf-tab>
-            <mf-tab id="Title_Status" label="Status">
-                <table>
+            </ias-tab>
+            <ias-tab id="Title_Status" label="Status">
+                <table class="details-table">
                     <tbody>
                     <tr ng-repeat="item in $ctrl.person.statusData">
                         <td ng-bind="item.label"></td>
@@ -70,9 +52,9 @@
                     </tr>
                     </tbody>
                 </table>
-            </mf-tab>
-            <mf-tab ng-if="!!$ctrl.person.userHistory" id="Title_UserEventHistory" label="Password History">
-                <table>
+            </ias-tab>
+            <ias-tab ng-if="!!$ctrl.person.userHistory" id="Title_UserEventHistory" label="Password History">
+                <table class="details-table">
                     <tbody>
                     <tr ng-repeat="item in $ctrl.person.userHistory">
                         <td ng-bind="item.timestamp | dateFilter"></td>
@@ -80,9 +62,9 @@
                     </tr>
                     </tbody>
                 </table>
-            </mf-tab>
-            <mf-tab id="Title_PasswordPolicy" label="Password Policy">
-                <table>
+            </ias-tab>
+            <ias-tab id="Title_PasswordPolicy" label="Password Policy">
+                <table class="details-table">
                     <tbody>
                     <tr>
                         <td ng-bind="'Field_Policy' | translate"></td>
@@ -106,9 +88,9 @@
                     </tr>
                     </tbody>
                 </table>
-            </mf-tab>
-            <mf-tab id="Title_SecurityResponses" label="Security Responses">
-                <table>
+            </ias-tab>
+            <ias-tab id="Title_SecurityResponses" label="Security Responses">
+                <table class="details-table">
                     <tbody>
                     <tr ng-repeat="item in $ctrl.person.helpdeskResponses">
                         <td ng-bind="item.label"></td>
@@ -118,37 +100,37 @@
                     </tr>
                     </tbody>
                 </table>
-            </mf-tab>
-        </mf-tabset>
+            </ias-tab>
+        </ias-tabset>
     </div>
 
     <div class="help-desk-buttons">
-        <mf-button ng-click="$ctrl.gotoSearch()"
+        <ias-button ng-click="$ctrl.gotoSearch()"
                    ng-disabled="$ctrl.buttonDisabled('back')"
-                   ng-if="$ctrl.buttonVisible('back')">{{ 'Button_GoBack' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.refresh()"
+                   ng-if="$ctrl.buttonVisible('back')">{{ 'Button_GoBack' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.refresh()"
                    ng-disabled="$ctrl.buttonDisabled('refresh')"
-                   ng-if="$ctrl.buttonVisible('refresh')">{{ 'Display_CaptchaRefresh' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.changePassword()"
+                   ng-if="$ctrl.buttonVisible('refresh')">{{ 'Display_CaptchaRefresh' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.changePassword()"
                    ng-disabled="$ctrl.buttonDisabled('changePassword')"
-                   ng-if="$ctrl.buttonVisible('changePassword')">{{ 'Button_ChangePassword' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.unlockUser()"
+                   ng-if="$ctrl.buttonVisible('changePassword')">{{ 'Button_ChangePassword' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.unlockUser()"
                    ng-disabled="$ctrl.buttonDisabled('unlock')"
-                   ng-if="$ctrl.buttonVisible('unlock')">{{ 'Button_Unlock' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.clearResponses()"
+                   ng-if="$ctrl.buttonVisible('unlock')">{{ 'Button_Unlock' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.clearResponses()"
                    ng-disabled="$ctrl.buttonDisabled('clearResponses')"
-                   ng-if="$ctrl.buttonVisible('clearResponses')">{{ 'Button_ClearResponses' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.clearOtpSecret()"
+                   ng-if="$ctrl.buttonVisible('clearResponses')">{{ 'Button_ClearResponses' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.clearOtpSecret()"
                    ng-disabled="$ctrl.buttonDisabled('clearOtpSecret')"
-                   ng-if="$ctrl.buttonVisible('clearOtpSecret')">{{ 'Button_HelpdeskClearOtpSecret' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.verifyUser()"
+                   ng-if="$ctrl.buttonVisible('clearOtpSecret')">{{ 'Button_HelpdeskClearOtpSecret' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.verifyUser()"
                    ng-disabled="$ctrl.buttonDisabled('verification')"
-                   ng-if="$ctrl.buttonVisible('verification')">{{ 'Button_Verify' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.deleteUser()"
+                   ng-if="$ctrl.buttonVisible('verification')">{{ 'Button_Verify' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.deleteUser()"
                    ng-disabled="$ctrl.buttonDisabled('deleteUser')"
-                   ng-if="$ctrl.buttonVisible('deleteUser')">{{ 'Button_Delete' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.clickCustomButton(button)"
+                   ng-if="$ctrl.buttonVisible('deleteUser')">{{ 'Button_Delete' | translate }}</ias-button>
+        <ias-button ng-click="$ctrl.clickCustomButton(button)"
                    ng-repeat="button in $ctrl.person.customButtons"
-                   ng-attr-title="{{button.description}}">{{ button.label }}</mf-button>
+                   ng-attr-title="{{button.description}}">{{ button.label }}</ias-button>
     </div>
 </div>

+ 46 - 88
client/src/helpdesk/helpdesk-detail.component.scss

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,103 +25,61 @@ help-desk-detail {
     margin-right: 20px;
   }
 
-  mf-icon-button {
-    margin-left: 5px;
-  }
-
-  person-card {
-    margin-top: 20px;
+  .ias-tile {
+    margin: 20px 0 15px;
   }
 
   > .help-desk-content {
-    > .person-details-content,
-    > .help-desk-buttons {
-      display: inline-block;
-      vertical-align: top;
-    }
-
-    > .person-details-content {
-      min-width: 580px;
-      max-width: 700px;
-      padding: 10px;
-
-      .mf-tab-pane-container {
-        max-height: 500px;
-        overflow: auto;
-
-        table {
-          border: none;
-          border-collapse: collapse;
-          max-width: 560px;
-          max-height: 500px;
-          width: 100%;
-
-          tr {
-            height: 25px;
-
-            &.bottom-border {
-              border-bottom: 1px solid #949494;
-            }
-
-            td {
-              border: none;
-              font-size: 12px;
-              height: 19px;
-              text-align: left;
-
-              &:first-child {
-                color: #949494;
-                width: 200px;
-                text-align: right;
-                padding: 3px 0;
-              }
-
-              &:last-child {
-                padding: 3px 10px;
+    display: flex;
+    flex-flow: row wrap;
 
-                > .detail-container {
-                  > ul {
-                    > li {
-                      > mf-icon-button {
-                        display: inline-block;
-                        height: 16px;
-                        width: 16px;
-
-                        > button {
-                          > mf-icon {
-                            font-size: 16px;
-                          }
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-
-              ul {
-                list-style: none;
-                margin: 0;
-                padding: 0;
+    > .help-desk-buttons {
+      margin-left: 15px;
 
-                > li {
-                  margin: 0;
-                  padding: 0;
-                }
-              }
-            }
-          }
-        }
+      > .ias-button {
+        display: block;
+        margin-bottom: 5px;
+        width: 100%;
       }
     }
+  }
+}
+
+.details-table {
+  border: none;
+  border-collapse: collapse;
+  //width: 100%;
+
+  tr {
+    height: 25px;
+
+    td {
+      border: none;
+      font-size: 12px;
+      height: 19px;
+      text-align: left;
+
+      &:first-child {
+        color: #949494;
+        //width: 100px;
+        text-align: right;
+        padding: 3px 0;
+      }
 
-    > .help-desk-buttons {
-      > mf-button {
-        display: block;
+      &:last-child {
+        padding: 3px 15px;
+      }
+
+      ul {
+        list-style: none;
+        margin: 0;
+        padding: 0;
 
-        > button {
-          margin: 0 auto;
+        > li {
+          margin: 0;
+          padding: 0;
         }
       }
     }
   }
-}
+}

+ 34 - 20
client/src/helpdesk/helpdesk-detail.component.ts

@@ -3,7 +3,7 @@
   htt://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,9 +24,8 @@
 import {Component} from '../component';
 import {IButtonInfo, IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
 import {IScope, ui} from 'angular';
-import {IQService, noop} from 'angular';
+import {noop} from 'angular';
 import {IHelpDeskConfigService, PASSWORD_UI_MODES} from '../services/helpdesk-config.service';
-import DialogService from '../ux/ias-dialog.service';
 import {IPeopleService} from '../services/people.service';
 import {IPerson} from '../models/person.model';
 import {IChangePasswordSuccess} from '../changepassword/success-change-password.controller';
@@ -42,6 +41,9 @@ const STATUS_WAIT = 'wait';
 const STATUS_CONFIRM = 'confirm';
 const STATUS_SUCCESS = 'success';
 
+declare const PWM_HELPDESK: any;
+declare const PWM_VAR: any;
+
 @Component({
     stylesheetUrl: require('helpdesk/helpdesk-detail.component.scss'),
     templateUrl: require('helpdesk/helpdesk-detail.component.html')
@@ -52,7 +54,6 @@ export default class HelpDeskDetailComponent {
     photosEnabled: boolean;
 
     static $inject = [
-        '$q',
         '$state',
         '$stateParams',
         'ConfigService',
@@ -60,12 +61,11 @@ export default class HelpDeskDetailComponent {
         'IasDialogService',
         'PeopleService'
     ];
-    constructor(private $q: IQService,
-                private $state: ui.IStateService,
+    constructor(private $state: ui.IStateService,
                 private $stateParams: ui.IStateParamsService,
                 private configService: IHelpDeskConfigService,
                 private helpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private peopleService: IPeopleService) {
     }
 
@@ -92,17 +92,33 @@ export default class HelpDeskDetailComponent {
     changePassword(): void {
         this.configService.getPasswordUiMode()
             .then((passwordUiMode) => {
-                if (passwordUiMode === PASSWORD_UI_MODES.AUTOGEN) {
-                    this.changePasswordAutogen();
-                }
-                else if (passwordUiMode === PASSWORD_UI_MODES.RANDOM) {
-                    this.changePasswordRandom();
-                }
-                else if (passwordUiMode === PASSWORD_UI_MODES.BOTH || passwordUiMode === PASSWORD_UI_MODES.TYPE) {
-                    this.changePasswordType();
+                if (passwordUiMode) {
+                    if (document.title === 'PWM Development') {
+                        const pwUiMode: string = passwordUiMode.toUpperCase();
+                        if (pwUiMode === PASSWORD_UI_MODES.TYPE) {
+                            this.changePasswordType();
+                        }
+                        else if (pwUiMode === PASSWORD_UI_MODES.AUTOGEN) {
+                            this.changePasswordAutogen();
+                        }
+                        else if (pwUiMode === PASSWORD_UI_MODES.BOTH) {
+                            // TODO: Need to take into account both autogen and typing in this scenario
+                            this.changePasswordType();
+                        }
+                        else if (pwUiMode === PASSWORD_UI_MODES.RANDOM) {
+                            this.changePasswordRandom();
+                        }
+                    }
+                    else {
+                        // Until the new AngularJS version of "Change Password" is finished, we'll just call into the
+                        // old PWM JavaScript functions:
+                        PWM_VAR['helpdesk_obfuscatedDN'] = this.getUserKey();
+                        PWM_VAR['helpdesk_setting_PwUiMode'] = passwordUiMode;
+                        PWM_HELPDESK.initiateChangePasswordDialog();
+                    }
                 }
                 else {
-                    throw new Error('Password type unsupported!');  // TODO: best way to do this?
+                    throw new Error('Unable to retrieve a valid password UI mode.');
                 }
             });
     }
@@ -312,7 +328,7 @@ export default class HelpDeskDetailComponent {
                     'translateFilter',
                     function ($scope: IScope | any,
                               helpDeskService: IHelpDeskService,
-                              IasDialogService: DialogService,
+                              IasDialogService: any,
                               translateFilter: (id: string) => string) {
                         $scope.status = STATUS_CONFIRM;
                         $scope.title = translateFilter('Button_Confirm');
@@ -336,14 +352,12 @@ export default class HelpDeskDetailComponent {
             });
     }
 
-
-
     getUserKey(): string {
         return this.$stateParams['personId'];
     }
 
     gotoSearch(): void {
-        this.$state.go('search');
+        this.$state.go('search.cards');
     }
 
     initialize(): void {

+ 222 - 0
client/src/helpdesk/helpdesk-search-base.component.ts

@@ -0,0 +1,222 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IPeopleService} from '../services/people.service';
+import SearchResult from '../models/search-result.model';
+import {isArray, isString, IPromise, IQService, IScope} from 'angular';
+import {IPerson} from '../models/person.model';
+import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import LocalStorageService from '../services/local-storage.service';
+import PromiseService from '../services/promise.service';
+
+let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
+let recentVerificationsDialogTemplateUrl = require('./recent-verifications-dialog.template.html');
+
+export default abstract class HelpDeskSearchBaseComponent {
+    columnConfiguration: any;
+    errorMessage: string;
+    protected pendingRequests: IPromise<any>[] = [];
+    photosEnabled: boolean;
+    query: string;
+    searchMessage: string;
+    searchResult: SearchResult;
+    searchTextLocalStorageKey: string;
+    searchViewLocalStorageKey: string;
+    verificationsEnabled: boolean;
+    view: string;
+
+    constructor(protected $q: IQService,
+                protected $scope: IScope,
+                protected $stateParams: angular.ui.IStateParamsService,
+                protected $translate: angular.translate.ITranslateService,
+                protected configService: IHelpDeskConfigService,
+                protected IasDialogService: any,
+                protected localStorageService: LocalStorageService,
+                protected peopleService: IPeopleService,
+                protected promiseService: PromiseService) {
+        this.searchTextLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_TEXT;
+        this.searchViewLocalStorageKey = this.localStorageService.keys.HELPDESK_SEARCH_VIEW;
+
+        $scope.$watch('$ctrl.query', (newValue: string, oldValue: string) => {
+            this.onSearchTextChange(newValue, oldValue);
+        });
+    }
+
+    protected initialize(): void {
+        this.query = this.getSearchText();
+
+        this.configService.verificationsEnabled().then((verificationsEnabled: boolean) => {
+            this.verificationsEnabled = verificationsEnabled;
+        });
+    }
+
+    getMessage(): string {
+        return this.errorMessage || this.searchMessage;
+    }
+
+    private getSearchText(): string {
+        let param: string = this.$stateParams['query'];
+        // If multiple query parameters are defined, use the first one
+        if (isArray(param)) {
+            param = param[0].trim();
+        }
+        else if (isString(param)) {
+            param = param.trim();
+        }
+
+        return param || this.localStorageService.getItem(this.searchTextLocalStorageKey);
+    }
+
+    abstract fetchData(): void;
+
+    protected clearSearch(): void {
+        this.query = null;
+        this.searchResult = null;
+        this.clearErrorMessage();
+        this.clearSearchMessage();
+        this.abortPendingRequests();
+    }
+
+    protected fetchSearchData(): IPromise<void | SearchResult> {
+        this.abortPendingRequests();
+        this.searchResult = null;
+
+        if (!this.query) {
+            this.clearSearch();
+            return null;
+        }
+
+        let promise = this.peopleService.search(this.query);
+        this.pendingRequests.push(promise);
+
+        return promise
+            .then(
+                function(searchResult: SearchResult) {
+                    this.clearErrorMessage();
+                    this.clearSearchMessage();
+
+                    // Aborted request
+                    if (!searchResult) {
+                        return;
+                    }
+
+                    // Too many results returned
+                    if (searchResult.sizeExceeded) {
+                        this.setSearchMessage('Display_SearchResultsExceeded');
+                    }
+
+                    // No results returned. Not an else if statement so that the more important message is presented
+                    if (!searchResult.people.length) {
+                        this.setSearchMessage('Display_SearchResultsNone');
+                    }
+
+                    return this.$q.resolve(searchResult);
+                }.bind(this),
+                function(error) {
+                    this.setErrorMessage(error);
+                    this.clearSearchMessage();
+                }.bind(this))
+            .finally(function() {
+                this.removePendingRequest(promise);
+            }.bind(this));
+    }
+
+    private onSearchTextChange(newValue: string, oldValue: string): void {
+        if (newValue === oldValue) {
+            return;
+        }
+
+        this.storeSearchText();
+        this.clearSearchMessage();
+        this.clearErrorMessage();
+        this.fetchData();
+    }
+
+    protected abortPendingRequests() {
+        for (let index = 0; index < this.pendingRequests.length; index++) {
+            let pendingRequest = this.pendingRequests[index];
+            this.promiseService.abort(pendingRequest);
+        }
+
+        this.pendingRequests = [];
+    }
+
+    protected setErrorMessage(message: string) {
+        this.errorMessage = message;
+    }
+
+    protected clearErrorMessage() {
+        this.errorMessage = null;
+    }
+
+    // If message is a string it will be translated. If it is a promise it will assign the string from the resolved
+    // promise
+    protected setSearchMessage(translationKey: string) {
+        if (!translationKey) {
+            this.clearSearchMessage();
+            return;
+        }
+
+        const self = this;
+        this.$translate(translationKey.toString())
+            .then((translation: string) => {
+                self.searchMessage = translation;
+            });
+    }
+
+    protected clearSearchMessage(): void  {
+        this.searchMessage = null;
+    }
+
+    protected removePendingRequest(promise: IPromise<any>) {
+        let index = this.pendingRequests.indexOf(promise);
+
+        if (index > -1) {
+            this.pendingRequests.splice(index, 1);
+        }
+    }
+
+    protected selectPerson(person: IPerson): void {
+        this.IasDialogService
+            .open({
+                controller: 'VerificationsDialogController as $ctrl',
+                templateUrl: verificationsDialogTemplateUrl,
+                locals: {
+                    personUserKey: person.userKey,
+                    search: true
+                }
+            });
+    }
+
+    protected showVerifications(): void {
+        this.IasDialogService
+            .open({
+                controller: 'RecentVerificationsDialogController as $ctrl',
+                templateUrl: recentVerificationsDialogTemplateUrl
+            });
+    }
+
+    protected storeSearchText(): void {
+        this.localStorageService.setItem(this.searchTextLocalStorageKey, this.query || '');
+    }
+}

+ 63 - 0
client/src/helpdesk/helpdesk-search-cards.component.html

@@ -0,0 +1,63 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2018 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_HelpDesk">Help Desk</h2>
+    <ias-search-box ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    placeholder="{{'Placeholder_Search' | translate}}" auto-focus>
+    </ias-search-box>
+
+    <span class="ias-fill"></span>
+
+    <ias-button id="view-tile-icon" class="ias-icon-button ias-selected"
+                ng-disabled="true"
+                ng-attr-title="{{ 'Title_HelpDeskCard' | translate }}">
+        <ias-icon icon="view_tile_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="view-list-icon" class="ias-icon-button"
+                ng-click="$ctrl.gotoTableView()"
+                ng-attr-title="{{ 'Title_HelpDeskTable' | translate }}">
+        <ias-icon icon="view_list_thin"></ias-icon>
+    </ias-button>
+</div>
+
+<div class="search-info-container">
+    <div>
+        <ias-button class="verifications-button ias-cta" ng-if="$ctrl.verificationsEnabled"
+                    ng-click="$ctrl.showVerifications()">{{ 'Button_Verifications' | translate }}</ias-button>
+    </div>
+
+    <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
+         ng-if="$ctrl.loading || $ctrl.searchMessage || $ctrl.errorMessage"
+         ng-bind="$ctrl.getMessage() || ('Display_PleaseWait' | translate)">
+    </div>
+</div>
+
+<div class="people-search-component-content">
+    <div class="ias-grid">
+        <person-card person="person"
+                     show-image="$ctrl.photosEnabled"
+                     ng-repeat="person in $ctrl.searchResult.people | orderBy:'displayNames[0]'"
+                     ng-click="$ctrl.selectPerson(person)">
+        </person-card>
+    </div>
+</div>

+ 126 - 0
client/src/helpdesk/helpdesk-search-cards.component.ts

@@ -0,0 +1,126 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {Component} from '../component';
+import {IPeopleService} from '../services/people.service';
+import {IQService, IScope} from 'angular';
+import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import LocalStorageService from '../services/local-storage.service';
+import HelpDeskSearchBaseComponent from './helpdesk-search-base.component';
+import SearchResult from '../models/search-result.model';
+import {IPerson} from '../models/person.model';
+import PromiseService from '../services/promise.service';
+
+@Component({
+    stylesheetUrl: require('helpdesk/helpdesk-search.component.scss'),
+    templateUrl: require('helpdesk/helpdesk-search-cards.component.html')
+})
+export default class HelpDeskSearchCardsComponent extends HelpDeskSearchBaseComponent {
+    static $inject = [
+        '$q',
+        '$scope',
+        '$state',
+        '$stateParams',
+        '$translate',
+        'ConfigService',
+        'IasDialogService',
+        'LocalStorageService',
+        'PeopleService',
+        'PromiseService'
+    ];
+    constructor($q: IQService,
+                $scope: IScope,
+                private $state: angular.ui.IStateService,
+                $stateParams: angular.ui.IStateParamsService,
+                $translate: angular.translate.ITranslateService,
+                configService: IHelpDeskConfigService,
+                IasDialogService: any,
+                localStorageService: LocalStorageService,
+                peopleService: IPeopleService,
+                promiseService: PromiseService) {
+        super($q, $scope, $stateParams, $translate, configService, IasDialogService, localStorageService,
+            peopleService, promiseService);
+    }
+
+    $onInit() {
+        this.initialize();
+        this.fetchData();
+
+        this.configService.photosEnabled().then((photosEnabled: boolean) => {
+            this.photosEnabled = photosEnabled;
+        });
+    }
+
+    fetchData() {
+        let searchResult = this.fetchSearchData();
+        if (searchResult) {
+            searchResult.then(this.onSearchResult.bind(this));
+        }
+    }
+
+    gotoTableView(): void {
+        this.$state.go('search.table', {query: this.query});
+    }
+
+    private onSearchResult(searchResult: SearchResult): void {
+        // Aborted request
+        if (!searchResult) {
+            return;
+        }
+
+        this.searchResult = new SearchResult({
+            sizeExceeded: searchResult.sizeExceeded,
+            searchResults: []
+        });
+
+        this.pendingRequests = searchResult.people.map(
+            (person: IPerson) => {
+                // Store this promise because it is abortable
+                let promise = this.peopleService.getPerson(person.userKey);
+
+                promise
+                    .then(function(person: IPerson) {
+                            // Aborted request
+                            if (!person) {
+                                return;
+                            }
+
+                            // searchResult may be overwritten by ESC->[LETTER] typed in after a search
+                            // has started but before all calls to peopleService.getPerson have resolved
+                            if (this.searchResult) {
+                                this.searchResult.people.push(person);
+                            }
+                        }.bind(this),
+                        (error) => {
+                            this.setErrorMessage(error);
+                        })
+                    .finally(() => {
+                        this.removePendingRequest(promise);
+                    });
+
+                return promise;
+            },
+            this
+        );
+    }
+}

+ 85 - 0
client/src/helpdesk/helpdesk-search-table.component.html

@@ -0,0 +1,85 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2018 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_HelpDesk">Help Desk</h2>
+    <ias-search-box ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    placeholder="{{'Placeholder_Search' | translate}}" auto-focus>
+    </ias-search-box>
+
+    <span class="ias-fill"></span>
+
+    <ias-button id="view-tile-icon" class="ias-icon-button"
+                ng-click="$ctrl.gotoCardsView()"
+                ng-attr-title="{{ 'Title_HelpDeskCard' | translate }}">
+        <ias-icon icon="view_tile_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="view-list-icon" class="ias-icon-button ias-selected"
+                ng-disabled="true"
+                ng-attr-title="{{ 'Title_HelpDeskTable' | translate }}">
+        <ias-icon icon="view_list_thin"></ias-icon>
+    </ias-button>
+    <div class="icon-divider vertical"></div>
+    <ias-button class="ias-icon-button table-configuration-menu-toggle" ias-toggle="menu1">
+        <ias-icon icon="configure_thick"></ias-icon>
+    </ias-button>
+    <ias-menu name="menu1" ias-align="end end">
+        <div class="ias-input-container">
+            <div class="checkbox-button" ng-repeat="(key, value) in $ctrl.columnConfiguration">
+                <input type="checkbox" ng-checked="value.visible" aria-label="Toggle column visibility" disabled/>
+                <ias-button ng-click="$ctrl.toggleColumnVisible($event, key)">{{value.label}}</ias-button>
+            </div>
+        </div>
+    </ias-menu>
+</div>
+
+<div class="search-info-container">
+    <div>
+        <ias-button class="verifications-button ias-cta" ng-if="$ctrl.verificationsEnabled"
+                    ng-click="$ctrl.showVerifications()">{{ 'Button_Verifications' | translate }}</ias-button>
+    </div>
+
+    <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
+         ng-if="$ctrl.loading || $ctrl.searchMessage || $ctrl.errorMessage"
+         ng-bind="$ctrl.getMessage() || ('Display_PleaseWait' | translate)">
+    </div>
+</div>
+
+<div class="people-search-component-content">
+    <table class="ias-table" ias-sort="$ctrl.sort" ng-show="$ctrl.searchResult.people.length">
+        <thead>
+        <tr>
+            <th ng-repeat="(key, value) in $ctrl.columnConfiguration" ng-if="value.visible"
+                ias-sort-on="key">{{value.label}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="person in $ctrl.searchResult.people | orderBy:$ctrl.sort"
+            ng-click="$ctrl.selectPerson(person)">
+            <td ng-repeat="(key, value) in $ctrl.columnConfiguration" ng-if="value.visible">
+                <span ng-bind="person[key]"></span>
+            </td>
+            <td></td>
+        </tr>
+        </tbody>
+    </table>
+</div>

+ 115 - 0
client/src/helpdesk/helpdesk-search-table.component.ts

@@ -0,0 +1,115 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IQService, IScope} from 'angular';
+import HelpDeskSearchBaseComponent from './helpdesk-search-base.component';
+import {Component} from '../component';
+import SearchResult from '../models/search-result.model';
+import {IPeopleService} from '../services/people.service';
+import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import LocalStorageService from '../services/local-storage.service';
+import PromiseService from '../services/promise.service';
+
+@Component({
+    stylesheetUrl: require('helpdesk/helpdesk-search.component.scss'),
+    templateUrl: require('helpdesk/helpdesk-search-table.component.html')
+})
+export default class HelpDeskSearchTableComponent extends HelpDeskSearchBaseComponent {
+    columnConfiguration: any;
+
+    static $inject = [
+        '$q',
+        '$scope',
+        '$state',
+        '$stateParams',
+        '$translate',
+        'ConfigService',
+        'IasDialogService',
+        'LocalStorageService',
+        'PeopleService',
+        'PromiseService'
+    ];
+    constructor($q: IQService,
+                $scope: IScope,
+                private $state: angular.ui.IStateService,
+                $stateParams: angular.ui.IStateParamsService,
+                $translate: angular.translate.ITranslateService,
+                configService: IHelpDeskConfigService,
+                IasDialogService: any,
+                localStorageService: LocalStorageService,
+                peopleService: IPeopleService,
+                 promiseService: PromiseService) {
+        super($q, $scope, $stateParams, $translate, configService, IasDialogService, localStorageService,
+            peopleService, promiseService);
+    }
+
+    $onInit() {
+        this.initialize();
+        this.fetchData();
+
+        // The table columns are dynamic and configured via a service
+        this.configService.getColumnConfig().then(
+            (columnConfiguration: any) => {
+                this.columnConfiguration = Object.keys(columnConfiguration).reduce(
+                    function(accumulator, columnId) {
+                        accumulator[columnId] = {
+                            label: columnConfiguration[columnId],
+                            visible: true
+                        };
+
+                        return accumulator;
+                    },
+                    {});
+            },
+            (error) => {
+                this.setErrorMessage(error);
+            });
+    }
+
+    fetchData() {
+        let searchResult = this.fetchSearchData();
+        if (searchResult) {
+            searchResult.then(this.onSearchResult.bind(this));
+        }
+    }
+
+    gotoCardsView(): void {
+        this.$state.go('search.cards', {query: this.query});
+    }
+
+    toggleColumnVisible(event, columnId): void {
+        const visibleColumns = Object.keys(this.columnConfiguration).filter((columnId) => {
+            return this.columnConfiguration[columnId].visible;
+        });
+
+        if (!(visibleColumns.length === 1 && this.columnConfiguration[columnId].visible)) {
+            this.columnConfiguration[columnId].visible = !this.columnConfiguration[columnId].visible;
+        }
+
+        event.stopImmediatePropagation();
+    }
+
+    private onSearchResult(searchResult: SearchResult): void {
+        this.searchResult = searchResult;
+    }
+}

+ 0 - 65
client/src/helpdesk/helpdesk-search.component.html

@@ -1,65 +0,0 @@
-<!--
-  ~ Password Management Servlets (PWM)
-  ~ http://www.pwm-project.org
-  ~
-  ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
-  ~
-  ~ This program is free software; you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation; either version 2 of the License, or
-  ~ (at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program; if not, write to the Free Software
-  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-  -->
-
-<mf-app-bar>
-    <div id="page-content-title" translate="Title_HelpDesk">Help Desk</div>
-    <mf-search-bar search-text="$ctrl.query"
-                   on-search-text-change="$ctrl.onSearchTextChange(value)"
-                   auto-focus></mf-search-bar>
-    <span flex></span>
-    <mf-icon-button
-        icon="view-tile_thin"
-        ng-attr-title="{{ 'Title_HelpDeskCard' | translate }}"
-        ng-class="{selected: $ctrl.view === 'cards'}"
-        ng-click="$ctrl.gotoCardsView()"
-        ng-disabled="$ctrl.view === 'cards'"
-        id="view-tile-icon"></mf-icon-button>
-    <mf-icon-button
-        icon="view-list_thin"
-        ng-attr-title="{{ 'Title_HelpDeskTable' | translate }}"
-        ng-class="{selected: $ctrl.view === 'table'}"
-        ng-click="$ctrl.gotoTableView()"
-        ng-disabled="$ctrl.view === 'table'"
-        id="view-list-icon"></mf-icon-button>
-</mf-app-bar>
-<mf-button ng-if="$ctrl.verificationsEnabled"
-           ng-click="$ctrl.showVerifications()">{{ 'Button_Verifications' | translate }}</mf-button>
-
-<div class="people-search-component-content">
-    <div class="person-card-list" ng-show="$ctrl.view === 'cards'">
-        <person-card person="person"
-                     show-image="$ctrl.photosEnabled"
-                     ng-repeat="person in $ctrl.searchResult.people | orderBy:'displayNames[0]'"
-                     ng-click="$ctrl.selectPerson(person)">
-        </person-card>
-    </div>
-
-    <mf-table data="person in $ctrl.searchResult.people"
-              ng-show="$ctrl.view === 'table' && $ctrl.searchResult.people.length"
-              search-highlight="$ctrl.query"
-              on-click-item="$ctrl.selectPerson(person)">
-        <mf-table-column ng-repeat="(key, value) in $ctrl.columnConfiguration"
-                         label="value"
-                         value="'person.' + key">
-        </mf-table-column>
-    </mf-table>
-</div>

+ 19 - 161
client/src/helpdesk/helpdesk-search.component.scss

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,180 +20,38 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-help-desk-search {
+
+.verifications-button {
+  margin-bottom: 15px;
+}
+
+help-desk-search-cards,
+help-desk-search-table {
   display: flex;
   flex-flow: column nowrap;
   height: 100%;
 
-  // At medium size, cards are centered and no longer take up 100% width
-  &.medium {
-    > .people-search-component-content {
-      > .person-card-list {
-        > person-card {
-          margin: 0 auto;
-          display: block;
-          width: 272px;
-        }
-      }
-    }
-  }
-
-  // At large size, cards fit next to each other
-  &.large {
-    > .people-search-component-content {
-      > .person-card-list {
-        text-align: left;
-        margin: 0;
-
-        > person-card {
-          display: inline-block;
-          margin-right: 5px;
-        }
-      }
-    }
-  }
-
   > .people-search-component-content {
     flex: 1 1;
     overflow: auto;
-    text-align: center;
-
-    > .person-card-list {
-      > person-card {
-        display: inline-block;
-        width: 100%;
-
-        &:not(:last-child) {
-          margin-bottom: 5px;
-        }
-      }
-    }
   }
 }
 
-ias-dialog {
-  p,
-  .paragraph {
-    max-width: 400px;
-  }
-
-  .aligned-inputs {
-    margin-top: 8px;
-
-    input,
-    label,
-    .first-col,
-    .second-col {
-      display: inline-block;
-    }
-
-    input,
-    .second-col {
-      min-width: 200px;
-    }
+.aligned-input {
+  margin-top: 15px;
 
-    input {
-      background-color: transparent;
-      border: 1px solid #dae1e1;
-      border-radius: 3px;
-      font-size: 15px;
-      padding: 8px;
-    }
-
-    label,
-    .first-col {
-      color: #808080;
-      font-size: 13px;
-      font-weight: normal;
-      line-height: 21px;
-      margin-right: 5px;
-      min-width: 150px;
-      text-align: right;
-      user-select: none;
-    }
-
-    mf-button {
-      margin: 0;
-
-      > button {
-        min-width: 90px;
-      }
-    }
-  }
-
-  .ias-actions {
-    mf-icon {
-      .icon_m_check_thick,
-      .icon_m_close_thick {
-        margin-left: 5px;
-      }
-
-      .icon_m_check_thick {
-        color: green;
-      }
-
-      .icon_m_close_thick {
-        color: red;
-      }
-    }
-
-    .loading-gif-25 {
-      background-image: url('../../images/icons/wait_25.gif');
-      display: inline-block;
-      height: 25px;
-      margin-left: 5px;
-      vertical-align: top;
-      width: 25px;
-    }
+  > * {
+    vertical-align: middle;
   }
 
-  table {
-    border: 1px solid #dae1e1;
-    border-collapse: collapse;
-    width: 100%;
-
-    > tbody {
-      > tr {
-        height: 25px;
-      }
-    }
-
-    th, td {
-      font-weight: normal;
-      overflow: hidden;
-      padding: 5px;
-      text-align: left;
-      vertical-align: top;
-    }
-
-    th {
-      background-color: #eeeeee;
-      color: #697c87;
-      user-select: none;
-    }
-
-    tr {
-      > th,
-      > td {
-        border-bottom: 1px solid #dae1e1;
-      }
-    }
+  .ias-button {
+    margin-right: 5px;
   }
 }
 
-[dir="rtl"] {
-  people-search-cards {
-    &.large {
-      > .people-search-component-content {
-        .person-card-list {
-          text-align: right;
-
-          > person-card {
-            margin-right: auto;
-            margin-left: 5px;
-          }
-        }
-      }
-    }
-  }
+.loading-gif-25 {
+  background-image: url('../../images/icons/wait_25.gif');
+  display: inline-block;
+  height: 25px;
+  width: 25px;
 }

+ 0 - 234
client/src/helpdesk/helpdesk-search.component.ts

@@ -1,234 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-import {Component} from '../component';
-import {IPeopleService} from '../services/people.service';
-import SearchResult from '../models/search-result.model';
-import {IPromise, IQService} from 'angular';
-import {IPerson} from '../models/person.model';
-import DialogService from '../ux/ias-dialog.service';
-import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
-
-let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
-let recentVerificationsDialogTemplateUrl = require('./recent-verifications-dialog.template.html');
-
-@Component({
-    stylesheetUrl: require('helpdesk/helpdesk-search.component.scss'),
-    templateUrl: require('helpdesk/helpdesk-search.component.html')
-})
-export default class HelpDeskSearchComponent {
-    columnConfiguration: any;
-    protected pendingRequests: IPromise<any>[] = [];
-    photosEnabled: boolean;
-    query: string;
-    searchResult: SearchResult;
-    verificationsEnabled: boolean;
-    view: string;
-
-    static $inject = ['$q',
-        'ConfigService',
-        'IasDialogService',
-        'PeopleService'
-    ];
-
-    constructor(private $q: IQService,
-                private configService: IHelpDeskConfigService,
-                private IasDialogService: DialogService,
-                private peopleService: IPeopleService) {
-    }
-
-    $onInit(): void {
-        this.view = 'cards';
-
-        this.configService.photosEnabled().then((photosEnabled: boolean) => {
-            this.photosEnabled = photosEnabled;
-        }); // TODO: only if in cards view (some other things are like that too)
-
-        this.configService.verificationsEnabled().then((verificationsEnabled: boolean) => {
-            this.verificationsEnabled = verificationsEnabled;
-        });
-
-        this.fetchData();
-    }
-
-    private fetchData() {
-        let searchResultPromise = this.fetchSearchData();
-        if (searchResultPromise) {
-
-            searchResultPromise.then(this.onSearchResult.bind(this));
-        }
-    }
-
-    private fetchSearchData(): IPromise<void | SearchResult> {
-        // this.abortPendingRequests();
-        this.searchResult = null;
-
-        if (!this.query) {
-            // this.clearSearch();
-            return null;
-        }
-
-        const self = this;
-
-        let promise = this.peopleService.search(this.query);
-
-        this.pendingRequests.push(promise);
-
-        return promise
-            .then(
-                (searchResult: SearchResult) => {
-                    // self.clearErrorMessage();
-                    // self.clearSearchMessage();
-
-                    // Aborted request
-                    if (!searchResult) {
-                        return;
-                    }
-
-                    // Too many results returned
-                    // if (searchResult.sizeExceeded) {
-                    //     self.setSearchMessage('Display_SearchResultsExceeded');
-                    // }
-
-                    // No results returned. Not an else if statement so that the more important message is presented
-                    // if (!searchResult.people.length) {
-                    //     self.setSearchMessage('Display_SearchResultsNone');
-                    // }
-
-                    return this.$q.resolve(searchResult);
-                },
-                (error) => {
-                    /*self.setErrorMessage(error);
-                    self.clearSearchMessage();*/
-                })
-            .finally(() => {
-                // self.removePendingRequest(promise);
-            });
-    }
-
-    gotoCardsView(): void {
-        if (this.view !== 'cards') {
-            this.view = 'cards';
-            this.fetchData();
-        }
-    }
-
-    gotoTableView(): void {
-        if (this.view !== 'table') {
-            this.view = 'table';
-            this.fetchData();
-        }
-
-        let self = this;
-
-        // The table columns are dynamic and configured via a service
-        this.configService.getColumnConfig().then(
-            (columnConfiguration: any) => {
-                self.columnConfiguration = columnConfiguration;
-            },
-            (error) => {
-                // self.setErrorMessage(error);
-            }); // TODO: remove self
-    }
-
-    private onSearchResult(searchResult: SearchResult): void {
-        if (this.view === 'table') {
-            this.searchResult = searchResult;
-            return;
-        }
-
-        // Aborted request
-        if (!searchResult) {
-            return;
-        }
-
-        this.searchResult = new SearchResult({
-            sizeExceeded: searchResult.sizeExceeded,
-            searchResults: []
-        });
-
-        let self = this;
-
-        this.pendingRequests = searchResult.people.map(
-            (person: IPerson) => {
-                // Store this promise because it is abortable
-                let promise = this.peopleService.getPerson(person.userKey);
-
-                promise
-                    .then((person: IPerson) => {
-                            // Aborted request
-                            if (!person) {
-                                return;
-                            }
-
-                            // searchResult may be overwritten by ESC->[LETTER] typed in after a search
-                            // has started but before all calls to peopleService.getPerson have resolved
-                            if (self.searchResult) {
-                                self.searchResult.people.push(person);
-                            }
-                        },
-                        (error) => {
-                            // self.setErrorMessage(error);
-                        })
-                    .finally(() => {
-                        // self.removePendingRequest(promise);
-                    });
-
-                return promise;
-            }
-        );  // TODO: this arg
-    }
-
-    onSearchTextChange(value: string): void {
-        if (value === this.query) {
-            return;
-        }
-
-        this.query = value;
-
-        // this.storeSearchText();
-        // this.clearSearchMessage();
-        // this.clearErrorMessage();
-        this.fetchData();
-    }
-
-    selectPerson(person: IPerson): void {
-        this.IasDialogService
-            .open({
-                controller: 'VerificationsDialogController as $ctrl',
-                templateUrl: verificationsDialogTemplateUrl,
-                locals: {
-                    personUserKey: person.userKey,
-                    search: true
-                }
-            });
-    }
-
-    showVerifications(): void {
-        this.IasDialogService
-            .open({
-                controller: 'RecentVerificationsDialogController as $ctrl',
-                templateUrl: recentVerificationsDialogTemplateUrl
-            });
-    }
-}

+ 8 - 5
client/src/helpdesk/helpdesk.module.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,10 +24,11 @@
 import {IComponentOptions, module} from 'angular';
 import { DateFilter } from './date.filters';
 import HelpDeskDetailComponent from './helpdesk-detail.component';
-import HelpDeskSearchComponent from './helpdesk-search.component';
+import HelpDeskSearchTableComponent from './helpdesk-search-table.component';
+import HelpDeskSearchCardsComponent from './helpdesk-search-cards.component';
 import LocalStorageService from '../services/local-storage.service';
 import ObjectService from '../services/object.service';
-import PersonCardComponent from '../peoplesearch/person-card.component';
+import PersonCardDirective from '../peoplesearch/person-card.component';
 import PromiseService from '../services/promise.service';
 import RecentVerificationsDialogController from './recent-verifications-dialog.controller';
 import uxModule from '../ux/ux.module';
@@ -42,12 +43,14 @@ require('../peoplesearch/peoplesearch.scss');
 const moduleName = 'help-desk';
 
 module(moduleName, [
+    'ngAria',
     uxModule
 ])
 
-    .component('helpDeskSearch', HelpDeskSearchComponent as IComponentOptions)
+    .component('helpDeskSearchCards', HelpDeskSearchCardsComponent as IComponentOptions)
+    .component('helpDeskSearchTable', HelpDeskSearchTableComponent as IComponentOptions)
     .component('helpDeskDetail', HelpDeskDetailComponent as IComponentOptions)
-    .component('personCard', PersonCardComponent as IComponentOptions)
+    .directive('personCard', PersonCardDirective)
     .controller('AutogenChangePasswordController', AutogenChangePasswordController)
     .controller('RandomChangePasswordController', RandomChangePasswordController)
     .controller('RecentVerificationsDialogController', RecentVerificationsDialogController)

+ 0 - 0
client/src/helpdesk/helpdesk.scss


+ 3 - 4
client/src/helpdesk/main.dev.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,13 +29,12 @@ import HelpDeskConfigService from '../services/helpdesk-config.service.dev';
 import HelpDeskService from '../services/helpdesk.service.dev';
 import PasswordService from '../services/password.service.dev';
 
-// fontgen-loader needs this :(
-require('../icons.json');
 
 module('app', [
     uiRouter,
     helpDeskModule,
-    'pascalprecht.translate'
+    'pascalprecht.translate',
+    'ng-ias'
 ])
     .config(['$translateProvider', ($translateProvider: angular.translate.ITranslateProvider) => {
         $translateProvider.translations('en', require('i18n/translations_en.json'));

+ 3 - 4
client/src/helpdesk/main.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,13 +31,12 @@ import HelpDeskConfigService from '../services/helpdesk-config.service';
 import HelpDeskService from '../services/helpdesk.service';
 import PasswordService from '../services/password.service';
 
-// fontgen-loader needs this :(
-require('../icons.json');
 
 module('app', [
     uiRouter,
     helpDeskModule,
-    'pascalprecht.translate'
+    'pascalprecht.translate',
+    'ng-ias'
 ])
     .config(routes)
     .config([

+ 1 - 1
client/src/helpdesk/recent-verifications-dialog.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 41 - 39
client/src/helpdesk/recent-verifications-dialog.template.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,47 +20,49 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog>
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="'Title_RecentVerifications' | translate"></div>
-    </div>
+<div class="ias-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-label">
+            <div class="ias-title" ng-bind="'Title_RecentVerifications' | translate"></div>
+        </div>
 
-    <div class="ias-dialog-body">
-        <p ng-bind="'Display_SearchResultsNone' | translate"
-           ng-if="!$ctrl.recentVerifications.length"></p>
-        <table ng-if="!!$ctrl.recentVerifications.length">
-            <thead>
-            <tr>
-                <th ng-bind="'Field_LdapProfile' | translate"></th>
-                <th ng-bind="'Field_Username' | translate"></th>
-                <th ng-bind="'Field_DateTime' | translate"></th>
-                <th ng-bind="'Field_Method' | translate"></th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr ng-repeat="record in $ctrl.recentVerifications">
-                <td ng-bind="record.profile"></td>
-                <td ng-bind="record.username"></td>
-                <td ng-bind="record.timestamp | dateFilter"></td>
-                <td ng-bind="record.method"></td>
-            </tr>
-            </tbody>
-        </table>
-        <div ng-repeat="method in $ctrl.availableVerificationMethods" class="aligned-inputs">
-            <div class="first-col"></div>
-            <mf-button ng-click="$ctrl.selectVerificationMethod(method.name)">
-                {{ method.label | translate }}
-            </mf-button>
+        <div class="ias-dialog-content">
+            <p ng-bind="'Display_SearchResultsNone' | translate"
+               ng-if="!$ctrl.recentVerifications.length"></p>
+            <table class="ias-table" ng-if="!!$ctrl.recentVerifications.length">
+                <thead>
+                <tr>
+                    <th ng-bind="'Field_LdapProfile' | translate"></th>
+                    <th ng-bind="'Field_Username' | translate"></th>
+                    <th ng-bind="'Field_DateTime' | translate"></th>
+                    <th ng-bind="'Field_Method' | translate"></th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr ng-repeat="record in $ctrl.recentVerifications">
+                    <td ng-bind="record.profile"></td>
+                    <td ng-bind="record.username"></td>
+                    <td ng-bind="record.timestamp | dateFilter"></td>
+                    <td ng-bind="record.method"></td>
+                </tr>
+                </tbody>
+            </table>
+            <div ng-repeat="method in $ctrl.availableVerificationMethods">
+                <ias-button ng-click="$ctrl.selectVerificationMethod(method.name)">
+                    {{ method.label | translate }}
+                </ias-button>
+            </div>
         </div>
-    </div>
 
-    <div class="ias-actions">
-        <mf-button ng-click="close()">{{ 'Button_OK' | translate }}</mf-button>
-    </div>
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
+        <div class="ias-actions">
+            <ias-button ng-click="close()">{{ 'Button_OK' | translate }}</ias-button>
+        </div>
+
+        <ias-button class="ias-icon-button ias-dialog-cancel-button"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="close()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-icon icon="close_thick"></ias-icon>
+        </ias-button>
+    </div>
+</div>

+ 9 - 3
client/src/helpdesk/routes.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,9 +30,15 @@ export default [
     ) => {
         $urlRouterProvider.otherwise(
             ($injector: angular.auto.IInjectorService, $location: angular.ILocationService) => {
-                $location.url('search');
+                $location.url('search/cards');
             });
 
-        $stateProvider.state('search', { url: '/search?query', component: 'helpDeskSearch' });
+        $stateProvider.state('search', {
+            url: '/search?query',
+            abstract: true,
+            template: '<div class="help-desk-search-component"><ui-view/></div>',
+        });
+        $stateProvider.state('search.cards', { url: '/cards', component: 'helpDeskSearchCards' });
+        $stateProvider.state('search.table', { url: '/table', component: 'helpDeskSearchTable' });
         $stateProvider.state('details', { url: '/details/{personId}', component: 'helpDeskDetail' });
     }];

+ 2 - 3
client/src/helpdesk/verifications-dialog.controller.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import {
     VERIFICATION_METHOD_NAMES
 } from '../services/helpdesk-config.service';
 import {IHelpDeskService, IVerificationTokenResponse} from '../services/helpdesk.service';
-import DialogService from '../ux/ias-dialog.service';
 import {IPerson} from '../models/person.model';
 import ObjectService from '../services/object.service';
 
@@ -62,7 +61,7 @@ export default class VerificationsDialogController {
                 private $timeout: ITimeoutService,
                 private configService: IHelpDeskConfigService,
                 private helpDeskService: IHelpDeskService,
-                private IasDialogService: DialogService,
+                private IasDialogService: any,
                 private objectService: ObjectService,
                 private personUserKey: string,
                 private search: boolean) {

+ 70 - 64
client/src/helpdesk/verifications-dialog.template.html

@@ -3,7 +3,7 @@
   ~ http://www.pwm-project.org
   ~
   ~ Copyright (c) 2006-2009 Novell, Inc.
-  ~ Copyright (c) 2009-2017 The PWM Project
+  ~ Copyright (c) 2009-2018 The PWM Project
   ~
   ~ This program is free software; you can redistribute it and/or modify
   ~ it under the terms of the GNU General Public License as published by
@@ -20,92 +20,98 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog>
-    <div ng-switch="$ctrl.status">
-        <div class="ias-actions" ng-switch-default>
-            <div class="WaitDialogBlank"></div>
-        </div>
-
-        <div ng-switch-when="select">
-            <div class="ias-dialog-header">
+<div class="ias-dialog" ng-switch="$ctrl.status">
+        <div class="ias-dialog-container" ng-switch-when="select">
+            <div class="ias-dialog-label">
                 <div class="ias-title" ng-bind="'Title_VerificationSend' | translate"></div>
             </div>
-            <div class="ias-dialog-body">
+            <div class="ias-dialog-content">
                 <p ng-bind="'Long_Title_VerificationSend' | translate"></p>
-                <div ng-repeat="method in $ctrl.availableVerificationMethods" class="aligned-inputs">
-                    <div class="first-col"></div>
-                    <mf-button ng-click="$ctrl.selectVerificationMethod(method.name)">
-                        {{ method.label | translate }}
-                    </mf-button>
-                </div>
+
+                <ias-button ng-click="$ctrl.selectVerificationMethod(method.name)"
+                            ng-repeat="method in $ctrl.availableVerificationMethods"
+                            style="margin-right: 3px;">
+                    {{ method.label | translate }}
+                </ias-button>
+
             </div>
             <div class="ias-actions">
-                <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
+                <ias-button ng-click="close()">{{ 'Button_Cancel' | translate }}</ias-button>
             </div>
+
+            <ias-button class="ias-icon-button ias-dialog-cancel-button"
+                        id="close-icon"
+                        ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                        ng-click="close()">
+                <ias-icon icon="close_thick"></ias-icon>
+            </ias-button>
         </div>
 
-        <div ng-switch-when="verify">
-            <div class="ias-dialog-header">
+        <div class="ias-dialog-container" ng-switch-when="verify">
+            <div class="ias-dialog-label">
                 <div class="ias-title" ng-bind="'Title_ValidateCode' | translate"></div>
             </div>
 
-            <div ng-switch="$ctrl.verificationMethod">
-                <div class="ias-dialog-body" ng-switch-when="ATTRIBUTES">
-                    <div ng-repeat="input in $ctrl.inputs" class="aligned-inputs">
-                        <label ng-attr-for="{{input.name}}" ng-bind="input.label"></label>
-                        <input ng-attr-id="{{input.name}}" type="text" ng-model="$ctrl.formData[input.name]">
+            <div class="ias-dialog-content" ng-switch="$ctrl.verificationMethod">
+                <form>
+                    <div ng-switch-when="ATTRIBUTES">
+                        <div class="ias-input-container" ng-repeat="input in $ctrl.inputs">
+                            <label ng-attr-for="{{input.name}}" ng-bind="input.label"></label>
+                            <input ng-attr-id="{{input.name}}" type="text" ng-model="$ctrl.formData[input.name]">
+                        </div>
                     </div>
-                </div>
-                <div class="ias-dialog-body" ng-switch-when="EMAIL|SMS" ng-switch-when-separator="|">
-                    <div class="aligned-inputs">
-                        <div class="first-col" ng-bind="'Display_TokenDestination' | translate"></div>
-                        <div class="second-col" ng-bind="$ctrl.tokenData.destination"></div>
+                    <div ng-switch-when="EMAIL|SMS" ng-switch-when-separator="|">
+                        <div class="ias-input-container">
+                            <label ng-bind="'Display_TokenDestination' | translate"></label>
+                            <input type="text" ng-value="$ctrl.tokenData.destination" readonly>
+                        </div>
+                        <div class="ias-input-container">
+                            <label>Token</label>
+                            <input id="token" type="text" ng-model="$ctrl.formData.code">
+                        </div>
+
                     </div>
-                    <div class="aligned-inputs">
-                        <div class="first-col">Token</div>
-                        <input class="second-col" id="code" type="text" ng-model="$ctrl.formData.code">
+                    <div ng-switch-when="OTP">
+                        <p ng-bind="'Display_HelpdeskOtpValidation' | translate"></p>
+                        <div class="ias-input-container">
+                            <label>Code</label>
+                            <input id="code" type="text" ng-model="$ctrl.formData.code">
+                        </div>
                     </div>
-                </div>
-                <div class="ias-dialog-body" ng-switch-when="OTP">
-                    <p ng-bind="'Display_HelpdeskOtpValidation' | translate"></p>
-                    <div class="aligned-inputs">
-                        <div class="first-col">Code</div>
-                        <input id="code" type="text" ng-model="$ctrl.formData.code">
+
+                    <div class="aligned-input">
+                        <ias-button ng-click="$ctrl.sendVerificationData()" ng-disabled="$ctrl.verificationStatus==='passed'">
+                            {{ 'Button_Verify' | translate }}
+                        </ias-button>
+                        <div class="loading-gif-25" ng-if="$ctrl.verificationStatus==='wait'"></div>
+                        <ias-icon icon="status_ok_thick" style="color:#37c26a;" class="ias-success" ng-if="$ctrl.verificationStatus==='passed'"></ias-icon>
+                        <ias-icon icon="status_error_thick" style="color:#e50000;" class="ias-error" ng-if="$ctrl.verificationStatus==='failed'"></ias-icon>
                     </div>
-                </div>
+                    <p ng-if="$ctrl.verificationStatus==='failed'">
+                        Viewing details only available after a user has been successfully verified
+                    </p>
+                </form>
             </div>
 
             <div class="ias-actions">
-                    <div class="aligned-inputs">
-                        <div class="first-col"></div>
-                        <mf-button ng-click="$ctrl.sendVerificationData()">{{ 'Button_Verify' | translate }}</mf-button>
-                        <div class="loading-gif-25" ng-if="$ctrl.verificationStatus==='wait'"></div>
-                        <mf-icon icon="check_thick" ng-if="$ctrl.verificationStatus==='passed'"></mf-icon>
-                        <mf-icon icon="close_thick" ng-if="$ctrl.verificationStatus==='failed'"></mf-icon>
-                    </div>
-                <br>
-                <div class="paragraph" ng-if="$ctrl.verificationStatus==='failed'">
-                    Viewing details only available after a user has been successfully verified
-                </div>
-                <mf-button ng-disabled="$ctrl.verificationStatus!=='passed'"
+                <ias-button ng-disabled="$ctrl.verificationStatus!=='passed'"
                            ng-click="$ctrl.viewDetails()"
                            ng-if="!$ctrl.isDetailsView">
                     View Details
-                </mf-button>
-                <mf-button ng-disabled="$ctrl.verificationStatus!=='passed'"
+                </ias-button>
+                <ias-button ng-disabled="$ctrl.verificationStatus!=='passed'"
                            ng-click="$ctrl.clickOkButton()"
                            ng-if="$ctrl.isDetailsView">
                     {{ 'Button_OK' | translate }}
-                </mf-button>
-                <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
+                </ias-button>
+                <ias-button ng-click="close()">{{ 'Button_Cancel' | translate }}</ias-button>
             </div>
-        </div>
-    </div>
 
-    <mf-icon-button class="ias-dialog-close-button"
-                    icon="close_thick"
-                    id="close-icon"
-                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
-                    ng-click="close()">
-    </mf-icon-button>
-</ias-dialog>
+            <ias-button class="ias-icon-button ias-dialog-cancel-button"
+                        id="close-icon"
+                        ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                        ng-click="close()">
+                <ias-icon icon="close_thick"></ias-icon>
+            </ias-button>
+        </div>
+</div>

+ 0 - 15
client/src/icons.json

@@ -1,15 +0,0 @@
-{
-  "baseClass": "icon",
-  "classPrefix": "icon_",
-  "files": [ "../images/icons/**/*.svg" ],
-  "fixedWidth": true,
-  "fontName": "icons",
-  "formatOptions": {
-    "ttf": {
-      "ts": 1451512800000
-    }
-  },
-  "html": true,
-  "normalize": true,
-  "types": [ "eot", "woff", "ttf", "svg" ]
-}

+ 2 - 3
client/src/main.dev.ts

@@ -30,13 +30,12 @@ import routes from './routes';
 import routeErrorHandler from './route-error-handler';
 import uiRouter from '@uirouter/angularjs';
 
-// fontgen-loader needs this :(
-require('./icons.json');
 
 module('app', [
     uiRouter,
     peopleSearchModule,
-    'pascalprecht.translate'
+    'pascalprecht.translate',
+    'ng-ias'
 ])
 
     .config(routes)

+ 2 - 4
client/src/main.ts

@@ -31,13 +31,11 @@ import routeErrorHandler from './route-error-handler';
 import TranslationsLoaderFactory from './services/translations-loader.factory';
 import uiRouter from '@uirouter/angularjs';
 
-// fontgen-loader needs this :(
-require('./icons.json');
-
 module('app', [
     uiRouter,
     peopleSearchModule,
-    'pascalprecht.translate'
+    'pascalprecht.translate',
+    'ng-ias'
 ])
 
     .config(routes)

+ 35 - 31
client/src/peoplesearch/orgchart-search.component.html

@@ -20,37 +20,41 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<mf-app-bar>
-    <div id="page-content-title" translate="Title_Organization">Organization</div>
-    <mf-auto-complete search-text="$ctrl.query"
-                      on-search-text-change="$ctrl.onSearchTextChange(value)"
-                      search="$ctrl.autoCompleteSearch(query)"
-                      input-debounce="$ctrl.inputDebounce"
-                      item-selected="$ctrl.onAutoCompleteItemSelected(person)"
-                      item="person">
-        <content-template>
-            <span ng-bind="person._displayName"></span>
-        </content-template>
-    </mf-auto-complete>
-    <span flex></span>
-    <mf-icon-button
-            icon="view-tile_thin"
-            ng-click="$ctrl.gotoSearchState('search.cards')"
-            ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}"
-            id="view-tile-icon"></mf-icon-button>
-    <mf-icon-button
-            icon="view-list_thin"
-            ng-click="$ctrl.gotoSearchState('search.table')"
-            ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}"
-            id="view-list-icon"></mf-icon-button>
-    <div class="mf-divider vertical"></div>
-    <mf-icon-button
-            icon="orgchart_thin"
-            class="selected"
-            disabled="true"
-            ng-attr-title="{{ 'Title_OrgChart' | translate }}"
-            id="orgcharg-icon"></mf-icon-button>
-</mf-app-bar>
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_Organization">Organization</h2>
+    <ias-search-box id="input" ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    placeholder="{{'Placeholder_Search' | translate}}" auto-focus>
+    </ias-search-box>
+    <!--<mf-auto-complete search-text="$ctrl.query"-->
+                      <!--on-search-text-change="$ctrl.onSearchTextChange(value)"-->
+                      <!--search="$ctrl.autoCompleteSearch(query)"-->
+                      <!--input-debounce="$ctrl.inputDebounce"-->
+                      <!--item-selected="$ctrl.onAutoCompleteItemSelected(person)"-->
+                      <!--item="person">-->
+        <!--<content-template>-->
+            <!--<span ng-bind="person._displayName"></span>-->
+        <!--</content-template>-->
+    <!--</mf-auto-complete>-->
+
+    <span class="ias-fill"></span>
+
+    <ias-button id="view-tile-icon" class="ias-icon-button"
+                ng-click="$ctrl.gotoSearchState('search.cards')"
+                ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}">
+        <ias-icon class="ias-selected" icon="view_tile_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="view-list-icon" class="ias-icon-button"
+                ng-click="$ctrl.gotoSearchState('search.table')"
+                ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}">
+        <ias-icon class="ias-selected" icon="view_list_thin"></ias-icon>
+    </ias-button>
+    <div class="icon-divider vertical"></div>
+    <ias-button id="orgchart-icon" class="ias-icon-button ias-selected" ng-disabled="true"
+                ng-attr-title="{{ 'Title_OrgChart' | translate }}">
+        <ias-icon class="ias-selected" icon="orgchart_thin"></ias-icon>
+    </ias-button>
+</div>
+
 
 <org-chart person="$ctrl.person"
            direct-reports="$ctrl.directReports"

+ 0 - 4
client/src/peoplesearch/orgchart-search.component.scss

@@ -30,8 +30,4 @@ org-chart-search {
     flex: 1 1;
     overflow-x: auto;
   }
-
-  mf-app-bar {
-    margin-bottom: 10px;
-  }
 }

+ 6 - 2
client/src/peoplesearch/orgchart-search.component.ts

@@ -63,6 +63,10 @@ export default class OrgChartSearchComponent {
                 private pwmService: IPwmService) {
         this.searchTextLocalStorageKey = this.localStorageService.keys.SEARCH_TEXT;
         this.inputDebounce = this.pwmService.ajaxTypingWait;
+
+        $scope.$watch('$ctrl.query', () => {
+            this.onSearchTextChange();
+        });
     }
 
     $onInit(): void {
@@ -131,8 +135,8 @@ export default class OrgChartSearchComponent {
         this.$state.go('orgchart.search', { personId: person.userKey, query: null });
     }
 
-    onSearchTextChange(value: string): void {
-        this.query = value;
+    onSearchTextChange(): void {
+        // this.query = value;
         this.storeSearchText();
     }
 

+ 1 - 1
client/src/peoplesearch/orgchart.component.html

@@ -60,7 +60,7 @@
     <h3 translate="Title_DirectReports">Direct Reports</h3>
     <div class="org-chart-connector"></div>
 
-    <div class="person-card-list">
+    <div class="ias-grid">
         <person-card person="directReport"
                      show-direct-report-count="true"
                      show-image="$ctrl.showImages"

+ 88 - 61
client/src/peoplesearch/orgchart.component.scss

@@ -27,6 +27,71 @@ $org-chart-text-color: #808080;
 
 $manager-connector-height: 16px;
 
+
+.assistant,
+.manager {
+  .ias-tile {
+    border: none;
+    background-color: transparent;
+    display: block;
+    height: 96px;
+    padding: 0;
+    vertical-align: top;
+    width: 120px;
+
+    > .ias-avatar {
+      border: 3px solid #808080;
+      border-radius: 100%;
+      display: block;
+      margin: 0 auto;
+
+      &:hover {
+        //border-color: $person-card-border-color;
+      }
+    }
+
+    > .ias-tile-content {
+      background-color: white;
+      display: block;
+      margin-top: 8px;
+      text-align: center;
+      width: 100%;
+
+      > .reports {
+        right: 20px;
+      }
+
+      :nth-child(n + 3) {
+        display: none;
+      }
+    }
+  }
+}
+
+.self {
+  &.ias-tile {
+    background-color: #ffffff;
+    border: 3px solid #808080;
+    border-radius: 3px;
+    height: auto;
+    min-height: 96px;
+    //width: 346px;
+    max-width: 100%;
+
+    > .ias-avatar {
+      flex: 0 0 65px;
+      height: 65px;
+      width: 65px;
+      margin-bottom: 5px;
+    }
+
+    > .ias-tile-content {
+      flex-flow: row nowrap;
+    }
+  }
+}
+
+
 // (XS) Default display
 org-chart {
   display: block;
@@ -36,24 +101,12 @@ org-chart {
     display: none;
   }
 
-  // (S) Too wide for full width person-card in direct reports
-  &.small {
-    > .org-chart-section {
-      > .person-card-list {
-        > person-card {
-          margin-right: 5px;
-          width: 272px;
-        }
-      }
-    }
-  }
-
   // (L) Wide enough to show main person offset to right and display managers horizontally
   &.large {
     > .org-chart-section {
       text-align: left;
 
-      > person-card {
+      > .ias-tile {
         &[size="large"] {
           margin: 0 0 0 128px;
         }
@@ -107,16 +160,15 @@ org-chart {
               width: 69px;
             }
 
-            > person-card {
-              > .person-card-content {
-                > .avatar {
-                  background-color: $org-chart-secondary-connector-color;
+            .ias-tile {
+              > .ias-avatar {
+                background-color: $org-chart-secondary-connector-color;
 
-                  &:not(:hover) {
-                    border-color: $org-chart-secondary-connector-color;
-                  }
+                &:not(:hover) {
+                  border-color: $org-chart-secondary-connector-color;
                 }
-
+              }
+              > .ias-tile-content {
                 > .details {
                   > :first-child {
                     color: $org-chart-connector-color;
@@ -133,23 +185,19 @@ org-chart {
       }
 
       &.self {
-        person-card {
-          display: inline-block;
-        }
+        display: inline-flex;
 
         > .assistant {
           display: inline-block;
           margin-left: 33px;
           position: relative;
 
-          > person-card {
-            > .person-card-content {
-              > .avatar {
-                background-color: $org-chart-secondary-connector-color;
+          .ias-tile {
+            > .ias-avatar {
+              background-color: $org-chart-secondary-connector-color;
 
-                &:not(:hover) {
-                  border-color: $org-chart-secondary-connector-color;
-                }
+              &:not(:hover) {
+                border-color: $org-chart-secondary-connector-color;
               }
             }
           }
@@ -184,10 +232,10 @@ org-chart {
       &.overflow {
         .manager {
           &:last-child {
-            > person-card {
-              > .person-card-content {
+            > .ias-tile {
+              > .ias-tile-content {
                 > .avatar {
-                  background-image: url('../../images/icons/m_circle-horz-menu_thin.svg');
+                  //background-image: url('../../images/icons/m_circle-horz-menu_thin.svg');
                 }
 
                 > .reports {
@@ -205,10 +253,10 @@ org-chart {
         text-align: center;
 
         &.empty-manager {
-          > person-card {
+          > .ias-tile {
             cursor: initial;
 
-            > .person-card-content {
+            > .ias-tile-content {
               > .avatar {
                 background: $org-chart-secondary-connector-color;
                 border-color: $org-chart-secondary-connector-color;
@@ -221,7 +269,7 @@ org-chart {
           }
         }
 
-        > person-card {
+        > .ias-tile {
           display: inline-block;
         }
       }
@@ -243,25 +291,16 @@ org-chart {
       text-align: left;
     }
 
-    > person-card {
+    > .ias-tile {
       &[size="large"] {
         margin: 0 auto;
       }
     }
 
-    > .person-card-list {
+    > .ias-grid {
       border-top: 3px solid $org-chart-connector-color;
       min-height: 90px;
       padding-top: 5px;
-
-      > person-card {
-        display: inline-block;
-        width: 100%;
-
-        &:not(:last-child) {
-          margin-bottom: 5px;
-        }
-      }
     }
 
     .org-chart-connector {
@@ -285,24 +324,12 @@ org-chart {
       }
     }
 
-    // (S) Too wide for full width person-card in direct reports
-    &.small {
-      > .org-chart-section {
-        > .person-card-list {
-          > person-card {
-            margin-left: 5px;
-            margin-right: auto;
-          }
-        }
-      }
-    }
-
     // (L) Wide enough to show main person offset to right and display managers horizontally
     &.large {
       > .org-chart-section {
         text-align: right;
 
-        > person-card {
+        > .ias-tile {
           &[size="large"] {
             margin: 0 128px 0 0;
           }

+ 6 - 12
client/src/peoplesearch/peoplesearch-base.component.ts

@@ -57,6 +57,10 @@ abstract class PeopleSearchBaseComponent {
         this.searchViewLocalStorageKey = this.localStorageService.keys.SEARCH_VIEW;
 
         this.inputDebounce = this.pwmService.ajaxTypingWait;
+
+        $scope.$watch('$ctrl.query', (newValue: string, oldValue: string) => {
+            this.onSearchTextChange(newValue, oldValue);
+        });
     }
 
     getMessage(): string {
@@ -71,21 +75,11 @@ abstract class PeopleSearchBaseComponent {
         this.$state.go(state, { query: this.query });
     }
 
-    onSearchBoxKeyDown(event: KeyboardEvent): void {
-        switch (event.keyCode) {
-            case 27: // ESC
-                this.clearSearch();
-                break;
-        }
-    }
-
-    onSearchTextChange(value: string): void {
-        if (value === this.query) {
+    private onSearchTextChange(newValue: string, oldValue: string): void {
+        if (newValue === oldValue) {
             return;
         }
 
-        this.query = value;
-
         this.storeSearchText();
         this.clearSearchMessage();
         this.clearErrorMessage();

+ 23 - 28
client/src/peoplesearch/peoplesearch-cards.component.html

@@ -20,33 +20,28 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<mf-app-bar>
-    <div id="page-content-title" translate="Title_PeopleSearch">People Search</div>
-    <mf-search-bar input-debounce="$ctrl.inputDebounce"
-                   search-text="$ctrl.query"
-                   on-search-text-change="$ctrl.onSearchTextChange(value)"
-                   on-key-down="$ctrl.onSearchBoxKeyDown($event)"
-                   auto-focus></mf-search-bar>
-    <span flex></span>
-    <mf-icon-button
-            icon="view-tile_thin"
-            class="selected"
-            disabled="true"
-            ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}"
-            id="view-tile-icon"></mf-icon-button>
-    <mf-icon-button
-            icon="view-list_thin"
-            ng-click="$ctrl.gotoTableView()"
-            ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}"
-            id="view-list-icon"></mf-icon-button>
-    <div class="mf-divider vertical" ng-if="$ctrl.orgChartEnabled"></div>
-    <mf-icon-button
-            icon="orgchart_thin"
-            ng-click="$ctrl.gotoOrgchart()"
-            ng-if="$ctrl.orgChartEnabled"
-            ng-attr-title="{{ 'Title_OrgChart' | translate }}"
-            id="orgchart-icon"></mf-icon-button>
-</mf-app-bar>
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_PeopleSearch">People Search</h2>
+    <ias-search-box id="input" ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    placeholder="{{'Placeholder_Search' | translate}}" auto-focus>
+    </ias-search-box>
+
+    <span class="ias-fill"></span>
+
+    <ias-button id="view-tile-icon" class="ias-icon-button ias-selected" ng-disabled="true"
+                ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}">
+        <ias-icon class="ias-selected" icon="view_tile_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="view-list-icon" class="ias-icon-button" ng-click="$ctrl.gotoTableView()"
+                ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}">
+        <ias-icon class="ias-selected" icon="view_list_thin"></ias-icon>
+    </ias-button>
+    <div class="icon-divider vertical" ng-if="$ctrl.orgChartEnabled"></div>
+    <ias-button id="orgchart-icon" class="ias-icon-button" ng-click="$ctrl.gotoOrgchart()" ng-if="$ctrl.orgChartEnabled"
+                ng-attr-title="{{ 'Title_OrgChart' | translate }}">
+        <ias-icon class="ias-selected" icon="orgchart_thin"></ias-icon>
+    </ias-button>
+</div>
 
 <div class="search-info-container">
     <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
@@ -56,7 +51,7 @@
 </div>
 
 <div class="people-search-component-content">
-    <div class="person-card-list">
+    <div class="ias-grid">
         <person-card person="person"
                      show-image="$ctrl.photosEnabled"
                      ng-repeat="person in $ctrl.searchResult.people | orderBy:'displayNames[0]'"

+ 52 - 36
client/src/peoplesearch/peoplesearch-table.component.html

@@ -20,33 +20,41 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<mf-app-bar>
-    <div id="page-content-title" translate="Title_PeopleSearch">People Search</div>
-    <mf-search-bar input-debounce="$ctrl.inputDebounce"
-                   search-text="$ctrl.query"
-                   on-search-text-change="$ctrl.onSearchTextChange(value)"
-                   on-key-down="$ctrl.onSearchBoxKeyDown($event)"
-                   auto-focus></mf-search-bar>
-    <span flex></span>
-    <mf-icon-button
-            icon="view-tile_thin"
-            id="view-title-button"
-            ng-click="$ctrl.gotoCardsView()"
-            ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}"></mf-icon-button>
-    <mf-icon-button
-            icon="view-list_thin"
-            id="view-list-button"
-            class="selected"
-            disabled="true"
-            ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}"></mf-icon-button>
-    <div class="mf-divider vertical" ng-if="$ctrl.orgChartEnabled"></div>
-    <mf-icon-button
-            icon="orgchart_thin"
-            id="view-orgchart-button"
-            ng-click="$ctrl.gotoOrgchart()"
-            ng-if="$ctrl.orgChartEnabled"
-            ng-attr-title="{{ 'Title_OrgChart' | translate }}"></mf-icon-button>
-</mf-app-bar>
+<div class="ias-header">
+    <h2 id="page-content-title" translate="Title_PeopleSearch">People Search</h2>
+    <ias-search-box id="input" ng-model="$ctrl.query" ng-model-options="{debounce: $ctrl.inputDebounce}"
+                    placeholder="{{'Placeholder_Search' | translate}}" auto-focus>
+    </ias-search-box>
+
+    <span class="ias-fill"></span>
+
+    <ias-button id="view-title-button" class="ias-icon-button"
+                ng-click="$ctrl.gotoCardsView()"
+                ng-attr-title="{{ 'Title_PeopleSearchCard' | translate }}">
+        <ias-icon class="ias-selected" icon="view_tile_thin"></ias-icon>
+    </ias-button>
+    <ias-button id="view-list-button" class="ias-icon-button ias-selected" ng-disabled="true"
+                ng-attr-title="{{ 'Title_PeopleSearchTable' | translate }}">
+        <ias-icon class="ias-selected" icon="view_list_thin"></ias-icon>
+    </ias-button>
+    <div class="icon-divider vertical"></div>
+    <ias-button id="view-orgchart-button" class="ias-icon-button" ng-click="$ctrl.gotoOrgchart()"
+                ng-if="$ctrl.orgChartEnabled"
+                ng-attr-title="{{ 'Title_OrgChart' | translate }}">
+        <ias-icon class="ias-selected" icon="orgchart_thin"></ias-icon>
+    </ias-button>
+    <ias-button class="ias-icon-button table-configuration-menu-toggle" ias-toggle="menu1">
+        <ias-icon icon="configure_thick"></ias-icon>
+    </ias-button>
+    <ias-menu name="menu1" ias-align="end end">
+        <div class="ias-input-container">
+            <div class="checkbox-button" ng-repeat="(key, value) in $ctrl.columnConfiguration">
+                <input type="checkbox" ng-checked="value.visible" aria-label="Toggle column visibility" disabled/>
+                <ias-button ng-click="$ctrl.toggleColumnVisible($event, key)">{{value.label}}</ias-button>
+            </div>
+        </div>
+    </ias-menu>
+</div>
 
 <div class="search-info-container">
     <div class="search-info" ng-class="{'loading': !$ctrl.getMessage()}"
@@ -56,15 +64,23 @@
 </div>
 
 <div class="people-search-component-content">
-    <mf-table data="person in $ctrl.searchResult.people"
-              ng-show="$ctrl.searchResult.people.length"
-              search-highlight="$ctrl.query"
-              on-click-item="$ctrl.selectPerson(person)">
-        <mf-table-column ng-repeat="(key, value) in $ctrl.columnConfiguration"
-                         label="value"
-                         value="'person.' + key">
-        </mf-table-column>
-    </mf-table>
+    <table class="ias-table" ias-sort="$ctrl.sort" ng-show="$ctrl.searchResult.people.length">
+        <thead>
+        <tr>
+            <th ng-repeat="(key, value) in $ctrl.columnConfiguration" ng-if="value.visible"
+                ias-sort-on="key">{{value.label}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-repeat="person in $ctrl.searchResult.people | orderBy:$ctrl.sort"
+            ng-click="$ctrl.selectPerson(person)">
+            <td ng-repeat="(key, value) in $ctrl.columnConfiguration" ng-if="value.visible">
+                <span ng-bind="person[key]"></span>
+            </td>
+            <td></td>
+        </tr>
+        </tbody>
+    </table>
 
     <ui-view></ui-view>
 </div>

+ 8 - 1
client/src/peoplesearch/peoplesearch-table.component.scss

@@ -29,6 +29,13 @@ people-search-table {
     > .people-search-component-content {
         flex: 1 1;
         overflow: auto;
+        position: relative;
         text-align: center;
+
+        .table-configuration-menu-toggle {
+            position: absolute;
+            top: 0;
+            right: 0;
+        }
     }
-}
+}

+ 22 - 1
client/src/peoplesearch/peoplesearch-table.component.ts

@@ -81,7 +81,16 @@ export default class PeopleSearchTableComponent extends PeopleSearchBaseComponen
         // The table columns are dynamic and configured via a service
         this.configService.getColumnConfig().then(
             (columnConfiguration: any) => {
-                self.columnConfiguration = columnConfiguration;
+                self.columnConfiguration = Object.keys(columnConfiguration).reduce(
+                    function(accumulator, columnId) {
+                        accumulator[columnId] = {
+                            label: columnConfiguration[columnId],
+                            visible: true
+                        };
+
+                        return accumulator;
+                    },
+                    {});
             },
             (error) => {
                 self.setErrorMessage(error);
@@ -99,6 +108,18 @@ export default class PeopleSearchTableComponent extends PeopleSearchBaseComponen
         }
     }
 
+    toggleColumnVisible(event, columnId): void {
+        const visibleColumns = Object.keys(this.columnConfiguration).filter((columnId) => {
+            return this.columnConfiguration[columnId].visible;
+        });
+
+        if (!(visibleColumns.length === 1 && this.columnConfiguration[columnId].visible)) {
+            this.columnConfiguration[columnId].visible = !this.columnConfiguration[columnId].visible;
+        }
+
+        event.stopImmediatePropagation();
+    }
+
     private onSearchResult(searchResult: SearchResult): void {
         this.searchResult = searchResult;
     }

+ 3 - 2
client/src/peoplesearch/peoplesearch.module.ts

@@ -28,7 +28,7 @@ import OrgChartComponent from './orgchart.component';
 import OrgChartSearchComponent from './orgchart-search.component';
 import PeopleSearchTableComponent from './peoplesearch-table.component';
 import PeopleSearchCardsComponent from './peoplesearch-cards.component';
-import PersonCardComponent from './person-card.component';
+import PersonCardDirective from './person-card.component';
 import PersonDetailsDialogComponent from './person-details-dialog.component';
 import LocalStorageService from '../services/local-storage.service';
 import PromiseService from '../services/promise.service';
@@ -39,6 +39,7 @@ require('./peoplesearch.scss');
 const moduleName = 'people-search';
 
 module(moduleName, [
+    'ngAria',
     'pascalprecht.translate',
     uxModule
 ])
@@ -46,7 +47,7 @@ module(moduleName, [
     .filter('highlight', HighlightFilter)
     .component('orgChart', OrgChartComponent as IComponentOptions)
     .component('orgChartSearch', OrgChartSearchComponent as IComponentOptions)
-    .component('personCard', PersonCardComponent as IComponentOptions)
+    .directive('personCard', PersonCardDirective)
     .component('peopleSearchTable', PeopleSearchTableComponent as IComponentOptions)
     .component('peopleSearchCards', PeopleSearchCardsComponent as IComponentOptions)
     .component('personDetailsDialogComponent', PersonDetailsDialogComponent as IComponentOptions)

+ 26 - 20
client/src/peoplesearch/peoplesearch.scss

@@ -36,21 +36,7 @@ body {
   }
 }
 
-a {
-  color: #007cd0;
-  cursor: pointer;
-  font-weight: normal;
-  text-decoration: none;
-
-  &:focus {
-    outline: 1px solid #01a9e7;
-  }
-
-  &:hover {
-    background-color: #f6f9f8;
-  }
-}
-
+.help-desk-search-component,
 .people-search-component {
   height: 100%;
 
@@ -59,10 +45,6 @@ a {
     overflow: auto;
   }
 
-  mf-app-bar {
-    margin-bottom: 10px;
-  }
-
   .search-info-container {
     text-align: left;
 
@@ -93,4 +75,28 @@ a {
       text-align: right;
     }
   }
-}
+}
+
+.ias-avatar {
+  background: transparent url('../../images/user.png') no-repeat center center;
+  background-size: contain;
+}
+
+.ias-header {
+  max-width: 100%;
+}
+
+.checkbox-button {
+  align-items: center;
+  display: flex;
+  flex-flow: row nowrap;
+}
+
+.icon-divider {
+  &.vertical {
+    background-color: rgba(#808080, .5);
+    height: 25px;
+    margin: 0 5px;
+    width: 1px;
+  }
+}

+ 15 - 14
client/src/peoplesearch/person-card.component.html

@@ -20,34 +20,35 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<div class="person-card-content" ng-attr-id="{{'displayName-' + $ctrl.person.displayNames[0] }}" ng-switch="$ctrl.size">
-    <div class="avatar" ng-if="$ctrl.isSmall() || $ctrl.showImage" ng-style="$ctrl.getAvatarStyle()" aria-label="User avatar"></div>
+<div class="ias-tile ias-medium" ng-attr-id="{{'displayName-' + $ctrl.person.displayNames[0] }}" ng-switch="$ctrl.size">
+    <div class="ias-avatar" ng-if="$ctrl.isSmall() || $ctrl.showImage"
+         ng-style="$ctrl.getAvatarStyle()"
+         aria-label="User avatar"></div>
+
     <div class="reports"
          ng-if="$ctrl.numDirectReportsVisible"
          ng-bind="$ctrl.person.numDirectReports"
          ng-attr-title="{{$ctrl.person.numDirectReports}} {{ 'Title_DirectReports' | translate }}"></div>
 
-    <div class="details" ng-switch-when="small">
-        <div ng-bind="$ctrl.person.displayNames[0]"></div>
+    <div class="ias-tile-content" ng-switch-when="small">
+        <h3 ng-bind="$ctrl.person.displayNames[0]"></h3>
         <div ng-bind="$ctrl.person.displayNames[1]"></div>
     </div>
 
-    <div ng-class="{details: true, 'direct-reports': $ctrl.numDirectReportsVisible}" ng-switch-when="large">
-        <div ng-bind="$ctrl.person.displayNames[0]"></div>
+    <div class="ias-tile-content" ng-class="{'direct-reports': $ctrl.numDirectReportsVisible}" ng-switch-when="large">
+        <h3 ng-bind="$ctrl.person.displayNames[0]"></h3>
         <div ng-bind="$ctrl.person.displayNames[1]"></div>
         <div ng-bind="$ctrl.person.displayNames[2]"></div>
         <div ng-bind="$ctrl.person.displayNames[3]"></div>
 
-        <div class="secondary-details">
-            <div ng-bind="$ctrl.person.displayNames[4]"></div>
-            <div ng-bind="$ctrl.person.displayNames[5]"></div>
-            <div ng-bind="$ctrl.person.displayNames[6]"></div>
-            <div ng-bind="$ctrl.person.displayNames[7]"></div>
-        </div>
+        <div ng-bind="$ctrl.person.displayNames[4]"></div>
+        <div ng-bind="$ctrl.person.displayNames[5]"></div>
+        <div ng-bind="$ctrl.person.displayNames[6]"></div>
+        <div ng-bind="$ctrl.person.displayNames[7]"></div>
     </div>
 
-    <div ng-class="{details: true, 'direct-reports': $ctrl.numDirectReportsVisible}" ng-switch-default>
-        <div ng-bind="$ctrl.person.displayNames[0]"></div>
+    <div class="ias-tile-content" ng-class="{'direct-reports': $ctrl.numDirectReportsVisible}" ng-switch-default>
+        <h3 ng-bind="$ctrl.person.displayNames[0]"></h3>
         <div ng-bind="$ctrl.person.displayNames[1]"></div>
         <div ng-bind="$ctrl.person.displayNames[2]"></div>
         <div ng-bind="$ctrl.person.displayNames[3]"></div>

+ 24 - 15
client/src/peoplesearch/person-card.component.ts

@@ -21,24 +21,14 @@
  */
 
 
-import { isString, IAugmentedJQuery } from 'angular';
-import { Component } from '../component';
+import {IAugmentedJQuery} from 'angular';
 import { IPerson } from '../models/person.model';
 import { IPeopleService } from '../services/people.service';
 
-@Component({
-    bindings: {
-        directReports: '<',
-        disableFocus: '<',
-        person: '<',
-        showImage: '<',
-        size: '@',
-        showDirectReportCount: '<'
-    },
-    stylesheetUrl: require('peoplesearch/person-card.component.scss'),
-    templateUrl: require('peoplesearch/person-card.component.html')
-})
-export default class PersonCardComponent {
+const templateUrl = require('peoplesearch/person-card.component.html');
+require('peoplesearch/person-card.component.scss');
+
+class PersonCardController {
     private details: any[]; // For large style cards
     private disableFocus: boolean;
     private person: IPerson;
@@ -124,3 +114,22 @@ export default class PersonCardComponent {
         }
     }
 }
+
+export default function PersonCardDirectiveFactory() {
+    return {
+        bindToController: true,
+        controller: PersonCardController,
+        controllerAs: '$ctrl',
+        restrict: 'E',
+        replace: true,
+        scope: {
+            directReports: '<',
+            disableFocus: '<',
+            person: '<',
+            showImage: '<',
+            size: '@',
+            showDirectReportCount: '<'
+        },
+        templateUrl: templateUrl
+    };
+}

+ 38 - 23
client/src/peoplesearch/person-details-dialog.component.html

@@ -20,23 +20,33 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<mf-dialog class="person-details" on-close="$ctrl.closeDialog()">
-    <div class="mf-dialog-content">
-        <div class="person-details-header">
-            <person-card size="medium"
-                         person="$ctrl.person"
-                         disable-focus="true"
-                         show-image="$ctrl.photosEnabled"
-                         show-direct-report-count="false"></person-card>
-            <mf-button type="button" ng-click="$ctrl.gotoOrgChart()" ng-if="$ctrl.orgChartEnabled">
-                <mf-icon icon="orgchart_thin" id="orgchart-button"></mf-icon>
-                <span translate="Title_OrgChart">Organizational Chart</span>
-            </mf-button>
-        </div>
+<div class="ias-dialog person-details-dialog">
+    <div class="ias-dialog-container">
+        <div class="ias-dialog-content">
+            <div class="person-details-dialog-header">
+                <div class="ias-avatar" ng-if="$ctrl.photosEnabled" ng-style="$ctrl.getAvatarStyle()" alt="User image"></div>
 
-        <div class="person-details-content">
-            <table>
-                <tbody>
+                <div class="ias-header">
+                    <h2 ng-bind="$ctrl.person.displayNames[0]"></h2>
+                    <span class="ias-fill"></span>
+                    <ias-button class="ias-icon-button" ng-click="$ctrl.closeDialog()">
+                        <ias-icon icon="close_thick"></ias-icon>
+                    </ias-button>
+                </div>
+                <div ng-bind="$ctrl.person.displayNames[1]"></div>
+                <div ng-bind="$ctrl.person.displayNames[2]"></div>
+                <div ng-bind="$ctrl.person.displayNames[3]"></div>
+                <div class="person-dialog-actions">
+                    <ias-button type="button" class="ias-icon-text-button"
+                                ng-click="$ctrl.gotoOrgChart()" ng-if="$ctrl.orgChartEnabled">
+                        <ias-icon icon="orgchart_thin" id="orgchart-button"></ias-icon>
+                        <span translate="Title_OrgChart">Organizational Chart</span>
+                    </ias-button>
+                </div>
+            </div>
+            <div class="person-details-content">
+                <table class="details-table">
+                    <tbody>
                     <tr>
                         <td></td>
                         <td>
@@ -69,17 +79,22 @@
                                            ng-if="detail.type === 'tel'"></a>
                                         <span ng-bind="value"
                                               ng-if="detail.type !== 'email' && detail.type !== 'tel'"></span>
-                                        <mf-icon-button icon="search_thick"
-                                                        ng-click="$ctrl.searchText(value)"
-                                                        ng-if="detail.searchable"
-                                                        ng-attr-title="{{('Placeholder_Search' | translate) + ' \'' + value + '\''}}"></mf-icon-button>
+
+                                        <a ui-sref="search.table({ query: value })"
+                                           class="details-table-search-link"
+                                           ng-if="detail.searchable"
+                                           ng-attr-title="{{('Placeholder_Search' | translate) + ' \'' + value + '\''}}">
+                                            <ias-icon icon="search_thick"></ias-icon>
+                                        </a>
+
                                     </li>
                                 </ul>
                             </div>
                         </td>
                     </tr>
-                </tbody>
-            </table>
+                    </tbody>
+                </table>
+            </div>
         </div>
     </div>
-</mf-dialog>
+</div>

+ 64 - 93
client/src/peoplesearch/person-details-dialog.component.scss

@@ -20,114 +20,85 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+.person-dialog-actions {
+  clear: both;
+  margin-top: 15px;
+  text-align: center;
+}
+
+.person-details-dialog {
+  text-align: left;
 
-.person-details {
-  mf-app-bar {
-    padding: 0 5px;
+  .ias-dialog-container {
+    padding: 0;
   }
 
-  .mf-dialog-content {
-    > .person-details-header {
-      background-color: #eef2f2;
+  .ias-avatar {
+    float: left;
+    height: 100px;
+    margin-bottom: 15px;
+    margin-right: 15px;
+    width: 100px;
+  }
+}
 
-      > mf-button {
-        margin: 0 0 10px 0;
-      }
+.person-details-dialog-header {
+  background-color: #eef2f2;
+  color: #434c50;
+  font-size: 12px;
+  line-height: 15px;
+  padding: 15px;
+}
 
-      > person-card {
-        height: 120px;
-        max-width: 100%;
-        width: 100%;
+.person-details-content {
+  padding: 15px;
+}
 
-        > .person-card-content {
-          > .avatar {
-            flex-basis: 100px;
-            height: 100px;
-            width: 100px;
-          }
-        }
+.details-table {
+  border: none;
+  border-collapse: collapse;
+  width: 100%;
+
+  tr {
+    height: 25px;
+
+    td {
+      border: none;
+      font-size: 12px;
+      height: 19px;
+      text-align: left;
+
+      &:first-child {
+        color: #949494;
+        width: 100px;
+        text-align: right;
+        padding: 3px 0;
       }
-    }
-
-    > .person-details-content {
-      padding: 10px;
-
-      > table {
-        border: none;
-        border-collapse: collapse;
-        width: 100%;
 
-        tr {
-          height: 25px;
-
-          td {
-            border: none;
-            font-size: 12px;
-            height: 19px;
-            text-align: left;
-
-            &:first-child {
-              color: #949494;
-              width: 100px;
-              text-align: right;
-              padding: 3px 0;
-            }
-
-            &:last-child {
-              padding: 3px 10px;
-
-              > .detail-container {
-                > ul {
-                  > li {
-                    > mf-icon-button {
-                      display: inline-block;
-                      height: 16px;
-                      width: 16px;
-
-                      > button {
-                        > mf-icon {
-                          font-size: 16px;
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-            }
+      &:last-child {
+        padding: 3px 15px;
+      }
 
-            ul {
-              list-style: none;
-              margin: 0;
-              padding: 0;
+      ul {
+        list-style: none;
+        margin: 0;
+        padding: 0;
 
-              > li {
-                margin: 0;
-                padding: 0;
-              }
-            }
-          }
+        > li {
+          margin: 0;
+          padding: 0;
         }
       }
     }
   }
-}
 
-[dir="rtl"] {
-  .person-details {
-    .mf-dialog-content {
-      > .person-details-content {
-        > table {
-          tr {
-            td {
-              text-align: right;
+  .details-table-search-link {
+    display: inline-block;
+    font-size: 25px;
+    vertical-align: middle;
 
-              &:first-child {
-                text-align: left;
-              }
-            }
-          }
-        }
-      }
+    .ias-icon {
+      display:block;
     }
   }
-}
+}

+ 8 - 0
client/src/peoplesearch/person-details-dialog.component.ts

@@ -79,6 +79,14 @@ export default class PersonDetailsDialogComponent {
         this.$state.go('^', { query: this.$stateParams['query'] });
     }
 
+    getAvatarStyle(): any {
+        if (!this.person || !this.person.photoURL || !this.photosEnabled) {
+            return null;
+        }
+
+        return  { 'background-image': 'url(' + this.person.photoURL + ')' };
+    }
+
     gotoOrgChart(): void {
         this.$state.go('orgchart.search', { personId: this.person.userKey });
     }

+ 1 - 1
client/src/services/base-config.service.dev.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 1 - 1
client/src/services/helpdesk-config.service.dev.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 1 - 1
client/src/services/helpdesk-config.service.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 2 - 2
client/src/services/helpdesk.service.dev.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ import {
 } from './helpdesk.service';
 import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular';
 
-const SIMULATED_RESPONSE_TIME = 30;
+const SIMULATED_RESPONSE_TIME = 300;
 
 export default class HelpDeskService implements IHelpDeskService {
     PWM_GLOBAL: any;

+ 9 - 7
client/src/services/helpdesk.service.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -118,8 +118,8 @@ export default class HelpDeskService implements IHelpDeskService {
 
         return this.pwmService
             .httpRequest(url, { data: data })
-            .then((result: IVerificationStatus) => {
-                return this.$q.resolve(result);
+            .then((result: any) => {
+                return this.$q.resolve(result.data);
             });
     }
 
@@ -180,7 +180,7 @@ export default class HelpDeskService implements IHelpDeskService {
         return this.pwmService
             .httpRequest(url, {})
             .then((result: any) => {
-                return this.$q.resolve(result);
+                return this.$q.resolve(result.data);
             });
     }
 
@@ -275,12 +275,14 @@ export default class HelpDeskService implements IHelpDeskService {
 
         return this.pwmService
             .httpRequest(url, { data: data })
-            .then((result: IValidationStatus) => {
+            .then((result: any) => {
+                const validationStatus: IValidationStatus = result.data;
+
                 this.localStorageService.setItem(
                     this.localStorageService.keys.VERIFICATION_STATE,
-                    result.verificationState
+                    validationStatus.verificationState
                 );
-                return this.$q.resolve(result);
+                return this.$q.resolve(validationStatus);
             });
     }
 

+ 2 - 1
client/src/services/local-storage.service.ts

@@ -26,6 +26,7 @@ import { ILogService, IWindowService } from 'angular';
 const PWM_PREFIX = 'PWM_';
 const KEYS = {
     SEARCH_TEXT: 'searchText',
+    HELPDESK_SEARCH_TEXT: 'helpdeskSearchText',
     SEARCH_VIEW: 'searchView',
     VERIFICATION_STATE: 'verificationState'
 };
@@ -51,7 +52,7 @@ export default class LocalStorageService {
     }
 
     setItem(key: string, value: any): void {
-        if (this.localStorageEnabled) {
+        if (this.localStorageEnabled && value) {
             this.$window.sessionStorage[this.prepKey(key)] = value;
         }
     }

+ 20 - 22
client/src/services/people.data.json

@@ -9,7 +9,7 @@
     "title": "Senior Sales Associate",
     "workforceId": "E84-001",
     "managerId": null,
-    "photoURL": "/images/avatars/1.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=1",
     "_displayName": "Jean Ryan - Senior Sales Associate",
     "displayNames": [
       "Jean Ryan",
@@ -90,7 +90,7 @@
     "title": "Quality Engineer",
     "workforceId": "E84-002",
     "managerId": 1,
-    "photoURL": "/images/avatars/2.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=2",
     "_displayName": "Ruby Bowman - Quality Engineer",
     "displayNames": [
       "Ruby Bowman",
@@ -171,7 +171,7 @@
     "title": "Community Outreach Specialist",
     "workforceId": "E84-003",
     "managerId": 1,
-    "photoURL": "/images/avatars/3.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=3",
     "_displayName": "William Carter - Community Outreach Specialist",
     "displayNames": [
       "William Carter",
@@ -252,7 +252,7 @@
     "title": "Research Nurse",
     "workforceId": "E84-004",
     "managerId": 1,
-    "photoURL": "/images/avatars/4.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=4",
     "_displayName": "Alan Snyder - Research Nurse",
     "displayNames": [
       "Alan Snyder",
@@ -333,7 +333,7 @@
     "title": "Financial Analyst",
     "workforceId": "E84-005",
     "managerId": 2,
-    "photoURL": "/images/avatars/5.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=5",
     "_displayName": "Aaron Alvarez - Financial Analyst",
     "displayNames": [
       "Aaron Alvarez",
@@ -495,7 +495,7 @@
     "title": "Design Engineer",
     "workforceId": "E84-007",
     "managerId": 2,
-    "photoURL": "/images/avatars/7.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=7",
     "_displayName": "Mildred Hayes - Design Engineer",
     "displayNames": [
       "Mildred Hayes",
@@ -576,7 +576,7 @@
     "title": "Structural Engineer",
     "workforceId": "E84-008",
     "managerId": 3,
-    "photoURL": "/images/avatars/8.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=8",
     "_displayName": "Margaret Holmes - Structural Engineer",
     "displayNames": [
       "Margaret Holmes",
@@ -657,7 +657,7 @@
     "title": "Junior Executive",
     "workforceId": "E84-009",
     "managerId": 3,
-    "photoURL": "/images/avatars/9.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=9",
     "_displayName": "Jack Jackson - Junior Executive",
     "displayNames": [
       "Jack Jackson",
@@ -749,7 +749,7 @@
     "title": "Chief Design Engineer",
     "workforceId": "E84-010",
     "managerId": 3,
-    "photoURL": "/images/avatars/10.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=10",
     "_displayName": "Judy Butler - Chief Design Engineer",
     "displayNames": [
       "Judy Butler",
@@ -830,7 +830,7 @@
     "title": "Engineer IV",
     "workforceId": "E84-011",
     "managerId": 9,
-    "photoURL": "/images/avatars/11.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=11",
     "_displayName": "Tina Gutierrez - Engineer IV",
     "displayNames": [
       "Tina Gutierrez",
@@ -911,7 +911,7 @@
     "title": "Human Resources Manager",
     "workforceId": "E84-012",
     "managerId": 9,
-    "photoURL": "/images/avatars/12.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=12",
     "_displayName": "Jose Cox - Human Resources Manager",
     "displayNames": [
       "Jose Cox",
@@ -1073,7 +1073,7 @@
     "title": "Automation Specialist I",
     "workforceId": "E84-014",
     "managerId": 9,
-    "photoURL": "/images/avatars/14.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=14",
     "_displayName": "Joe Stevens - Automation Specialist I",
     "displayNames": [
       "Joe Stevens",
@@ -1154,7 +1154,7 @@
     "title": "Safety Technician I",
     "workforceId": "E84-015",
     "managerId": 20,
-    "photoURL": "/images/avatars/15.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=15",
     "_displayName": "Randy Grant - Safety Technician I",
     "displayNames": [
       "Randy Grant",
@@ -1235,7 +1235,7 @@
     "title": "Compensation Analyst",
     "workforceId": "E84-016",
     "managerId": 9,
-    "photoURL": "/images/avatars/16.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=16",
     "_displayName": "Martin Mason - Compensation Analyst",
     "displayNames": [
       "Martin Mason",
@@ -1316,7 +1316,7 @@
     "title": "Data Coordiator",
     "workforceId": "E84-017",
     "managerId": 16,
-    "photoURL": "/images/avatars/17.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=17",
     "_displayName": "Cynthia Porter - Data Coordiator",
     "displayNames": [
       "Cynthia Porter",
@@ -1397,13 +1397,11 @@
     "title": "Business Systems Development Analyst",
     "workforceId": "E84-018",
     "managerId": 17,
-    "photoURL": "/images/avatars/18.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=18",
     "_displayName": "Nancy Burns - Business Systems Development Analyst",
     "displayNames": [
       "Nancy Burns",
-      "nburnsh@wordpress.org",
-      "Business Systems Development Analyst",
-      "(444) 231-5492"
+      "nburnsh@wordpress.org"
     ],
     "detail": {
       "sn": {
@@ -1478,7 +1476,7 @@
     "title": "Structural Engineer",
     "workforceId": "E84-019",
     "managerId": 18,
-    "photoURL": "/images/avatars/19.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=26",
     "_displayName": "Jimmy Montgomery - Structural Engineer",
     "displayNames": [
       "Jimmy Montgomery",
@@ -1559,7 +1557,7 @@
     "title": "Desktop Support Technician",
     "workforceId": "E84-020",
     "managerId": 19,
-    "photoURL": "/images/avatars/20.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=20",
     "_displayName": "Bruce Carroll - Desktop Support Technician",
     "displayNames": [
       "Bruce Carroll",
@@ -1640,7 +1638,7 @@
     "title": "No Real Position",
     "workforceId": "E84-021",
     "managerId": null,
-    "photoURL": "/images/avatars/21.jpg",
+    "photoURL": "http://i.pravatar.cc/128?img=21",
     "_displayName": "Orphan User - No Real Position",
     "displayNames": [
       "Orphan User",

+ 1 - 1
client/src/services/peoplesearch-config.service.ts

@@ -3,7 +3,7 @@
  * http://www.pwm-project.org
  *
  * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2017 The PWM Project
+ * Copyright (c) 2009-2018 The PWM Project
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by

+ 20 - 1
client/src/services/pwm.service.ts

@@ -111,7 +111,26 @@ export default class PwmService implements IPwmService {
                     return this.handlePwmError(response);
                 }
 
-                return this.$q.resolve(response.data['data']);
+                // Note: sometimes response.data looks like this:
+                // {
+                //     "error": false,
+                //     "errorCode": 0,
+                //     "data": {
+                //         "foo": "1",
+                //         "bar": "2"
+                //     }
+                // }
+
+                // Note: other times, response.data looks like this:
+                // {
+                //     "error": false,
+                //     "errorCode": 0,
+                //     "successMessage": "The operation has been successfully completed."
+                // }
+
+                // Since we can't make assumptions about the structure, we just need to return the whole response.data
+                // payload:
+                return this.$q.resolve(response.data);
             });
 
         return promise;

+ 0 - 93
client/src/ux/app-bar.component.scss

@@ -1,93 +0,0 @@
-/*!
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-$mf-app-bar-height: 37px;
-$mf-app-bar-icon-size: 32px;
-
-mf-app-bar {
-  display: block;
-  min-height: $mf-app-bar-height;
-
-  // For larger displays, place the search bar inline
-  &.large {
-    > .mf-app-bar-content {
-      > mf-auto-complete,
-      > mf-search-bar {
-        margin: 0 10px;
-        max-width: 320px;
-        flex: 1000 1;
-        order: initial;
-        width: auto;
-      }
-    }
-  }
-
-  // For extra-large displays, add extra spacing around search bar
-  &.extra-large {
-    > .mf-app-bar-content {
-      > mf-auto-complete,
-      > mf-search-bar {
-        margin: 0 30px;
-      }
-    }
-  }
-
-  > .mf-app-bar-content {
-    display: flex;
-    flex-flow: row wrap;
-    line-height: $mf-app-bar-height;
-
-    > mf-auto-complete,
-    > mf-search-bar {
-      height: $mf-app-bar-height;
-      margin-top: 5px;
-      order: 1;
-      width: 100%;
-    }
-
-    > span[flex] {
-      display: inline-block;
-      flex: 1 1;
-    }
-
-    > #page-content-title {
-      font-size: 20px;
-      font-weight: normal;
-      height: $mf-app-bar-height;
-      line-height: $mf-app-bar-height;
-    }
-
-    > mf-icon-button {
-      margin-top: ($mf-app-bar-height - $mf-app-bar-icon-size) / 2;
-    }
-  }
-
-  .mf-divider {
-    &.vertical {
-      background-color: transparentize(#808080, .50);
-      height: $mf-app-bar-height;
-      margin: 0 5px;
-      width: 1px;
-    }
-  }
-}

+ 0 - 46
client/src/ux/app-bar.component.ts

@@ -1,46 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-import { Component } from '../component';
-import { IAugmentedJQuery } from 'angular';
-import ElementSizeService from './element-size.service';
-
-export enum AppBarSize {
-    Large = 413,
-    ExtraLarge = 473
-}
-
-@Component({
-    stylesheetUrl: require('ux/app-bar.component.scss'),
-    template: `<div class="mf-app-bar-content" ng-transclude></div>`,
-    transclude: true
-})
-export default class AppBarComponent {
-    static $inject = [ '$element', 'MfElementSizeService' ];
-    constructor(private $element: IAugmentedJQuery, private elementSizeService: ElementSizeService) {
-    }
-
-    $onInit(): void {
-        this.elementSizeService.watchWidth(this.$element, AppBarSize);
-    }
-}

+ 0 - 86
client/src/ux/auto-complete.component.scss

@@ -1,86 +0,0 @@
-/*!
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-mf-auto-complete {
-  display: block;
-  height: 21px;
-  position: relative;
-
-  > mf-search-bar {
-    height: 100%;
-    max-width: 100%;
-    width: 100%;
-  }
-
-  > .results {
-    background-color: white;
-    bottom: 0;
-    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26);
-    list-style: none;
-    margin: 0;
-    max-height: 240px;
-    max-width: 100%;
-    overflow-y: auto;
-    padding: 0;
-    position: absolute;
-    transform: translateY(100%);
-    width: 100%;
-    z-index: 100;
-
-    > li {
-      box-sizing: border-box;
-      cursor: pointer;
-      height: 24px;
-      line-height: 24px;
-      overflow: hidden;
-      padding: 0 10px;
-      text-overflow: ellipsis;
-      text-align: left;
-      white-space: nowrap;
-      width: 100%;
-
-      &:hover {
-        background-color: #eef2f2;
-      }
-
-      &.search-message {
-        color: #808080;
-        text-align: center;
-      }
-
-      &.selected {
-        background-color: #dae1e1;
-      }
-    }
-  }
-}
-
-[dir="rtl"] {
-  mf-auto-complete {
-    > .results {
-      > li {
-        text-align: right;
-      }
-    }
-  }
-}

+ 0 - 236
client/src/ux/auto-complete.component.ts

@@ -1,236 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 The PWM Project
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-import { Component } from '../component';
-import { IAttributes, IAugmentedJQuery, IDocumentService, IPromise, IScope } from 'angular';
-
-@Component({
-    bindings: {
-        'onSearchTextChange': '&',
-        'inputDebounce': '<',
-        'itemSelected': '&',
-        'item': '@',
-        'itemText': '@',
-        'searchFunction': '&search',
-        'searchText': '<'
-    },
-    template: [
-        '$element',
-        '$attrs',
-        ($element: IAugmentedJQuery, $attrs: IAttributes): string => {
-            // Remove content template from dom
-            const contentTemplate: IAugmentedJQuery = $element.find('content-template');
-            contentTemplate.detach();
-
-            return `
-                <mf-search-bar input-debounce="$ctrl.inputDebounce"
-                               search-text="$ctrl.searchText"
-                               on-search-text-change="$ctrl.onSearchBarTextChange(value)"
-                               on-key-down="$ctrl.onSearchBarKeyDown($event)"
-                               ng-click="$ctrl.onSearchBarClick($event)"
-                               auto-focus></mf-search-bar>
-                <ul class="results" ng-if="$ctrl.show" ng-click="$event.stopPropagation()">
-                    <li ng-repeat="item in $ctrl.items"
-                       ng-click="$ctrl.selectItem(item)"
-                       ng-class="{ \'selected\': $index == $ctrl.selectedIndex }\">` +
-                contentTemplate.html().replace(new RegExp($attrs['item'], 'g'), 'item') +
-                    `</li>
-                    <li class="search-message"
-                        ng-if="$ctrl.show && $ctrl.searchText && !$ctrl.loading && !$ctrl.items.length">
-                        <span translate="Display_SearchResultsNone"></span>
-                    </li>
-                </ul>`;
-        }],
-    stylesheetUrl: require('ux/auto-complete.component.scss')
-})
-export default class AutoCompleteComponent {
-    item: string;
-    items: any[];
-    itemSelected: (item: any) => void;
-    loading: boolean;
-    onSearchTextChange: Function;
-    searchText: string;
-    searchFunction: (query: any) => IPromise<any[]>;
-    searchMessage: string;
-    selectedIndex: number;
-    show: boolean;
-
-    static $inject = [ '$document', '$element', '$scope' ];
-    constructor(private $document: IDocumentService,
-                private $element: IAugmentedJQuery,
-                private $scope: IScope) {
-    }
-
-    $onDestroy(): void {
-        this.$document.off('click', this.onDocumentClick.bind(this));
-    }
-
-    $onInit(): void {
-        this.selectedIndex = -1;
-
-        if (this.searchText) {
-            this.fetchAutoCompleteData(this.searchText);
-        }
-
-        this.hideResults();
-    }
-
-    $postLink(): void {
-        let self = this;
-
-        // Listen for clicks outside of the auto-complete component
-        // Implemented as a click event instead of a blur event, so the results list can be clicked
-        this.$document.on('click', this.onDocumentClick.bind(this));
-    }
-
-    onSearchBarClick(event: Event): void {
-        event.stopImmediatePropagation();
-    }
-
-    onSearchBarFocus(): void {
-        if (this.hasItems()) {
-            this.showResults();
-        }
-    }
-
-    onSearchBarTextChange(value: string): void {
-        this.searchText = value;
-        this.fetchAutoCompleteData(value);
-        this.showResults();
-
-        this.onSearchTextChange({ value: value });
-    }
-
-    onSearchBarKeyDown(event: KeyboardEvent): void {
-        switch (event.keyCode) {
-            case 40: // ArrowDown
-                if (this.hasItems() && !this.show) {
-                    this.showResults();
-                }
-                else {
-                    this.selectNextItem();
-                    event.preventDefault();
-                }
-                break;
-            case 38: // ArrowUp
-                this.selectPreviousItem();
-                event.preventDefault();
-                break;
-            case 27: // Escape
-                if (!this.show || !this.hasItems()) {
-                    this.clearResults();
-                }
-                else {
-                    this.hideResults();
-                }
-
-                break;
-            case 13: // Enter
-                if (this.hasItems() && this.show) {
-                    const item = this.getSelectedItem();
-                    this.selectItem(item);
-                }
-                break;
-            case 9: // Tab
-                if (!this.searchText || !this.show) {
-                    return;
-                }
-
-                if (event.shiftKey) {
-                    this.selectPreviousItem();
-                }
-                else {
-                    this.selectNextItem();
-                }
-
-                event.preventDefault();
-                break;
-        }
-    }
-
-    selectItem(item: any): void {
-        this.clearResults();
-
-        const data = {};
-        data[this.item] = item;
-        this.itemSelected(data);
-    }
-
-    private clearResults(): void {
-        this.resetSelection();
-        this.searchText = null;
-        this.items = [];
-    }
-
-    private onDocumentClick(): void {
-        if (this.show) {
-            this.hideResults();
-            this.$scope.$apply();
-        }
-    }
-
-    private fetchAutoCompleteData(value: string): void {
-        this.loading = true;
-        const self = this;
-        this.searchFunction({ query: value })
-            .then((results: any[]) => {
-                self.items = results;
-                self.resetSelection();
-            })
-            .finally(() => {
-                self.loading = false;
-            });
-    }
-
-    private getSelectedItem(): any {
-        return this.items[this.selectedIndex];
-    }
-
-    private hasItems(): boolean {
-        return this.items && !!this.items.length;
-    }
-
-    private hideResults(): void  {
-        this.show = false;
-    }
-
-    private resetSelection(): void {
-        this.selectedIndex = 0;
-    }
-
-    private selectNextItem(): void {
-        if (this.hasItems() && this.selectedIndex < this.items.length - 1) {
-            this.selectedIndex++;
-        }
-    }
-
-    private selectPreviousItem(): void {
-        if (this.hasItems() && this.selectedIndex > 0) {
-            this.selectedIndex--;
-        }
-    }
-
-    private showResults(): void  {
-        this.show = true;
-    }
-}

Some files were not shown because too many files changed in this diff