Browse Source

Revert "web: rewrite interface in react"

Revert "Fix publish-openstack-javascript-content"

This reverts commit ca199eb9db.
This reverts commit 1082faae95.

This appears to remove the tarball publishing system that we rely on.

Change-Id: Id746fb826dfc01b157c5b772adc1d2991ddcd93a
tags/3.3.0
James E. Blair 7 months ago
parent
commit
3dba813c64
95 changed files with 5928 additions and 5969 deletions
  1. 8
    0
      .babelrc
  2. 7
    0
      .eslintrc
  3. 20
    14
      .zuul.yaml
  4. 0
    1
      MANIFEST.in
  5. 1
    5
      TESTING.rst
  6. 18
    83
      doc/source/admin/installation.rst
  7. 84
    49
      doc/source/developer/javascript.rst
  8. 86
    0
      package.json
  9. 21
    0
      playbooks/dashboard/multi.yaml
  10. 13
    14
      playbooks/dashboard/run.yaml
  11. 0
    11
      releasenotes/notes/react-zuul-a593c7627ca22b37.yaml
  12. 2
    2
      tests/unit/test_web.py
  13. 8
    8
      tests/unit/test_web_urls.py
  14. 2
    4
      tools/pip.sh
  15. 17
    0
      tsconfig.json
  16. 7
    0
      tslint.json
  17. 0
    23
      web/.eslintrc
  18. 1
    0
      web/.gitignore
  19. 1
    0
      web/.jshintignore
  20. 21
    0
      web/.jshintrc
  21. 84
    0
      web/app-routing.module.ts
  22. 28
    0
      web/app.component.ts
  23. 73
    0
      web/app.module.ts
  24. 0
    1
      web/build
  25. 28
    0
      web/builds/build.ts
  26. 67
    0
      web/builds/builds.component.html
  27. 78
    0
      web/builds/builds.component.ts
  28. 24
    0
      web/config/main.ejs
  29. 178
    0
      web/config/status-basic.json
  30. 312
    0
      web/config/status-openstack.json
  31. 1
    0
      web/config/status-tree.json
  32. 147
    0
      web/config/webpack.common.js
  33. 23
    0
      web/config/webpack.dev.js
  34. 47
    0
      web/config/webpack.lint.js
  35. 32
    0
      web/config/webpack.prod.js
  36. 24
    0
      web/core/core.module.ts
  37. BIN
      web/images/black.png
  38. BIN
      web/images/green.png
  39. BIN
      web/images/grey.png
  40. 0
    0
      web/images/line-angle.png
  41. 0
    0
      web/images/line-t.png
  42. 0
    0
      web/images/line.png
  43. BIN
      web/images/red.png
  44. 17
    0
      web/jobs/details.ts
  45. 25
    0
      web/jobs/job.ts
  46. 47
    0
      web/jobs/jobs.component.html
  47. 54
    0
      web/jobs/jobs.component.ts
  48. 22
    0
      web/main.ts
  49. 30
    0
      web/navigation/navigation.component.html
  50. 34
    0
      web/navigation/navigation.component.ts
  51. 0
    41
      web/package.json
  52. BIN
      web/public/favicon.ico
  53. 0
    17
      web/public/index.html
  54. 0
    15
      web/public/manifest.json
  55. 0
    162
      web/src/App.jsx
  56. 0
    102
      web/src/App.test.jsx
  57. 0
    133
      web/src/api.js
  58. 0
    237
      web/src/containers/TableFilters.jsx
  59. 0
    99
      web/src/containers/status/Change.jsx
  60. 0
    317
      web/src/containers/status/ChangePanel.jsx
  61. 0
    64
      web/src/containers/status/ChangePanel.test.jsx
  62. 0
    54
      web/src/containers/status/ChangeQueue.jsx
  63. 0
    136
      web/src/containers/status/Pipeline.jsx
  64. BIN
      web/src/images/logo.png
  65. 0
    83
      web/src/images/logo.svg
  66. 0
    122
      web/src/index.css
  67. 0
    40
      web/src/index.js
  68. 0
    159
      web/src/pages/Builds.jsx
  69. 0
    99
      web/src/pages/Jobs.jsx
  70. 0
    289
      web/src/pages/Status.jsx
  71. 0
    158
      web/src/pages/Stream.jsx
  72. 0
    79
      web/src/pages/Tenants.jsx
  73. 0
    94
      web/src/reducers.js
  74. 0
    119
      web/src/registerServiceWorker.js
  75. 0
    52
      web/src/routes.js
  76. 944
    0
      web/status/jquery.zuul.js
  77. 32
    0
      web/status/status.component.html
  78. 57
    0
      web/status/status.component.ts
  79. 102
    0
      web/status/zuulStart.js
  80. 18
    0
      web/stream/stream.component.css
  81. 24
    0
      web/stream/stream.component.html
  82. 90
    0
      web/stream/stream.component.ts
  83. 65
    0
      web/styles/zuul.css
  84. 18
    0
      web/tenants/tenant.ts
  85. 40
    0
      web/tenants/tenants.component.html
  86. 38
    0
      web/tenants/tenants.component.ts
  87. 18
    0
      web/zuul/description.ts
  88. 18
    0
      web/zuul/info.ts
  89. 19
    0
      web/zuul/infoResponse.ts
  90. 152
    0
      web/zuul/zuul.service.ts
  91. 3
    0
      webpack.config.js
  92. 2572
    3053
      yarn.lock
  93. 4
    8
      zuul/_setup_hook.py
  94. 22
    22
      zuul/web/__init__.py
  95. 0
    0
      zuul/web/static/.keep

+ 8
- 0
.babelrc View File

@@ -0,0 +1,8 @@
1
+{
2
+  "presets": ['env'],
3
+  "plugins": [[
4
+    "angularjs-annotate", {
5
+      "explicitOnly": false
6
+    }
7
+  ]]
8
+}

+ 7
- 0
.eslintrc View File

@@ -0,0 +1,7 @@
1
+parser: babel-eslint
2
+plugins:
3
+  - standard
4
+rules:
5
+  camelcase: off
6
+extends:
7
+  - ./node_modules/eslint-config-standard/eslintrc.json

+ 20
- 14
.zuul.yaml View File

@@ -42,20 +42,27 @@
42 42
 - job:
43 43
     name: zuul-build-dashboard
44 44
     parent: build-javascript-content
45
-    success-url: 'npm/html/'
45
+    success-url: 'npm/html/status.html'
46 46
     files:
47
+      - package.json
48
+      - tsconfig.json
49
+      - tslint.json
47 50
       - web/.*
51
+      - webpack.config.js
52
+      - yarn.lock
48 53
     vars:
49
-      javascript_content_dir: "../zuul/web/static"
50
-      zuul_work_dir: "{{ zuul.project.src_dir }}/web"
54
+      javascript_content_dir: zuul/web/static
51 55
       zuul_api_url: https://zuul.openstack.org
52 56
     run: playbooks/dashboard/run.yaml
53 57
 
54 58
 - job:
55 59
     name: zuul-build-dashboard-multi-tenant
56 60
     parent: zuul-build-dashboard
61
+    success-url: 'npm/html/tenants.html'
62
+    post-run: playbooks/dashboard/multi.yaml
57 63
     vars:
58 64
       zuul_api_url: https://softwarefactory-project.io/zuul
65
+      javascript_copy_links: false
59 66
 
60 67
 - project:
61 68
     check:
@@ -74,16 +81,16 @@
74 81
         - zuul-build-dashboard
75 82
         - zuul-build-dashboard-multi-tenant
76 83
         - nodejs-npm-run-lint:
77
-            vars:
78
-              node_version: 8
79
-              zuul_work_dir: "{{ zuul.project.src_dir }}/web"
80
-        - nodejs-npm-run-test:
81 84
             vars:
82 85
                 node_version: 8
83
-                zuul_work_dir: "{{ zuul.project.src_dir }}/web"
84 86
             success-url: 'npm/reports/bundle.html'
85 87
             files:
88
+              - package.json
89
+              - tsconfig.json
90
+              - tslint.json
86 91
               - web/.*
92
+              - webpack.config.js
93
+              - yarn.lock
87 94
         - zuul-stream-functional
88 95
         - zuul-tox-remote
89 96
         - pbrx-build-container-images:
@@ -106,16 +113,16 @@
106 113
               - playbooks/zuul-migrate/.*
107 114
         - zuul-build-dashboard
108 115
         - nodejs-npm-run-lint:
109
-            vars:
110
-              node_version: 8
111
-              zuul_work_dir: "{{ zuul.project.src_dir }}/web"
112
-        - nodejs-npm-run-test:
113 116
             vars:
114 117
                 node_version: 8
115
-                zuul_work_dir: "{{ zuul.project.src_dir }}/web"
116 118
             success-url: 'npm/reports/bundle.html'
117 119
             files:
120
+              - package.json
121
+              - tsconfig.json
122
+              - tslint.json
118 123
               - web/.*
124
+              - webpack.config.js
125
+              - yarn.lock
119 126
         - zuul-stream-functional
120 127
         - zuul-tox-remote
121 128
         - pbrx-build-container-images:
@@ -128,7 +135,6 @@
128 135
         - publish-openstack-javascript-content:
129 136
             vars:
130 137
                 node_version: 8
131
-                zuul_work_dir: "{{ zuul.project.src_dir }}/web"
132 138
         - openstackzuul-pbrx-push-container-images:
133 139
             vars:
134 140
               pbrx_prefix: zuul

+ 0
- 1
MANIFEST.in View File

@@ -1,7 +1,6 @@
1 1
 include AUTHORS
2 2
 include ChangeLog
3 3
 include zuul/web/static/*
4
-include zuul/web/static/static/*/*
5 4
 
6 5
 exclude .gitignore
7 6
 exclude .gitreview

+ 1
- 5
TESTING.rst View File

@@ -38,15 +38,11 @@ As of zuul v3, a running zookeeper is required to execute tests.
38 38
 
39 39
 *Install javascript dependencies*::
40 40
 
41
-  pushd web
42 41
   yarn install
43
-  popd
44 42
 
45 43
 *Build javascript assets*::
46 44
 
47
-  pushd web
48
-  yarn build
49
-  popd
45
+  npm run build:dev
50 46
 
51 47
 Run The Tests
52 48
 -------------

+ 18
- 83
doc/source/admin/installation.rst View File

@@ -95,7 +95,7 @@ Static External
95 95
 Sub-URL
96 96
   Serve a Zuul dashboard from a location below the root URL as part of
97 97
   presenting integration with other application.
98
-  https://softwarefactory-project.io/zuul/ is an example of a Zuul dashboard
98
+  https://softwarefactory-project.io/zuul3/ is an example of a Zuul dashboard
99 99
   that is being served from a Sub-URL.
100 100
 
101 101
 None of those make any sense for simple non-production oriented deployments, so
@@ -121,71 +121,18 @@ simplest reverse-proxy case is::
121 121
 Static Offload
122 122
 --------------
123 123
 
124
-To have the Reverse Proxy serve the static html/javascript assets instead of
124
+To have the Reverse Proxy serve the static html/javscript assets instead of
125 125
 proxying them to the REST layer, register the location where you unpacked
126
-the web application as the document root and add rewrite rules::
126
+the web application as the document root and add a simple rewrite rule::
127 127
 
128
-  <Directory /usr/share/zuul>
128
+  DocumentRoot /var/lib/html
129
+  <Directory /var/lib/html>
129 130
     Require all granted
130 131
   </Directory>
131
-  Alias / /usr/share/zuul/
132
-  <Location />
133
-    RewriteEngine on
134
-    RewriteBase /
135
-    # Rewrite api to the zuul-web endpoint
136
-    RewriteRule api/tenant/(.*)/console-stream ws://localhost:9000/api/tenant/$1/console-stream [P,L]
137
-    RewriteRule api/(.*)$ http://localhost:9000/api/$1 [P,L]
138
-    # Backward compatible rewrite
139
-    RewriteRule t/(.*)/(.*).html(.*) /t/$1/$2$3 [R=301,L,NE]
140
-
141
-    # Don't rewrite files or directories
142
-    RewriteCond %{REQUEST_FILENAME} !-f
143
-    RewriteCond %{REQUEST_FILENAME} !-d
144
-    RewriteRule . /index.html [L]
145
-  </Location>
146
-
147
-
148
-Sub directory serving
149
----------------------
150
-
151
-The web application needs to be rebuild to update the internal location of
152
-the static files. Set the homepage setting in the package.json to an
153
-absolute path or url. For example, to deploy the web interface through a
154
-'/zuul/' sub directory:
155
-
156
-.. note::
157
-
158
-   The web dashboard source code and package.json are located in the ``web``
159
-   directory. All the yarn commands need to be executed from the ``web``
160
-   directory.
161
-
162
-.. code-block:: bash
163
-
164
-  sed -e 's#"homepage": "/"#"homepage": "/zuul/"#' -i package.json
165
-  yarn build
166
-
167
-Then assuming the web application is unpacked in /usr/share/zuul,
168
-add the following rewrite rules::
169
-
170
-  <Directory /usr/share/zuul>
171
-    Require all granted
172
-  </Directory>
173
-  Alias /zuul /usr/share/zuul/
174
-  <Location /zuul>
175
-    RewriteEngine on
176
-    RewriteBase /zuul
177
-    # Rewrite api to the zuul-web endpoint
178
-    RewriteRule api/tenant/(.*)/console-stream ws://localhost:9000/api/tenant/$1/console-stream [P,L]
179
-    RewriteRule api/(.*)$ http://localhost:9000/api/$1 [P,L]
180
-    # Backward compatible rewrite
181
-    RewriteRule t/(.*)/(.*).html(.*) /t/$1/$2$3 [R=301,L,NE]
182
-
183
-    # Don't rewrite files or directories
184
-    RewriteCond %{REQUEST_FILENAME} !-f
185
-    RewriteCond %{REQUEST_FILENAME} !-d
186
-    RewriteRule . /zuul/index.html [L]
187
-  </Location>
188
-
132
+  RewriteEngine on
133
+  RewriteRule ^/t/.*/(.*)$ /$1 [L]
134
+  RewriteRule ^/api/tenant/(.*)/console-stream ws://localhost:9000/api/tenant/$1/console-stream [P]
135
+  RewriteRule ^/api/(.*)$ http://localhost:9000/api/$1 [P]
189 136
 
190 137
 White Labeled Tenant
191 138
 --------------------
@@ -200,27 +147,14 @@ rule to ensure connection webhooks don't try to get put into the tenant scope.
200 147
 
201 148
 Assuming the zuul tenant name is "example", the rewrite rules are::
202 149
 
203
-  <Directory /usr/share/zuul>
150
+  DocumentRoot /var/lib/html
151
+  <Directory /var/lib/html>
204 152
     Require all granted
205 153
   </Directory>
206
-  Alias / /usr/share/zuul/
207
-  <Location />
208
-    RewriteEngine on
209
-    RewriteBase /
210
-    # Rewrite api to the zuul-web endpoint
211
-    RewriteRule api/connection/(.*)$ http://localhost:9000/api/connection/$1 [P,L]
212
-    RewriteRule api/console-stream ws://localhost:9000/api/tenant/example/console-stream [P,L]
213
-    RewriteRule api/(.*)$ http://localhost:9000/api/tenant/example/$1 [P,L]
214
-    # Backward compatible rewrite
215
-    RewriteRule t/(.*)/(.*).html(.*) /t/$1/$2$3 [R=301,L,NE]
216
-
217
-    # Don't rewrite files or directories
218
-    RewriteCond %{REQUEST_FILENAME} !-f
219
-    RewriteCond %{REQUEST_FILENAME} !-d
220
-    RewriteRule . /index.html [L]
221
-  </Location>
222
-
223
-
154
+  RewriteEngine on
155
+  RewriteRule ^/api/connection/(.*)$ http://localhost:9000/api/connection/$1 [P]
156
+  RewriteRule ^/api/console-stream ws://localhost:9000/api/tenant/example/console-stream [P]
157
+  RewriteRule ^/api/(.*)$ http://localhost:9000/api/tenant/example/$1 [P]
224 158
 
225 159
 Static External
226 160
 ---------------
@@ -231,8 +165,9 @@ Static External
231 165
   dynamic url rewrite rules only works for white-labeled deployments.
232 166
 
233 167
 In order to serve the zuul dashboard code from an external static location,
234
-``REACT_APP_ZUUl_API`` must be set at javascript build time:
168
+``ZUUL_API_URL`` must be set at javascript build time by passing the
169
+``--define`` flag to the ``npm build:dist`` command.
235 170
 
236 171
 .. code-block:: bash
237 172
 
238
-  REACT_APP_ZUUL_API='http://zuul-web.example.com' yarn build
173
+  npm build:dist -- --define "ZUUL_API_URL='http://zuul-web.example.com'"

+ 84
- 49
doc/source/developer/javascript.rst View File

@@ -6,15 +6,9 @@ is managed using Javascript toolchains. It is intended to be served by zuul-web
6 6
 directly from zuul/web/static in the simple case, or to be published to
7 7
 an alternate static web location, such as an Apache server.
8 8
 
9
-The web dashboard is written in `React`_ and `Patternfly`_ and is
10
-managed by `create-react-app`_ and `yarn`_ which in turn both assume a
11
-functioning and recent `nodejs`_ installation.
12
-
13
-.. note::
14
-
15
-   The web dashboard source code and package.json are located in the ``web``
16
-   directory. All the yarn commands need to be executed from the ``web``
17
-   directory.
9
+The web dashboard is written in `Typescript`_ and `Angular`_ and is
10
+managed by `yarn`_ and `webpack`_ which in turn both assume a functioning
11
+and recent `nodejs`_ installation.
18 12
 
19 13
 For the impatient who don't want deal with javascript toolchains
20 14
 ----------------------------------------------------------------
@@ -104,27 +98,28 @@ conflicts is to first resolve the conflicts, if any, in ``package.json``. Then:
104 98
 Which causes yarn to discard the ``yarn.lock`` file, recalculate the
105 99
 dependencies and write new content.
106 100
 
107
-React Components
108
-----------------
101
+webpack asset management
102
+------------------------
109 103
 
110
-Each page is a React Component. For instance the status.html page code is
111
-``web/src/pages/status.jsx``.
104
+`webpack`_ takes care of bundling web assets for deployment, including tasks
105
+such as minifying and transpiling for older browsers. It takes a
106
+javascript-first approach, and generates a html file that includes the
107
+appropriate javascript and CSS to get going.
112 108
 
113
-Mapping of pages/urls to components can be found in the route list in
114
-``web/src/routes.js``.
109
+The main `webpack`_ config file is ``webpack.config.js``. In the Zuul tree that
110
+file is a stub file that includes either a dev or a prod environment from
111
+``web/config/webpack.dev.js`` or ``web/config/webpack.prod.js``. Most of the
112
+important bits are in ``web/config/webpack.common.js``.
115 113
 
116
-Here are some useful documentation about the different libraries:
114
+Angular Components
115
+------------------
117 116
 
118
-- https://reactjs.org/docs/getting-started.html
119
-- https://reacttraining.com/react-router/web/guides/philosophy
120
-- https://react-bootstrap.github.io/components/forms/
121
-- https://redux.js.org/introduction/coreconcepts
122
-- https://www.patternfly.org/pattern-library/
123
-- https://rawgit.com/patternfly/patternfly-react/gh-pages/
117
+Each page has an `Angular Component`_ associated with it. For instance, the
118
+``status.html`` page has code in ``web/status/status.component.ts`` and the
119
+relevant HTML can be found in ``web/status/status.component.html``.
124 120
 
125
-The gh-pages are built from storybook present in the patternfly-react
126
-repository. Sometime the 'View Info' is not enough and using grep in the
127
-repository may yield better documentation.
121
+Mapping of pages/urls to components can be found in the routing module in
122
+``web/app-routing.module.ts``.
128 123
 
129 124
 Development
130 125
 -----------
@@ -133,43 +128,68 @@ Building the code can be done with:
133 128
 
134 129
 .. code-block:: bash
135 130
 
136
-  yarn build
131
+  npm run build
137 132
 
138 133
 zuul-web has a ``static`` route defined which serves files from
139
-``zuul/web/static``. ``yarn build`` will put the build output files
134
+``zuul/web/static``. ``npm run build`` will put the build output files
140 135
 into the ``zuul/web/static`` directory so that zuul-web can serve them.
141 136
 
142
-Development server that handles things like reloading and
143
-hot-updating of code can be started with:
137
+There is a also a development-oriented version of that same command:
138
+
139
+.. code-block:: bash
140
+
141
+  npm run build:dev
142
+
143
+which will build for the ``dev`` environment. This causes some sample data
144
+to be bundled and included.
145
+
146
+Webpack includes a development server that handles things like reloading and
147
+hot-updating of code. The following:
148
+
149
+.. code-block:: bash
150
+
151
+  npm run start
152
+
153
+will build the code and launch the dev server on `localhost:8080`. It will
154
+be configured to use the API endpoint from OpenStack's Zuul. The
155
+``webpack-dev-server`` watches for changes to the files and
156
+re-compiles/refresh as needed.
144 157
 
145 158
 .. code-block:: bash
146 159
 
147
-  yarn start
160
+  npm run start:multi
148 161
 
149
-will build the code and launch the dev server on `localhost:3000`. Fake
150
-api response needs to be set in the ``web/public/api`` directory::
162
+will do the same but will be pointed at the SoftwareFactory Project Zuul, which
163
+is multi-tenant.
164
+
165
+Arbitrary command line options will be passed through after a ``--`` such as:
151 166
 
152 167
 .. code-block:: bash
153 168
 
154
-  mkdir public/api/
155
-  for route in info status jobs builds; do
156
-  curl -o public/api/${route} https://zuul.openstack.org/api/${route}
157
-  done
169
+  npm run start -- --open-file='status.html'
170
+
171
+That's kind of annoying though, so additional targets exist for common tasks:
158 172
 
159
-To use an existing zuul api, uses the REACT_APP_ZUUl_API environment
160
-variable:
173
+Run status against `basic` built-in demo data.
161 174
 
162 175
 .. code-block:: bash
163 176
 
164
-  # Use openstack zuul's api:
165
-  yarn start:openstack
177
+  npm run start:basic
178
+
179
+Run status against `openstack` built-in demo data
166 180
 
167
-  # Use software-factory multi-tenant zuul's api:
168
-  yarn start:multi
181
+.. code-block:: bash
182
+
183
+  npm run start:openstack
169 184
 
170
-  # Use a custom zuul:
171
-  REACT_APP_ZUUL_API="https://zuul.example.com/api/" yarn start
185
+Run status against `tree` built-in demo data.
172 186
 
187
+.. code-block:: bash
188
+
189
+  npm run start:tree
190
+
191
+Additional run commands can be added in `package.json` in the ``scripts``
192
+section.
173 193
 
174 194
 Deploying
175 195
 ---------
@@ -179,16 +199,31 @@ by zuul-web from its ``static`` route. In order to make sure this works
179 199
 properly, the javascript build needs to be performed so that the javascript
180 200
 files are in the ``zuul/web/static`` directory. Because the javascript
181 201
 build outputs into the ``zuul/web/static`` directory, as long as
182
-``yarn build`` has been done before ``pip install .`` or
202
+``npm run build`` has been done before ``pip install .`` or
183 203
 ``python setup.py sdist``, all the files will be where they need to be.
184 204
 As long as `yarn`_ is installed, the installation of zuul will run
185
-``yarn build`` appropriately.
205
+``npm run build`` appropriately.
206
+
207
+Debugging minified code
208
+-----------------------
209
+
210
+Both the ``dev`` and ``prod`` ennvironments use the same `devtool`_
211
+called ``source-map`` which makes debugging errors easier by including mapping
212
+information from the minified and bundled resources to their approriate
213
+non-minified source code locations. Javascript errors in the browser as seen
214
+in the developer console can be clicked on and the appropriate actual source
215
+code location will be shown.
216
+
217
+``source-map`` is considered an appropriate `devtool`_ for production, but has
218
+the downside that it is slower to update. However, since it includes the most
219
+complete mapping information and doesn't impact execution performance, so in
220
+our case we use it for both.
186 221
 
187 222
 .. _yarn: https://yarnpkg.com/en/
188 223
 .. _nodejs: https://nodejs.org/
189 224
 .. _webpack: https://webpack.js.org/
190 225
 .. _devtool: https://webpack.js.org/configuration/devtool/#devtool
191 226
 .. _nodeenv: https://pypi.org/project/nodeenv
192
-.. _React: https://reactjs.org/
193
-.. _Patternfly: https://www.patternfly.org/
194
-.. _create-react-app: https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md
227
+.. _Angular: https://angular.io
228
+.. _Angular Component: https://angular.io/guide/architecture-components
229
+.. _Typescript: https://www.typescriptlang.org/

+ 86
- 0
package.json View File

@@ -0,0 +1,86 @@
1
+{
2
+  "name": "@zuul-ci/dashboard",
3
+  "version": "1.0.0",
4
+  "description": "Web Dashboard for Zuul",
5
+  "main": "web/main.ts",
6
+  "repository": "https://git.zuul-ci.org/zuul",
7
+  "author": "OpenStack Infra",
8
+  "license": "Apache-2.0",
9
+  "babel": {
10
+    "presets": [
11
+      "env"
12
+    ]
13
+  },
14
+  "dependencies": {
15
+    "@angular/common": "^6.0.3",
16
+    "@angular/compiler": "^6.0.3",
17
+    "@angular/core": "^6.0.3",
18
+    "@angular/forms": "^6.0.3",
19
+    "@angular/platform-browser": "^6.0.3",
20
+    "@angular/platform-browser-dynamic": "^6.0.3",
21
+    "@angular/router": "^6.0.3",
22
+    "bootstrap": "3.1.1",
23
+    "core-js": "^2.5.3",
24
+    "graphitejs": "https://github.com/prestontimmons/graphitejs/archive/master.tar.gz",
25
+    "jquery": "^3.3.1",
26
+    "jquery-visibility": "https://github.com/mathiasbynens/jquery-visibility/archive/master.tar.gz",
27
+    "reflect-metadata": "^0.1.12",
28
+    "rxjs": "^6.2.0",
29
+    "rxjs-compat": "^6.0.0-rc.0",
30
+    "zone.js": "^0.8.26"
31
+  },
32
+  "scripts": {
33
+    "build": "npm run build:dist",
34
+    "build:dev": "webpack --env=dev",
35
+    "build:dist": "webpack --env=prod",
36
+    "build:dist-with-depends": "yarn install && npm run build:dist",
37
+    "format": "tslint --project tsconfig.json -c tslint.json --fix",
38
+    "lint": "webpack --env=lint",
39
+    "start": "webpack-dev-server --env=dev --define ZUUL_API_URL=\"'https://zuul.openstack.org'\" --open-page='status.html'",
40
+    "start:multi": "webpack-dev-server --env=dev --define ZUUL_API_URL=\"'https://softwarefactory-project.io/zuul'\" --open-page='tenants.html'",
41
+    "start:basic": "webpack-dev-server --env=dev --open-page='status.html?demo=basic'",
42
+    "start:openstack": "webpack-dev-server --env=dev --open-page='status.html?demo=openstack'",
43
+    "start:tree": "webpack-dev-server --env=dev --open-page='status.html?demo=tree'"
44
+  },
45
+  "devDependencies": {
46
+    "@types/angular": "^1.6.43",
47
+    "@types/bootstrap": "^4.0.0",
48
+    "@types/core-js": "^0.9.46",
49
+    "@types/jquery": "^3.3.1",
50
+    "@types/node": "^9.4.7",
51
+    "babel-core": "^6.26.0",
52
+    "babel-eslint": "^8.0.3",
53
+    "babel-loader": "^7.1.2",
54
+    "babel-plugin-angularjs-annotate": "^0.8.2",
55
+    "babel-preset-env": "^1.6.1",
56
+    "clean-webpack-plugin": "^0.1.16",
57
+    "codelyzer": "^4.2.1",
58
+    "css-loader": "^0.28.10",
59
+    "eslint": ">=3.19.0",
60
+    "eslint-config-standard": "^11.0.0-beta.0",
61
+    "eslint-loader": "^2.0.0",
62
+    "eslint-plugin-import": "^2.8.0",
63
+    "eslint-plugin-node": "^6.0.0",
64
+    "eslint-plugin-promise": "^3.6.0",
65
+    "eslint-plugin-standard": "^3.0.1",
66
+    "file-loader": "^1.1.11",
67
+    "fork-ts-checker-webpack-plugin": "^0.4.1",
68
+    "html-loader": "^0.5.5",
69
+    "html-webpack-plugin": "^3.0.0",
70
+    "resolve-url-loader": "^2.1.0",
71
+    "style-loader": "^0.20.3",
72
+    "ts-loader": "^4.1.0",
73
+    "ts-node": "^5.0.1",
74
+    "tslint": "^5.9.1",
75
+    "tslint-angular": "^1.1.1",
76
+    "tslint-loader": "^3.6.0",
77
+    "typescript": "2.7.2",
78
+    "url-loader": "^0.5.9",
79
+    "webpack": "^4.4.0",
80
+    "webpack-archive-plugin": "^3.0.0",
81
+    "webpack-bundle-analyzer": "^2.9.1",
82
+    "webpack-cli": "^2.0.11",
83
+    "webpack-dev-server": "^3.0.0",
84
+    "webpack-merge": "^4.1.0"
85
+  }
86
+}

+ 21
- 0
playbooks/dashboard/multi.yaml View File

@@ -0,0 +1,21 @@
1
+- hosts: all
2
+  tasks:
3
+
4
+    - name: Make tenant subdir
5
+      file:
6
+        state: directory
7
+        dest: '{{ zuul.project.src_dir }}/{{ javascript_content_dir }}/t'
8
+
9
+    - name: Copy the html/javascript content into subdirs
10
+      shell: |
11
+          CONTENT_DIR="{{ zuul.project.src_dir }}/{{ javascript_content_dir }}"
12
+          mkdir $CONTENT_DIR/t/{{ item }}
13
+          for f in $(find $CONTENT_DIR -type f -mindepth 1 -maxdepth 1) ; do
14
+              cp $f $CONTENT_DIR/t/{{ item }}
15
+          done
16
+      with_items:
17
+        - local
18
+        - ansible
19
+        - ansible-dev
20
+        - openstack.org
21
+        - rdoproject.org

+ 13
- 14
playbooks/dashboard/run.yaml View File

@@ -1,19 +1,18 @@
1 1
 - hosts: all
2
-  pre_tasks:
3
-    - name: Update homepage for sub directory deployment
4
-      replace:
5
-        path: '{{ zuul.project.src_dir }}/web/package.json'
6
-        regexp: '"homepage": "/"'
7
-        replace: '"homepage": "./"'
8
-        # NOTE: using "./" is not enough to support html5 links, even with
9
-        # rewrite rules for unknown files, accessing 'job/devstack' will make
10
-        # the dashboard load static files from 'job/static/...'
11
-        # This works for the preview dashboard that can only be loaded from the
12
-        # npm/html directory anyway.
13 2
   roles:
14 3
     - revoke-sudo
15 4
     - set-zuul-log-path-fact
5
+    # Both sets of quotes are required.
6
+    # The "" quotes are for the shell to protect the '' quotes.
7
+    # We need the '' quotes because defines here are essentially
8
+    # direct string substitutions. Therefore:
9
+    #   --define "ZUUL_API_URL='https://zuul.openstack.org'"
10
+    # with the javascript code:
11
+    #   return ZUUL_API_URL
12
+    # results in
13
+    #   return 'https://zuul.openstack.org'
14
+    # in the compiled javascript.
16 15
     - role: npm
17
-      npm_command: build
18
-  environment:
19
-    REACT_APP_ZUUL_API: "{{ zuul_api_url }}/api/"
16
+      npm_command: >-
17
+        build:dist --
18
+          --define "ZUUL_API_URL='{{ zuul_api_url }}'"

+ 0
- 11
releasenotes/notes/react-zuul-a593c7627ca22b37.yaml View File

@@ -1,11 +0,0 @@
1
----
2
-features:
3
-  - |
4
-    The Zuul web dashboard has been rewritten in React.
5
-upgrade:
6
-  - |
7
-    The Zuul web dashboard is now a single index.html and static offload
8
-    server needs new rewrite rules. Check the install instruction for backward
9
-    compatible rewrite rules. Note that serving the web dashboard from a
10
-    sub-directory requires the application to be rebuilt using the desired
11
-    homepage location.

+ 2
- 2
tests/unit/test_web.py View File

@@ -245,9 +245,9 @@ class TestWeb(BaseTestWeb):
245 245
         self.assertEqual(1, data[0]['queue'])
246 246
 
247 247
     def test_web_bad_url(self):
248
-        # do we redirect to index.html
248
+        # do we 404 correctly
249 249
         resp = self.get_url("status/foo")
250
-        self.assertEqual(200, resp.status_code)
250
+        self.assertEqual(404, resp.status_code)
251 251
 
252 252
     def test_web_find_change(self):
253 253
         # can we filter by change id

+ 8
- 8
tests/unit/test_web_urls.py View File

@@ -51,8 +51,10 @@ class TestWebURLs(ZuulTestCase):
51 51
         ]:
52 52
             for item in page.find_all(tag):
53 53
                 suburl = item.get(attr)
54
-                if suburl.startswith('/'):
55
-                    suburl = suburl[1:]
54
+                # Skip empty urls. Also skip the navbar relative link for now.
55
+                # TODO(mordred) Remove when we have the top navbar link sorted.
56
+                if suburl is None or suburl == "../":
57
+                    continue
56 58
                 link = urllib.parse.urljoin(url, suburl)
57 59
                 self._get(self.port, link)
58 60
 
@@ -64,8 +66,7 @@ class TestDirect(TestWebURLs):
64 66
         self.port = self.web.port
65 67
 
66 68
     def test_status_page(self):
67
-        self._crawl('/')
68
-        self._crawl('/t/tenant-one/status')
69
+        self._crawl('/t/tenant-one/status.html')
69 70
 
70 71
 
71 72
 class TestWhiteLabel(TestWebURLs):
@@ -80,8 +81,7 @@ class TestWhiteLabel(TestWebURLs):
80 81
         self.port = self.proxy.port
81 82
 
82 83
     def test_status_page(self):
83
-        self._crawl('/')
84
-        self._crawl('/status')
84
+        self._crawl('/status.html')
85 85
 
86 86
 
87 87
 class TestWhiteLabelAPI(TestWebURLs):
@@ -108,11 +108,11 @@ class TestSuburl(TestWebURLs):
108 108
     def setUp(self):
109 109
         super(TestSuburl, self).setUp()
110 110
         rules = [
111
-            ('^/zuul/(.*)$', 'http://localhost:{}/\\1'.format(
111
+            ('^/zuul3/(.*)$', 'http://localhost:{}/\\1'.format(
112 112
                 self.web.port)),
113 113
         ]
114 114
         self.proxy = self.useFixture(WebProxyFixture(rules))
115 115
         self.port = self.proxy.port
116 116
 
117 117
     def test_status_page(self):
118
-        self._crawl('/zuul/')
118
+        self._crawl('/zuul3/t/tenant-one/status.html')

+ 2
- 4
tools/pip.sh View File

@@ -32,9 +32,7 @@ then
32 32
 fi
33 33
 if [[ ! -f zuul/web/static/status.html ]]
34 34
 then
35
-    pushd web/
36
-      yarn install
37
-      yarn build
38
-    popd
35
+    yarn install
36
+    npm run build:dev
39 37
 fi
40 38
 pip install $*

+ 17
- 0
tsconfig.json View File

@@ -0,0 +1,17 @@
1
+{
2
+  "compilerOptions": {
3
+    "sourceMap": true,
4
+    "moduleResolution": "node",
5
+    "outDir": "./zuul/web/static/",
6
+    "noImplicitAny": false,
7
+    "target": "es6",
8
+    "lib": [ "es2015", "dom" ],
9
+    "emitDecoratorMetadata": true,
10
+    "experimentalDecorators": true,
11
+    "allowJs": false
12
+  },
13
+  "include": [
14
+    "webpack.config.ts",
15
+    "web/**/*.ts"
16
+  ]
17
+}

+ 7
- 0
tslint.json View File

@@ -0,0 +1,7 @@
1
+{
2
+  "extends": ["tslint-angular"],
3
+  "rules": {
4
+    "array-type": [true, "array-simple"],
5
+    "semicolon": [true, "never"]
6
+  }
7
+}

+ 0
- 23
web/.eslintrc View File

@@ -1,23 +0,0 @@
1
-parser: babel-eslint
2
-plugins:
3
-  - standard
4
-  - jest
5
-rules:
6
-  no-console: off
7
-  semi: [error, never]
8
-  quotes: [error, single]
9
-  lines-between-class-members: error
10
-  react/prop-types: error
11
-  react/jsx-key: error
12
-  react/no-did-mount-set-state: error
13
-  react/no-did-update-set-state: error
14
-  react/no-deprecated: error
15
-extends:
16
-  - eslint:recommended
17
-  - plugin:react/recommended
18
-settings:
19
-  react:
20
-    version: 16.4
21
-env:
22
-  jest/globals: true
23
-  browser: true

+ 1
- 0
web/.gitignore View File

@@ -0,0 +1 @@
1
+public_html/lib

+ 1
- 0
web/.jshintignore View File

@@ -0,0 +1 @@
1
+public_html/lib

+ 21
- 0
web/.jshintrc View File

@@ -0,0 +1,21 @@
1
+{
2
+    "bitwise": true,
3
+    "eqeqeq": true,
4
+    "forin": true,
5
+    "latedef": true,
6
+    "newcap": true,
7
+    "noarg": true,
8
+    "noempty": true,
9
+    "nonew": true,
10
+    "undef": true,
11
+    "unused": true,
12
+
13
+    "strict": false,
14
+    "laxbreak": true,
15
+    "browser": true,
16
+
17
+    "predef": [
18
+        "jQuery",
19
+        "zuul"
20
+    ]
21
+}

+ 84
- 0
web/app-routing.module.ts View File

@@ -0,0 +1,84 @@
1
+// Routing information for Zuul dashboard pages
2
+//
3
+// Copyright 2018 Red Hat, Inc
4
+//
5
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+// not use this file except in compliance with the License. You may obtain
7
+// a copy of the License at
8
+//
9
+//      http://www.apache.org/licenses/LICENSE-2.0
10
+//
11
+// Unless required by applicable law or agreed to in writing, software
12
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+// License for the specific language governing permissions and limitations
15
+// under the License.
16
+
17
+import { NgModule, isDevMode } from '@angular/core'
18
+import { RouterModule, Routes } from '@angular/router'
19
+
20
+import BuildsComponent from './builds/builds.component'
21
+import JobsComponent from './jobs/jobs.component'
22
+import StatusComponent from './status/status.component'
23
+import StreamComponent from './stream/stream.component'
24
+import TenantsComponent from './tenants/tenants.component'
25
+
26
+// Have all routes go to builds.html for now
27
+const appRoutes: Routes = [
28
+  {
29
+    path: 't/:tenant/builds.html',
30
+    component: BuildsComponent
31
+  },
32
+  {
33
+    path: 'builds.html',
34
+    component: BuildsComponent
35
+  },
36
+  {
37
+    path: 't/:tenant/status.html',
38
+    component: StatusComponent
39
+  },
40
+  {
41
+    path: 'status.html',
42
+    component: StatusComponent
43
+  },
44
+  {
45
+    path: 't/:tenant/jobs.html',
46
+    component: JobsComponent
47
+  },
48
+  {
49
+    path: 'jobs.html',
50
+    component: JobsComponent
51
+  },
52
+  {
53
+    path: 'stream.html',
54
+    component: StreamComponent
55
+  },
56
+  {
57
+    path: 't/:tenant/stream.html',
58
+    component: StreamComponent
59
+  },
60
+  {
61
+    path: 'tenants.html',
62
+    component: TenantsComponent
63
+  },
64
+  {
65
+    path: 't/tenants.html',
66
+    component: TenantsComponent
67
+  },
68
+  {
69
+    path: '**',
70
+    component: StatusComponent
71
+  }
72
+]
73
+
74
+@NgModule({
75
+  imports: [
76
+    RouterModule.forRoot(
77
+      appRoutes,
78
+      // Enable router tracing in devel mode. This prints router decisions
79
+      // to the console.log.
80
+      { enableTracing: isDevMode() }
81
+    )],
82
+  exports: [RouterModule]
83
+})
84
+export class AppRoutingModule { }

+ 28
- 0
web/app.component.ts View File

@@ -0,0 +1,28 @@
1
+// Main dashboard component
2
+//
3
+// Copyright 2018 Red Hat, Inc
4
+//
5
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+// not use this file except in compliance with the License. You may obtain
7
+// a copy of the License at
8
+//
9
+//      http://www.apache.org/licenses/LICENSE-2.0
10
+//
11
+// Unless required by applicable law or agreed to in writing, software
12
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+// License for the specific language governing permissions and limitations
15
+// under the License.
16
+
17
+import { Component } from '@angular/core'
18
+
19
+@Component({
20
+    selector: 'zuul-dashboard',
21
+    template: `
22
+    <navigation></navigation>
23
+    <div class="container-fluid">
24
+      <router-outlet></router-outlet>
25
+    </div>
26
+    `
27
+})
28
+export class AppComponent {}

+ 73
- 0
web/app.module.ts View File

@@ -0,0 +1,73 @@
1
+// Entrypoint for Zuul dashboard pages
2
+//
3
+// Copyright 2018 Red Hat, Inc
4
+//
5
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+// not use this file except in compliance with the License. You may obtain
7
+// a copy of the License at
8
+//
9
+//      http://www.apache.org/licenses/LICENSE-2.0
10
+//
11
+// Unless required by applicable law or agreed to in writing, software
12
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+// License for the specific language governing permissions and limitations
15
+// under the License.
16
+
17
+import './styles/zuul.css'
18
+
19
+import { APP_BASE_HREF } from '@angular/common'
20
+import { NgModule } from '@angular/core'
21
+import { BrowserModule } from '@angular/platform-browser'
22
+import { HttpClientModule } from '@angular/common/http'
23
+import { FormsModule } from '@angular/forms'
24
+
25
+import { CoreModule } from './core/core.module'
26
+
27
+import { AppRoutingModule } from './app-routing.module'
28
+import { AppComponent } from './app.component'
29
+import { getAppBaseHref } from './zuul/zuul.service'
30
+
31
+import BuildsComponent from './builds/builds.component'
32
+import NavigationComponent from './navigation/navigation.component'
33
+import JobsComponent from './jobs/jobs.component'
34
+import StatusComponent from './status/status.component'
35
+import StreamComponent from './stream/stream.component'
36
+import TenantsComponent from './tenants/tenants.component'
37
+import ZuulService from './zuul/zuul.service'
38
+
39
+
40
+@NgModule({
41
+  imports: [
42
+    BrowserModule,
43
+    HttpClientModule,
44
+    FormsModule,
45
+    CoreModule.forRoot({}),
46
+    AppRoutingModule,
47
+  ],
48
+  declarations: [
49
+    AppComponent,
50
+    BuildsComponent,
51
+    NavigationComponent,
52
+    JobsComponent,
53
+    StatusComponent,
54
+    StreamComponent,
55
+    TenantsComponent
56
+  ],
57
+  entryComponents: [
58
+    BuildsComponent,
59
+    NavigationComponent,
60
+    JobsComponent,
61
+    StatusComponent,
62
+    StreamComponent,
63
+    TenantsComponent
64
+  ],
65
+  providers: [
66
+    ZuulService,
67
+    {provide: APP_BASE_HREF, useValue: getAppBaseHref()}
68
+  ],
69
+  bootstrap: [
70
+    AppComponent
71
+  ]
72
+})
73
+export class AppModule { }

+ 0
- 1
web/build View File

@@ -1 +0,0 @@
1
-../zuul/web/static/

+ 28
- 0
web/builds/build.ts View File

@@ -0,0 +1,28 @@
1
+// Copyright 2017 Red Hat
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+export default class Build {
16
+
17
+  constructor(
18
+    public job_name: string,
19
+    public result: string,
20
+    public project: string,
21
+    public pipeline: string,
22
+    public ref_url: string,
23
+    public duration: number,
24
+    public start_time: string,
25
+    public log_url?: string,
26
+  ) {}
27
+
28
+}

+ 67
- 0
web/builds/builds.component.html View File

@@ -0,0 +1,67 @@
1
+<!--
2
+Copyright 2017 Red Hat
3
+
4
+Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+not use this file except in compliance with the License. You may obtain
6
+a copy of the License at
7
+
8
+     http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+Unless required by applicable law or agreed to in writing, software
11
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+License for the specific language governing permissions and limitations
14
+under the License.
15
+-->
16
+<div class="container-fluid">
17
+  <span style="float: right; margin-top: 7px;">
18
+    <form class="form-inline" #buildsForm="ngForm">
19
+      <div class="form-group">
20
+        <label for="pipeline">Pipeline:</label>
21
+        <input class="form-control" id="pipeline"
22
+               [(ngModel)]="pipeline" name="pipeline" />
23
+      </div>
24
+      <div class="form-group">
25
+        <label for="job_name">Job:</label>
26
+        <input class="form-control" id="job_name"
27
+               [(ngModel)]="job_name" name="job_name" />
28
+      </div>
29
+      <div class="form-group">
30
+        <label for="project">Project:</label>
31
+        <input class="form-control" id="project"
32
+               [(ngModel)]="project" name="project">
33
+      </div>
34
+      <button type="submit" class="btn" (click)='buildsFetch()'>
35
+          Refresh
36
+      </button>
37
+    </form>
38
+  </span>
39
+</div>
40
+<table class="table table-hover table-condensed">
41
+  <thead>
42
+    <tr>
43
+      <th>Job</th>
44
+      <th>Project</th>
45
+      <th>Branch</th>
46
+      <th>Pipeline</th>
47
+      <th>Change</th>
48
+      <th>Duration</th>
49
+      <th>Log url</th>
50
+      <th>Start time</th>
51
+      <th>Result</th>
52
+    </tr>
53
+  </thead>
54
+  <tbody>
55
+    <tr *ngFor="let build of builds" [class]="getRowClass(build)">
56
+      <td>{{ build.job_name }}</td>
57
+      <td>{{ build.project }}</td>
58
+      <td>{{ build.branch }}</td>
59
+      <td>{{ build.pipeline }}</td>
60
+      <td><a href="{{ build.ref_url }}" target="_self">change</a></td>
61
+      <td>{{ build.duration }} seconds</td>
62
+      <td><a *ngIf="build.log_url" href="{{ build.log_url }}" target="_self">logs</a></td>
63
+      <td>{{ build.start_time }}</td>
64
+      <td>{{ build.result }}</td>
65
+    </tr>
66
+  </tbody>
67
+</table>

+ 78
- 0
web/builds/builds.component.ts View File

@@ -0,0 +1,78 @@
1
+// Copyright 2017 Red Hat
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+import { Component, OnInit } from '@angular/core'
16
+import { ActivatedRoute } from '@angular/router'
17
+import { HttpClient, HttpParams } from '@angular/common/http'
18
+import { Observable } from 'rxjs/Observable'
19
+import 'rxjs/add/operator/map'
20
+
21
+import ZuulService from '../zuul/zuul.service'
22
+import Build from './build'
23
+
24
+
25
+@Component({
26
+  template: require('./builds.component.html')
27
+})
28
+export default class BuildsComponent implements OnInit {
29
+  builds: Build[]
30
+  pipeline: string
31
+  job_name: string
32
+  project: string
33
+
34
+  constructor(
35
+    private http: HttpClient, private route: ActivatedRoute,
36
+    private zuul: ZuulService
37
+  ) {}
38
+
39
+  async ngOnInit() {
40
+    await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
41
+
42
+    this.pipeline = this.route.snapshot.queryParamMap.get('pipeline')
43
+    this.job_name = this.route.snapshot.queryParamMap.get('job_name')
44
+    this.project = this.route.snapshot.queryParamMap.get('project')
45
+
46
+    this.buildsFetch()
47
+  }
48
+
49
+  buildsFetch(): void  {
50
+    let params = new HttpParams()
51
+    if (this.pipeline) { params = params.set('pipeline', this.pipeline) }
52
+    if (this.job_name) { params = params.set('job_name', this.job_name) }
53
+    if (this.project) { params = params.set('project', this.project) }
54
+
55
+    const remoteLocation = this.zuul.getSourceUrl('builds')
56
+    if (remoteLocation) {
57
+      this.http.get<Build[]>(remoteLocation, {params: params})
58
+        .subscribe(builds => {
59
+          for (const build of builds) {
60
+            /* Fix incorect url for post_failure job */
61
+            /* TODO(mordred) Maybe let's fix this server side? */
62
+            if (build.log_url === build.job_name) {
63
+              build.log_url = undefined
64
+            }
65
+          }
66
+          this.builds = builds
67
+        })
68
+    }
69
+  }
70
+
71
+  getRowClass(build: Build): string {
72
+    if (build.result === 'SUCCESS') {
73
+      return 'success'
74
+    } else {
75
+      return 'warning'
76
+    }
77
+  }
78
+}

+ 24
- 0
web/config/main.ejs View File

@@ -0,0 +1,24 @@
1
+<!DOCTYPE html>
2
+<!--
3
+Copyright 2017 Red Hat
4
+
5
+Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+not use this file except in compliance with the License. You may obtain
7
+a copy of the License at
8
+
9
+     http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+Unless required by applicable law or agreed to in writing, software
12
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+License for the specific language governing permissions and limitations
15
+under the License.
16
+-->
17
+<html>
18
+<head>
19
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
20
+  <title id='pagetitle'><%= htmlWebpackPlugin.options.title %></title>
21
+</head>
22
+<body>
23
+  <zuul-dashboard></zuul-dashboard>
24
+</body></html>

+ 178
- 0
web/config/status-basic.json View File

@@ -0,0 +1,178 @@
1
+{
2
+    "last_reconfigured": 1389381756000,
3
+    "message": "Example info message",
4
+    "pipelines": [
5
+        {
6
+            "name": "test",
7
+            "description": "Lint and unit tests",
8
+            "change_queues": [
9
+                {
10
+                    "name": "some-jobs@worker301.ci-example.org",
11
+                    "heads": [
12
+                        [
13
+                            {
14
+                                "id": "10101,1",
15
+                                "url": "#!/review.example.org/r/10101",
16
+                                "project": "openstack/infra/zuul",
17
+                                "jobs": [
18
+                                    {
19
+                                        "name": "zuul-merge",
20
+                                        "url": "#!/jenkins.example.org/job/zuul-merge/201",
21
+                                        "result": "SUCCESS",
22
+                                        "voting": true
23
+                                    },
24
+                                    {
25
+                                        "name": "zuul-lint",
26
+                                        "url": "#!/jenkins.example.org/job/zuul-lint/201",
27
+                                        "result": "SUCCESS",
28
+                                        "voting": true
29
+                                    },
30
+                                    {
31
+                                        "name": "zuul-test",
32
+                                        "url": "#!/jenkins.example.org/job/zuul-test/201",
33
+                                        "result": "SUCCESS",
34
+                                        "voting": true
35
+                                    }
36
+                                ]
37
+                            }
38
+                        ],
39
+                        [
40
+                            {
41
+                                "id": "10103,1",
42
+                                "url": "#!/review.example.org/r/10103",
43
+                                "project": "google/gerrit",
44
+                                "jobs": [
45
+                                    {
46
+                                        "name": "gerrit-merge",
47
+                                        "url": "#!/jenkins.example.org/job/gerrit-merge/203",
48
+                                        "result": "SUCCESS",
49
+                                        "voting": false
50
+                                    }
51
+                                ]
52
+                            }
53
+                        ]
54
+                    ]
55
+                },
56
+                {
57
+                    "name": "other-jobs@worker301.ci-example.org",
58
+                    "heads": [
59
+                        [
60
+                            {
61
+                                "id": "10102,1",
62
+                                "url": "#!/review.example.org/r/10102",
63
+                                "project": "google/gerrit",
64
+                                "jobs": [
65
+                                    {
66
+                                        "name": "gerrit-merge",
67
+                                        "url": "#!/jenkins.example.org/job/gerrit-merge/202",
68
+                                        "result": "UNSTABLE",
69
+                                        "voting": false
70
+                                    }
71
+                                ]
72
+                            }
73
+                        ],
74
+                        [
75
+                            {
76
+                                "id": "10104,1",
77
+                                "url": "#!/review.example.org/r/10104",
78
+                                "project": "openstack/infra/zuul",
79
+                                "jobs": [
80
+                                    {
81
+                                        "name": "zuul-merge",
82
+                                        "url": "#!/jenkins.example.org/job/zuul-merge/204",
83
+                                        "result": "SUCCESS",
84
+                                        "voting": true
85
+                                    },
86
+                                    {
87
+                                        "name": "zuul-lint",
88
+                                        "url": "#!/jenkins.example.org/job/zuul-lint/204",
89
+                                        "result": "FAILURE",
90
+                                        "voting": true
91
+                                    },
92
+                                    {
93
+                                        "name": "zuul-test",
94
+                                        "url": "#!/jenkins.example.org/job/zuul-test/204",
95
+                                        "result": null,
96
+                                        "voting": true
97
+                                    }
98
+                                ]
99
+                            }
100
+                        ]
101
+                    ]
102
+                }
103
+            ]
104
+        },
105
+        {
106
+            "name": "gate-and-submit",
107
+            "change_queues": []
108
+        },
109
+        {
110
+            "name": "postmerge",
111
+            "change_queues": [
112
+                {
113
+                    "name": "some-jobs@worker301.ci-example.org",
114
+                    "heads": [
115
+                        [
116
+                            {
117
+                                "id": "7f1d65cb0f663c907698f915da01c008c7ef4748",
118
+                                "url": "#!/review.example.org/r/10100",
119
+                                "project": "openstack/infra/zuul",
120
+                                "jobs": [
121
+                                    {
122
+                                        "name": "zuul-lint",
123
+                                        "url": "#!/jenkins.example.org/job/zuul-lint/200",
124
+                                        "result": "SUCCESS",
125
+                                        "voting": true
126
+                                    },
127
+                                    {
128
+                                        "name": "zuul-test",
129
+                                        "url": "#!/jenkins.example.org/job/zuul-test/200",
130
+                                        "result": "FAILURE",
131
+                                        "voting": true
132
+                                    },
133
+                                    {
134
+                                        "name": "zuul-regression-python2",
135
+                                        "url": "#!/jenkins.example.org/job/zuul-regression-python2/200",
136
+                                        "result": "SUCCESS",
137
+                                        "voting": true
138
+                                    },
139
+                                    {
140
+                                        "name": "zuul-regression-python3",
141
+                                        "url": "#!/jenkins.example.org/job/zuul-regression-python3/200",
142
+                                        "result": "FAILURE",
143
+                                        "voting": true
144
+                                    },
145
+                                    {
146
+                                        "name": "zuul-performance-python2",
147
+                                        "url": null,
148
+                                        "result": null,
149
+                                        "voting": true
150
+                                    },
151
+                                    {
152
+                                        "name": "zuul-performance-python3",
153
+                                        "url": null,
154
+                                        "result": null,
155
+                                        "voting": true
156
+                                    },
157
+                                    {
158
+                                        "name": "zuul-docs-publish",
159
+                                        "url": null,
160
+                                        "result": null,
161
+                                        "voting": true
162
+                                    }
163
+                                ]
164
+                            }
165
+                        ]
166
+                    ]
167
+                }
168
+            ]
169
+        }
170
+    ],
171
+    "trigger_event_queue": {
172
+        "length": 0
173
+    },
174
+    "result_event_queue": {
175
+        "length": 0
176
+    },
177
+    "zuul_version": "2.0.0.19"
178
+}

+ 312
- 0
web/config/status-openstack.json View File

@@ -0,0 +1,312 @@
1
+{
2
+    "last_reconfigured": 1389381756000,
3
+    "pipelines": [
4
+        {
5
+            "name": "check",
6
+            "description": "Newly uploaded patchsets enter this pipeline to receive an initial +/-1 Verified vote from Jenkins.",
7
+            "change_queues": [
8
+                {
9
+                    "heads": [],
10
+                    "name": "stackforge/tripleo-image-elements"
11
+                }
12
+            ]
13
+        },
14
+        {
15
+            "description": "Changes that have been approved by core developers are enqueued in order in this pipeline, and .",
16
+            "change_queues": [
17
+                {
18
+                    "heads": [],
19
+                    "name": "openstack-detackforge/reddwarf-integration"
20
+                },
21
+                {
22
+                    "heads": [],
23
+                    "name": "stackforge/moniker"
24
+                },
25
+                {
26
+                    "heads": [
27
+                        [
28
+                            {
29
+                                "url": "https://review.openstack.org/26292",
30
+                                "project": "openstack/quantum",
31
+                                "jobs": [
32
+                                    {
33
+                                        "url": "https://jenkins.openstack.org/job/gate-quantum-docs/5501/consoleFull",
34
+                                        "voting": true,
35
+                                        "result": "SUCCESS",
36
+                                        "name": "gate-quantum-docs"
37
+                                    },
38
+                                    {
39
+                                        "url": "https://jenkins.openstack.org/job/gate-quantum-pep8/6254/consoleFull",
40
+                                        "voting": true,
41
+                                        "result": "SUCCESS",
42
+                                        "name": "gate-quantum-pep8"
43
+                                    },
44
+                                    {
45
+                                        "url": "https://jenkins.openstack.org/job/gate-quantum-python26/5876/",
46
+                                        "voting": true,
47
+                                        "result": null,
48
+                                        "name": "gate-quantum-python26"
49
+                                    },
50
+                                    {
51
+                                        "url": "https://jenkins.openstack.org/job/gate-quantum-python27/5887/",
52
+                                        "voting": true,
53
+                                        "result": null,
54
+                                        "name": "gate-quantum-python27"
55
+                                    },
56
+                                    {
57
+                                        "url": "https://jenkins.openstack.org/job/gate-tempest-devstack-vm-quantum/17548/",
58
+                                        "voting": true,
59
+                                        "result": null,
60
+                                        "name": "gate-tempest-devstack-vm-quantum"
61
+                                    }
62
+                                ],
63
+                                "id": "26292,1"
64
+                            }
65
+                        ]
66
+                    ],
67
+                    "name": "openstack-dev/devstack, openstack-infra/devstack-gate, openstack/cinder, openstack/glance, openstack/horizon, openstack/keystone, openstack/nova, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-quantumclient, openstack/quantum, openstack/swift, openstack/tempest, z/tempest"
68
+                },
69
+                {
70
+                    "heads": [],
71
+                    "name": "openstack/ceilometer"
72
+                },
73
+                {
74
+                    "heads": [],
75
+                    "name": "stackforge/puppet-openstack"
76
+                },
77
+                {
78
+                    "heads": [],
79
+                    "name": "stackforge/puppet-cinder"
80
+                },
81
+                {
82
+                    "heads": [],
83
+                    "name": "stackforge/marconi"
84
+                },
85
+                {
86
+                    "heads": [],
87
+                    "name": "openstack-infra/config"
88
+                },
89
+                {
90
+                    "heads": [],
91
+                    "name": "stackforge/tripleo-image-elements"
92
+                },
93
+                {
94
+                    "heads": [],
95
+                    "name": "stackforge/kwapi"
96
+                },
97
+                {
98
+                    "heads": [],
99
+                    "name": "stackforge/python-reddwarfclient"
100
+                },
101
+                {
102
+                    "heads": [],
103
+                    "name": "stackforge/python-savannaclient"
104
+                },
105
+                {
106
+                    "heads": [],
107
+                    "name": "stackforge/python-monikerclient"
108
+                },
109
+                {
110
+                    "heads": [],
111
+                    "name": "stackforge/packstack"
112
+                },
113
+                {
114
+                    "heads": [],
115
+                    "name": "openstack/oslo.config"
116
+                },
117
+                {
118
+                    "heads": [],
119
+                    "name": "openstack-infra/jenkins-job-builder"
120
+                },
121
+                {
122
+                    "heads": [],
123
+                    "name": "stackforge/puppet-horizon"
124
+                },
125
+                {
126
+                    "heads": [],
127
+                    "name": "openstack/heat-cfntools"
128
+                },
129
+                {
130
+                    "heads": [],
131
+                    "name": "openstack/oslo-incubator"
132
+                },
133
+                {
134
+                    "heads": [],
135
+                    "name": "stackforge/os-config-applier"
136
+                },
137
+                {
138
+                    "heads": [],
139
+                    "name": "openstack/requirements"
140
+                },
141
+                {
142
+                    "heads": [],
143
+                    "name": "stackforge/puppet-glance"
144
+                },
145
+                {
146
+                    "heads": [],
147
+                    "name": "openstack-infra/gearman-plugin"
148
+                },
149
+                {
150
+                    "heads": [],
151
+                    "name": "stackforge/puppet-keystone"
152
+                },
153
+                {
154
+                    "heads": [],
155
+                    "name": "stackforge/puppet-nova"
156
+                },
157
+                {
158
+                    "heads": [],
159
+                    "name": "stackforge/climate"
160
+                },
161
+                {
162
+                    "heads": [],
163
+                    "name": "openstack/python-swiftclient"
164
+                },
165
+                {
166
+                    "heads": [],
167
+                    "name": "openstack/python-ceilometerclient"
168
+                },
169
+                {
170
+                    "heads": [],
171
+                    "name": "openstack-infra/git-review"
172
+                },
173
+                {
174
+                    "heads": [],
175
+                    "name": "stackforge/bufunfa"
176
+                },
177
+                {
178
+                    "heads": [],
179
+                    "name": "stackforge/puppet-swift"
180
+                },
181
+                {
182
+                    "heads": [],
183
+                    "name": "openstack-infra/statusbot"
184
+                },
185
+                {
186
+                    "heads": [],
187
+                    "name": "openstack/openstack-planet"
188
+                },
189
+                {
190
+                    "heads": [],
191
+                    "name": "openstack/python-openstackclient"
192
+                },
193
+                {
194
+                    "heads": [],
195
+                    "name": "stackforge/diskimage-builder"
196
+                },
197
+                {
198
+                    "heads": [],
199
+                    "name": "openstack-infra/gerritlib"
200
+                },
201
+                {
202
+                    "heads": [],
203
+                    "name": "openstack-infra/zuul"
204
+                },
205
+                {
206
+                    "heads": [],
207
+                    "name": "stackforge/reddwarf"
208
+                },
209
+                {
210
+                    "heads": [],
211
+                    "name": "openstack-dev/hacking"
212
+                },
213
+                {
214
+                    "heads": [],
215
+                    "name": "openstack/python-heatclient"
216
+                },
217
+                {
218
+                    "heads": [],
219
+                    "name": "stackforge/python-libraclient"
220
+                },
221
+                {
222
+                    "heads": [],
223
+                    "name": "openstack-infra/reviewday"
224
+                },
225
+                {
226
+                    "heads": [],
227
+                    "name": "openstack-infra/jeepyb"
228
+                },
229
+                {
230
+                    "heads": [],
231
+                    "name": "openstack/heat"
232
+                },
233
+                {
234
+                    "heads": [],
235
+                    "name": "stackforge/libra"
236
+                },
237
+                {
238
+                    "heads": [],
239
+                    "name": "openstack-infra/gerrit"
240
+                },
241
+                {
242
+                    "heads": [],
243
+                    "name": "stackforge/healthnmon"
244
+                },
245
+                {
246
+                    "heads": [],
247
+                    "name": "openstack-infra/gerritbot"
248
+                },
249
+                {
250
+                    "heads": [],
251
+                    "name": "openstack-dev/pbr"
252
+                },
253
+                {
254
+                    "heads": [],
255
+                    "name": "stackforge/savanna"
256
+                },
257
+                {
258
+                    "heads": [],
259
+                    "name": "openstack/openstack-manuals"
260
+                }
261
+            ],
262
+            "name": "gate"
263
+        },
264
+        {
265
+            "description": "This pipeline runs jobs that operate after each change is merged.",
266
+            "change_queues": [
267
+                {
268
+                    "heads": [],
269
+                    "name": "openstack-dev/hacking, openstack-dev/openstack-qa, openstack-dev/pbr, openstack-infra/config, openstack-infra/gearman-plugin, openstack-infra/gerrit, openstack-infra/gerritbot, openstack-infra/git-review, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/api-site, openstack/ceilometer, openstack/cinder, openstack/compute-api, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/identity-api, openstack/image-api, openstack/keystone, openstack/netconn-api, openstack/nova, openstack/object-api, openstack/openstack-manuals, openstack/oslo-incubator, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/requirements, openstack/swift, openstack/volume-api, stackforge/bufunfa, stackforge/diskimage-builder, stackforge/moniker, stackforge/os-config-applier, stackforge/python-monikerclient, stackforge/python-savannaclient, stackforge/reddwarf, stackforge/savanna, stackforge/tripleo-image-elements"
270
+                }
271
+            ],
272
+            "name": "post"
273
+        },
274
+        {
275
+            "description": "This pipeline runs jobs on projects in response to pre-release tags.",
276
+            "change_queues": [
277
+                {
278
+                    "heads": [],
279
+                    "name": "openstack-dev/hacking, openstack-dev/pbr, openstack-infra/gerritbot, openstack-infra/gerritlib, openstack-infra/git-review, openstack-infra/jeepyb, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/ceilometer, openstack/cinder, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/keystone, openstack/nova, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/swift, stackforge/moniker, stackforge/python-monikerclient, stackforge/python-reddwarfclient, stackforge/python-savannaclient, stackforge/savanna"
280
+                }
281
+            ],
282
+            "name": "pre-release"
283
+        },
284
+        {
285
+            "description": "When a commit is tagged as a release, this pipeline runs jobs that publish archives and documentation.",
286
+            "change_queues": [
287
+                {
288
+                    "heads": [],
289
+                    "name": "openstack-dev/hacking, openstack-dev/openstack-qa, openstack-dev/pbr, openstack-infra/gerritbot, openstack-infra/gerritlib, openstack-infra/git-review, openstack-infra/jeepyb, openstack-infra/jenkins-job-builder, openstack-infra/nose-html-output, openstack-infra/reviewday, openstack-infra/statusbot, openstack-infra/zuul, openstack/ceilometer, openstack/cinder, openstack/glance, openstack/heat, openstack/heat-cfntools, openstack/horizon, openstack/keystone, openstack/nova, openstack/oslo-incubator, openstack/oslo.config, openstack/python-ceilometerclient, openstack/python-cinderclient, openstack/python-glanceclient, openstack/python-heatclient, openstack/python-keystoneclient, openstack/python-novaclient, openstack/python-openstackclient, openstack/python-quantumclient, openstack/python-swiftclient, openstack/quantum, openstack/swift, stackforge/moniker, stackforge/python-monikerclient, stackforge/python-reddwarfclient, stackforge/python-savannaclient, stackforge/savanna"
290
+                }
291
+            ],
292
+            "name": "release"
293
+        },
294
+        {
295
+            "description": "This pipeline is used for silently testing new jobs.",
296
+            "change_queues": [
297
+                {
298
+                    "heads": [],
299
+                    "name": ""
300
+                }
301
+            ],
302
+            "name": "silent"
303
+        }
304
+    ],
305
+    "trigger_event_queue": {
306
+        "length": 0
307
+    },
308
+    "result_event_queue": {
309
+        "length": 0
310
+    },
311
+    "zuul_version": "2.0.0.19"
312
+}

+ 1
- 0
web/config/status-tree.json
File diff suppressed because it is too large
View File


+ 147
- 0
web/config/webpack.common.js View File

@@ -0,0 +1,147 @@
1
+const path = require('path')
2
+const webpack = require('webpack')
3
+const HtmlWebpackPlugin = require('html-webpack-plugin')
4
+const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
5
+
6
+module.exports = {
7
+  entry: './web/main.ts',
8
+  resolve: {
9
+    extensions: [ '.tsx', '.ts', '.js' ]
10
+  },
11
+  output: {
12
+    filename: '[name].js',
13
+    // path.resolve(__dirname winds up relative to the config dir
14
+    path: path.resolve(__dirname, '../../zuul/web/static'),
15
+    publicPath: ''
16
+  },
17
+  // Some folks prefer "cheaper" source-map for dev and one that is more
18
+  // expensive to build for prod. Debugging without the full source-map sucks,
19
+  // so define it here in common.
20
+  devtool: 'source-map',
21
+  optimization: {
22
+    runtimeChunk: true,
23
+    splitChunks: {
24
+      cacheGroups: {
25
+        commons: {
26
+          test: /node_modules/,
27
+          name: 'vendor',
28
+          chunks: 'all'
29
+        }
30
+      }
31
+    }
32
+  },
33
+  plugins: [
34
+    new ForkTsCheckerWebpackPlugin(),
35
+    new webpack.ProvidePlugin({
36
+        $: 'jquery/dist/jquery',
37
+        jQuery: 'jquery/dist/jquery',
38
+    }),
39
+    // Each of the entries below lists a specific 'chunk' which is one of the
40
+    // entry items from above. We can collapse this to just do one single
41
+    // output file.
42
+    new HtmlWebpackPlugin({
43
+      filename: 'index.html',
44
+      template: 'web/config/main.ejs',
45
+      title: 'Zuul Status'
46
+    }),
47
+    new HtmlWebpackPlugin({
48
+      filename: 'status.html',
49
+      template: 'web/config/main.ejs',
50
+      title: 'Zuul Status'
51
+    }),
52
+    new HtmlWebpackPlugin({
53
+      title: 'Zuul Builds',
54
+      template: 'web/config/main.ejs',
55
+      filename: 'builds.html'
56
+    }),
57
+    new HtmlWebpackPlugin({
58
+      title: 'Zuul Jobs',
59
+      template: 'web/config/main.ejs',
60
+      filename: 'jobs.html'
61
+    }),
62
+    new HtmlWebpackPlugin({
63
+      title: 'Zuul Tenants',
64
+      template: 'web/config/main.ejs',
65
+      filename: 'tenants.html'
66
+    }),
67
+    new HtmlWebpackPlugin({
68
+      title: 'Zuul Console Stream',
69
+      template: 'web/config/main.ejs',
70
+      filename: 'stream.html'
71
+    })
72
+  ],
73
+  module: {
74
+    rules: [
75
+      {
76
+        test: /\.ts$/,
77
+        exclude: /node_modules/,
78
+        use: {
79
+          loader: 'ts-loader',
80
+          options: {
81
+            // disable type checker - we will use it in fork plugin
82
+            transpileOnly: true
83
+          }
84
+        }
85
+      },
86
+      {
87
+        test: /\.js$/,
88
+        exclude: /node_modules/,
89
+        use: [
90
+          'babel-loader'
91
+        ]
92
+      },
93
+      {
94
+        test: /.css$/,
95
+        use: [
96
+          'style-loader',
97
+          'css-loader'
98
+        ]
99
+      },
100
+      {
101
+        test: /\.(png|svg|jpg|gif)$/,
102
+        use: ['file-loader'],
103
+      },
104
+      // The majority of the rules below are all about getting bootstrap copied
105
+      // appropriately.
106
+      {
107
+        test: /\.woff(2)?(\?v=\d+\.\d+\.\d+)?$/,
108
+        use: {
109
+          loader: 'url-loader',
110
+          options: {
111
+            limit: 10000,
112
+            mimetype: 'application/font-woff'
113
+          }
114
+        }
115
+      },
116
+      {
117
+        test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
118
+        use: {
119
+          loader: 'url-loader',
120
+          options: {
121
+            limit: 10000,
122
+            mimetype: 'application/octet-stream'
123
+          }
124
+        }
125
+      },
126
+      {
127
+        test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
128
+        use: ['file-loader'],
129
+      },
130
+      {
131
+        test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
132
+        use: {
133
+          loader: 'url-loader',
134
+          options: {
135
+            limit: 10000,
136
+            mimetype: 'image/svg+xml'
137
+          }
138
+        }
139
+      },
140
+      {
141
+        test: /\.html$/,
142
+        use: ['html-loader'],
143
+        exclude: /node_modules/
144
+      }
145
+    ]
146
+  }
147
+};

+ 23
- 0
web/config/webpack.dev.js View File

@@ -0,0 +1,23 @@
1
+const path = require('path');
2
+const webpack = require('webpack');
3
+const Merge = require('webpack-merge');
4
+const CommonConfig = require('./webpack.common.js');
5
+
6
+module.exports = Merge(CommonConfig, {
7
+  mode: 'development',
8
+  // Enable Hot Module Replacement for devServer
9
+  devServer: {
10
+    hot: true,
11
+    contentBase: path.resolve(__dirname, './zuul/web/static'),
12
+    publicPath: '/'
13
+  },
14
+  plugins: [
15
+    new webpack.HotModuleReplacementPlugin(),
16
+    // We only need to bundle the demo files when we're running locally
17
+    new webpack.ProvidePlugin({
18
+        DemoStatusBasic: '../config/status-basic.json',
19
+        DemoStatusOpenStack: '../config/status-openstack.json',
20
+        DemoStatusTree: '../config/status-tree.json'
21
+    }),
22
+  ]
23
+})

+ 47
- 0
web/config/webpack.lint.js View File

@@ -0,0 +1,47 @@
1
+const path = require('path');
2
+const webpack = require('webpack');
3
+const Merge = require('webpack-merge');
4
+const CommonConfig = require('./webpack.common.js');
5
+const BundleAnalyzer = require('webpack-bundle-analyzer');
6
+
7
+module.exports = Merge(CommonConfig, {
8
+  mode: 'development',
9
+  module: {
10
+    rules: [
11
+      {
12
+        enforce: 'pre',
13
+        test: /\.ts$/,
14
+        exclude: /node_modules/,
15
+        use: [
16
+          {
17
+            loader: 'tslint-loader',
18
+            options: {
19
+              emitErrors: true,
20
+              typeCheck: false,
21
+
22
+            }
23
+          }
24
+        ]
25
+      },
26
+      {
27
+        enforce: 'pre',
28
+        test: /\.js$/,
29
+        use: [
30
+          'babel-loader',
31
+          'eslint-loader'
32
+        ],
33
+        exclude: /node_modules/,
34
+      }
35
+    ]
36
+  },
37
+  plugins: [
38
+    new webpack.HotModuleReplacementPlugin(),
39
+    new BundleAnalyzer.BundleAnalyzerPlugin({
40
+      analyzerMode: 'static',
41
+      reportFilename: '../../../reports/bundle.html',
42
+      generateStatsFile: true,
43
+      openAnalyzer: false,
44
+      statsFilename: '../../../reports/stats.json',
45
+    }),
46
+  ]
47
+})

+ 32
- 0
web/config/webpack.prod.js View File

@@ -0,0 +1,32 @@
1
+const path = require('path');
2
+const webpack = require('webpack');
3
+const Merge = require('webpack-merge');
4
+const CommonConfig = require('./webpack.common.js');
5
+const CleanWebpackPlugin = require('clean-webpack-plugin');
6
+const ArchivePlugin = require('webpack-archive-plugin');
7
+
8
+module.exports = Merge(CommonConfig, {
9
+  mode: 'production',
10
+  output: {
11
+    filename: '[name].[chunkhash].js',
12
+    // path.resolve(__dirname winds up relative to the config dir
13
+    path: path.resolve(__dirname, '../../zuul/web/static'),
14
+    publicPath: ''
15
+  },
16
+  optimization: {
17
+    minimize: true
18
+  },
19
+  plugins: [
20
+    new CleanWebpackPlugin(
21
+        ['zuul/web/static'], { root: path.resolve(__dirname, '../..')}),
22
+    // Keeps the vendor bundle from changing needlessly.
23
+    new webpack.HashedModuleIdsPlugin(),
24
+    new ArchivePlugin({
25
+      output: path.resolve(__dirname, '../../zuul-web'),
26
+      format: [
27
+        'tar',
28
+      ],
29
+      ext: 'tgz'
30
+    })
31
+  ]
32
+})

+ 24
- 0
web/core/core.module.ts View File

@@ -0,0 +1,24 @@
1
+import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'
2
+
3
+import { CommonModule } from '@angular/common'
4
+
5
+import { ZuulService } from '../zuul/zuul.service'
6
+
7
+@NgModule({
8
+  imports:      [ CommonModule ],
9
+  providers:    [ ZuulService ]
10
+})
11
+export class CoreModule {
12
+  constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
13
+    if (parentModule) {
14
+      throw new Error(
15
+        'CoreModule is already loaded. Import it in the AppModule only')
16
+    }
17
+  }
18
+
19
+  static forRoot(config: {}): ModuleWithProviders {
20
+    return {
21
+      ngModule: CoreModule,
22
+    }
23
+  }
24
+}

BIN
web/images/black.png View File


BIN
web/images/green.png View File


BIN
web/images/grey.png View File


web/src/images/line-angle.png → web/images/line-angle.png View File


web/src/images/line-t.png → web/images/line-t.png View File


web/src/images/line.png → web/images/line.png View File


BIN
web/images/red.png View File


+ 17
- 0
web/jobs/details.ts View File

@@ -0,0 +1,17 @@
1
+// Copyright 2018 Red Hat
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+export default class JobDetails {
16
+  source_context: string
17
+}

+ 25
- 0
web/jobs/job.ts View File

@@ -0,0 +1,25 @@
1
+// Copyright 2018 Red Hat
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+import JobDetails from './details'
16
+
17
+export default class Job {
18
+  expanded: boolean
19
+  details: JobDetails
20
+  name: string
21
+
22
+  constructor() {
23
+    this.expanded = false
24
+  }
25
+}

+ 47
- 0
web/jobs/jobs.component.html View File

@@ -0,0 +1,47 @@
1
+<!--
2
+Copyright 2017 Red Hat
3
+
4
+Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+not use this file except in compliance with the License. You may obtain
6
+a copy of the License at
7
+
8
+     http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+Unless required by applicable law or agreed to in writing, software
11
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+License for the specific language governing permissions and limitations
14
+under the License.
15
+-->
16
+<div class="container-fluid">
17
+  <table class="table table-hover table-condensed">
18
+    <thead>
19
+      <tr>
20
+        <th>Name</th>
21
+        <th>Description</th>
22
+        <th>Last builds</th>
23
+      </tr>
24
+    </thead>
25
+    <tbody>
26
+      <ng-container *ngFor="let job of jobs">
27
+        <tr>
28
+          <td>{{ job.name }}</td>
29
+          <td>{{ job.description }}</td>
30
+          <td><a [routerLink]="['../builds.html']"
31
+                 [queryParams]="{job_name: job.name}" target="_self">
32
+              builds</a></td>
33
+        </tr>
34
+        <tr *ngIf="job.expanded">
35
+          <td colspan="3">
36
+            <ul class="list-group">
37
+              <li class="list-group-item" *ngFor="let detail of job.details">
38
+                <!-- TODO: make clickable link to cgit files ? -->
39
+                {{ detail.source_context }}
40
+              </li>
41
+            </ul>
42
+          </td>
43
+        </tr>
44
+      </ng-container>
45
+    </tbody>
46
+  </table>
47
+</div>

+ 54
- 0
web/jobs/jobs.component.ts View File

@@ -0,0 +1,54 @@
1
+// Copyright 2017 Red Hat
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+import { Component, OnInit } from '@angular/core'
16
+import { HttpClient } from '@angular/common/http'
17
+import { ActivatedRoute } from '@angular/router'
18
+
19
+import ZuulService from '../zuul/zuul.service'
20
+import JobDetails from './details'
21
+import Job from './job'
22
+
23
+@Component({
24
+  template: require('./jobs.component.html')
25
+})
26
+export default class JobsComponent implements OnInit {
27
+
28
+  jobs: Job[]
29
+
30
+  constructor(
31
+    private http: HttpClient, private route: ActivatedRoute,
32
+    private zuul: ZuulService
33
+  ) {}
34
+
35
+  async ngOnInit() {
36
+    await this.zuul.setTenant(this.route.snapshot.paramMap.get('tenant'))
37
+    this.jobsFetch()
38
+  }
39
+
40
+  jobsFetch(): void {
41
+    const remoteLocation = this.zuul.getSourceUrl('jobs')
42
+    if (remoteLocation) {
43
+      this.http.get<Job[]>(remoteLocation)
44
+        .subscribe(jobs => this.injestJobs(jobs))
45
+    }
46
+  }
47
+
48
+  injestJobs(jobs: Job[]): void {
49
+    for (const job of jobs) {
50
+      job.expanded = false
51
+    }
52
+    this.jobs = jobs
53
+  }
54
+}

+ 22
- 0
web/main.ts View File

@@ -0,0 +1,22 @@
1
+// Copyright 2018 Red Hat, Inc
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+import 'zone.js'
16
+import 'reflect-metadata'
17
+
18
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
19
+
20
+import { AppModule } from './app.module'
21
+
22
+platformBrowserDynamic().bootstrapModule(AppModule)

+ 30
- 0
web/navigation/navigation.component.html View File

@@ -0,0 +1,30 @@
1
+<!--
2
+Copyright 2017 Red Hat
3
+
4
+Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+not use this file except in compliance with the License. You may obtain
6
+a copy of the License at
7
+
8
+     http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+Unless required by applicable law or agreed to in writing, software
11
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+License for the specific language governing permissions and limitations
14
+under the License.
15
+-->
16
+<nav class="navbar navbar-default">
17
+  <div class="container-fluid">
18
+    <div class="navbar-header">
19
+      <a *ngIf="zuul.info && !zuul.info.whiteLabel" class="navbar-brand" [routerLink]="dashboardLink" target="_self">Zuul Dashboard</a>
20
+      <span *ngIf="zuul.info && zuul.info.whiteLabel" class="navbar-brand">Zuul Dashboard</span>
21
+    </div>
22
+    <ul class="nav navbar-nav" *ngIf="zuul.info && zuul.info.tenant !== ''">
23
+      <li routerLinkActive="active" *ngFor="let route of zuul.navbarRoutes">
24
+        <a [routerLink]="route.url">
25
+          {{ route.title }}
26
+        </a>
27
+      </li>
28
+    </ul>
29
+  </div>
30
+</nav>

+ 34
- 0
web/navigation/navigation.component.ts View File

@@ -0,0 +1,34 @@
1
+// Copyright 2018 Red Hat, Inc.
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+// not use this file except in compliance with the License. You may obtain
5
+// a copy of the License at
6
+//
7
+//      http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+// License for the specific language governing permissions and limitations
13
+// under the License.
14
+
15
+import { OnInit, Component } from '@angular/core'
16
+import { Router, ResolveEnd } from '@angular/router'
17
+import { Observable } from 'rxjs/Observable'
18
+import { filter } from 'rxjs/operators'
19
+
20
+import ZuulService from '../zuul/zuul.service'
21
+
22
+@Component({
23
+  selector: 'navigation',
24
+  template: require('./navigation.component.html')
25
+})
26
+export default class NavigationComponent implements OnInit {
27
+  dashboardLink: string
28
+
29
+  constructor(private router: Router, private zuul: ZuulService) {}
30
+
31
+  async ngOnInit() {
32
+    this.dashboardLink = '/t/tenants.html'
33
+  }
34
+}

+ 0
- 41
web/package.json View File

@@ -1,41 +0,0 @@
1
-{
2
-  "name": "@zuul-ci/dashboard",
3
-  "version": "1.0.0",
4
-  "description": "Web Dashboard for Zuul",
5
-  "repository": "https://git.zuul-ci.org/zuul",
6
-  "author": "OpenStack Infra",
7
-  "license": "Apache-2.0",
8
-  "homepage": "/",
9
-  "private": true,
10
-  "dependencies": {
11
-    "axios": "^0.18.0",
12
-    "lodash": "^4.17.10",
13
-    "patternfly-react": "^2.13.1",
14
-    "prop-types": "^15.6.2",
15
-    "react": "^16.4.2",
16
-    "react-dom": "^16.4.2",
17
-    "react-redux": "^5.0.7",
18
-    "react-router": "^4.3.1",
19
-    "react-router-dom": "^4.3.1",
20
-    "react-scripts": "1.1.4",
21
-    "redux": "<4.0.0",
22
-    "redux-thunk": "^2.3.0",
23
-    "sockette": "^2.0.0"
24
-  },
25
-  "devDependencies": {
26
-    "eslint": "^5.3.0",
27
-    "eslint-plugin-jest": "^21.21.0",
28
-    "eslint-plugin-react": "^7.11.1",
29
-    "eslint-plugin-standard": "^3.1.0",
30
-    "yarn": "^1.9.4"
31
-  },
32
-  "scripts": {
33
-    "start:openstack": "REACT_APP_ZUUL_API='https://zuul.openstack.org/api/' react-scripts start",
34
-    "start:multi": "REACT_APP_ZUUL_API='https://softwarefactory-project.io/zuul/api/' react-scripts start",
35
-    "start": "react-scripts start",
36
-    "build": "react-scripts build",
37
-    "test": "react-scripts test --env=jsdom",
38
-    "eject": "react-scripts eject",
39
-    "lint": "eslint --ext .js --ext .jsx src"
40
-  }
41
-}

BIN
web/public/favicon.ico View File


+ 0
- 17
web/public/index.html View File

@@ -1,17 +0,0 @@
1
-<!DOCTYPE html>
2
-<html lang="en" class="layout-pf layout-pf-fixed">
3
-  <head>
4
-    <meta charset="utf-8">
5
-    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
-    <meta name="theme-color" content="#000000">
7
-    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
8
-    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
9
-    <title>Zuul</title>
10
-  </head>
11
-  <body>
12
-    <noscript>
13
-      You need to enable JavaScript to run this app.
14
-    </noscript>
15
-    <div id="root"></div>
16
-  </body>
17
-</html>

+ 0
- 15
web/public/manifest.json View File

@@ -1,15 +0,0 @@
1
-{
2
-  "short_name": "Zuul",
3
-  "name": "Zuul Dashboard",
4
-  "icons": [
5
-    {
6
-      "src": "favicon.ico",
7
-      "sizes": "48x48 32x32",
8
-      "type": "image/x-icon"
9
-    }
10
-  ],
11
-  "start_url": "./index.html",
12
-  "display": "standalone",
13
-  "theme_color": "#000000",
14
-  "background_color": "#ffffff"
15
-}

+ 0
- 162
web/src/App.jsx View File

@@ -1,162 +0,0 @@
1
-// Copyright 2018 Red Hat, Inc
2
-//
3
-// Licensed under the Apache License, Version 2.0 (the "License"); you may
4
-// not use this file except in compliance with the License. You may obtain
5
-// a copy of the License at
6
-//
7
-//      http://www.apache.org/licenses/LICENSE-2.0
8
-//
9
-// Unless required by applicable law or agreed to in writing, software
10
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
-// License for the specific language governing permissions and limitations
13
-// under the License.
14
-
15
-// The App is the parent component of every pages. Each page content is
16
-// rendered by the Route object according to the current location.
17
-
18
-import React from 'react'
19
-import PropTypes from 'prop-types'
20
-import { matchPath, withRouter } from 'react-router'
21
-import { Link, Redirect, Route, Switch } from 'react-router-dom'
22
-import { connect } from 'react-redux'
23
-import { Masthead } from 'patternfly-react'
24
-
25
-import logo from './images/logo.png'
26
-import { routes } from './routes'
27
-import { setTenantAction } from './reducers'
28
-
29
-
30
-class App extends React.Component {
31
-  static propTypes = {
32
-    info: PropTypes.object,
33
-    tenant: PropTypes.object,
34
-    location: PropTypes.object,
35
-    dispatch: PropTypes.func
36
-  }
37
-
38
-  constructor() {
39
-    super()
40
-    this.menu = routes()
41
-  }
42
-
43
-  renderMenu() {
44
-    const { location } = this.props
45
-    const activeItem = this.menu.find(
46
-      item => location.pathname === item.to
47
-    )
48
-    return (
49
-      <ul className='nav navbar-nav navbar-primary'>
50
-        {this.menu.filter(item => item.title).map(item => (
51
-          <li key={item.to} className={item === activeItem ? 'active' : ''}>
52
-            <Link to={this.props.tenant.linkPrefix + item.to}>
53
-              {item.title}
54
-            </Link>
55
-          </li>
56
-        ))}
57
-      </ul>
58
-    )
59
-  }
60
-
61
-  renderContent = () => {
62
-    const { tenant } = this.props
63
-    const allRoutes = []
64
-    this.menu
65
-      // Do not include '/tenants' route in white-label setup
66
-      .filter(item =>
67
-              (tenant.whiteLabel && !item.globalRoute) || !tenant.whiteLabel)
68
-      .forEach((item, index) => {
69
-        allRoutes.push(
70
-          <Route
71
-            key={index}
72
-            path={item.globalRoute ? item.to : tenant.routePrefix + item.to}
73
-            component={item.component}
74
-            exact
75
-            />
76
-        )
77
-    })
78
-    return (
79
-      <Switch>
80
-        {allRoutes}
81
-        <Redirect from='*' to={tenant.defaultRoute} key='default-route' />
82
-      </Switch>
83
-    )
84
-  }
85
-
86
-  componentDidUpdate() {
87
-    // This method is called when info property is updated
88
-    const { tenant, info } = this.props
89
-    if (info.capabilities) {
90
-      let tenantName, whiteLabel
91
-
92
-      if (info.tenant) {
93
-        // White label
94
-        whiteLabel = true
95
-        tenantName = info.tenant
96
-      } else if (!info.tenant) {
97
-        // Multi tenant, look for tenant name in url
98
-        whiteLabel = false
99
-
100
-        const match = matchPath(
101
-          this.props.location.pathname, {path: '/t/:tenant'})
102
-
103
-        if (match) {
104
-          tenantName = match.params.tenant
105
-        } else {
106
-          tenantName = ''
107
-        }
108
-      }
109
-      // Set tenant only if it changed to prevent DidUpdate loop
110
-      if (typeof tenant.name === 'undefined' || tenant.name !== tenantName) {
111
-        this.props.dispatch(setTenantAction(tenantName, whiteLabel))
112
-      }
113
-    }
114
-  }
115
-
116
-  render() {
117
-    const { tenant } = this.props
118
-    if (typeof tenant.name === 'undefined') {
119
-      return (<h2>Loading...</h2>)
120
-    }
121
-
122
-    return (
123
-      <React.Fragment>
124
-        <Masthead
125
-          iconImg={logo}
126
-          navToggle
127
-          thin
128
-          >
129
-          <div className='collapse navbar-collapse'>
130
-            {tenant.name && this.renderMenu()}
131
-            <ul className='nav navbar-nav navbar-utility'>
132
-              <li>
133
-                <a href='https://zuul-ci.org/docs'
134
-                   rel='noopener noreferrer' target='_blank'>
135
-                  Documentation
136
-                </a>
137
-              </li>
138
-              {tenant.name && (
139
-                <li>
140
-                  <Link to={tenant.defaultRoute}>
141
-                    <strong>Tenant</strong> {tenant.name}
142
-                  </Link>
143
-                </li>
144
-              )}
145
-            </ul>
146
-          </div>
147
-        </Masthead>
148
-        <div className='container-fluid container-cards-pf'>
149
-          {this.renderContent()}
150
-        </div>
151
-      </React.Fragment>
152
-    )
153
-  }
154
-}
155
-
156
-// This connect the info state from the store to the info property of the App.
157
-export default withRouter(connect(
158
-  state => ({
159
-    info: state.info,
160
-    tenant: state.tenant
161
-  })
162
-)(App))