From 22e7fec35a2abec605fc97f078a7a99eaf277d05 Mon Sep 17 00:00:00 2001 From: Amir Fefer Date: Wed, 22 Nov 2017 15:52:12 +0200 Subject: [PATCH] Fixes #21737 - move about page to react --- app/assets/javascripts/about.js | 21 ---- app/views/about/index.html.erb | 104 +++--------------- config/initializers/assets.rb | 1 - config/routes.rb | 2 +- package.json | 1 + .../javascripts/react_app/common/helpers.js | 8 ++ .../components/about/compute/index.js | 45 ++++++++ .../components/about/plugin/index.js | 30 +++++ .../components/about/provider/index.js | 24 ++++ .../components/about/proxies/index.js | 60 ++++++++++ .../components/common/status/index.js | 43 ++++++++ .../components/common/table/index.js | 54 +++++++++ .../react_app/components/componentRegistry.js | 8 ++ .../react_app/redux/actions/status/index.js | 12 ++ .../javascripts/react_app/redux/consts.js | 3 + .../react_app/redux/reducers/index.js | 2 + .../react_app/redux/reducers/status/index.js | 19 ++++ 17 files changed, 323 insertions(+), 114 deletions(-) delete mode 100644 app/assets/javascripts/about.js create mode 100644 webpack/assets/javascripts/react_app/components/about/compute/index.js create mode 100644 webpack/assets/javascripts/react_app/components/about/plugin/index.js create mode 100644 webpack/assets/javascripts/react_app/components/about/provider/index.js create mode 100644 webpack/assets/javascripts/react_app/components/about/proxies/index.js create mode 100644 webpack/assets/javascripts/react_app/components/common/status/index.js create mode 100644 webpack/assets/javascripts/react_app/components/common/table/index.js create mode 100644 webpack/assets/javascripts/react_app/redux/actions/status/index.js create mode 100644 webpack/assets/javascripts/react_app/redux/reducers/status/index.js diff --git a/app/assets/javascripts/about.js b/app/assets/javascripts/about.js deleted file mode 100644 index 5857fae75718..000000000000 --- a/app/assets/javascripts/about.js +++ /dev/null @@ -1,21 +0,0 @@ -$(function() { - "use strict"; - $(".compute-status").each(function(index, item) { - var item = $(item); - var url = item.data('url'); - $.ajax({ - type: 'post', - url: url, - success: function(response) { - item.text(__(response.status)); - item.attr('title',response.message); - if(response.status === "OK"){ - item.addClass('label label-success') - }else{ - item.addClass('label label-danger') - } - item.tooltip({html: true}); - } - }); - }); -}); diff --git a/app/views/about/index.html.erb b/app/views/about/index.html.erb index 5c37fafe2009..e6b5522ec980 100644 --- a/app/views/about/index.html.erb +++ b/app/views/about/index.html.erb @@ -1,5 +1,4 @@ <% title _("About") %> -<%= javascript 'proxy_status', 'charts', 'about' %>
@@ -16,105 +15,28 @@
- <% if @smart_proxies.empty? %> -

<%= _("No smart proxies to show") %>

- <% else %> - - - - - - - - - - - <% @smart_proxies.each do |proxy| %> - - - - - - - <% end %> - -
<%= _("Name") %><%= _("Features") %><%= _("Status") %><%= _("Version") %>
<%= link_to_if_authorized proxy.name, hash_for_smart_proxy_path(proxy) %><%=h proxy.features.map(&:name).to_sentence %>
<%= spinner %>
<%= spinner %>
- <% end %> + <%= data = @smart_proxies.map {|proxy| {:id => {:name => proxy.name, :id => proxy.id}, + :features => h(proxy.features.map(&:name).to_sentence)}} %> + <%= mount_react_component('AboutProxyTable', '#smart_proxies', data.to_json) %>
<% if SETTINGS[:unattended] %>
- - - - - - - - - <% @providers.sort_by { |prov| prov[:friendly_name].downcase }.each do |provider| %> - - - <% if provider[:status] == :installed %> - - <% else %> - - <% end %> - - <% end %> - -
<%= _("Provider") %><%= _("Status") %>
<%= provider[:friendly_name] %>
<%= _('Installed') %>
<%= _('Not Installed') %>
+ <%= data = @providers.map {|provider| {:provider => provider[:friendly_name], + :status => provider[:status] == :installed}}%> + <%= mount_react_component('AboutProviderTable', '#available_providers', data.to_json) %> + <%= _('To enable a provider, either install the OS package (e.g. foreman-libvirt) or enable the bundler group for development setup (e.g. ovirt).') %>
- <% if @compute_resources.empty? %> -

<%= _("No compute resource to show") %>

- <% else %> - - - - - - - - - - <% @compute_resources.each do |compute| %> - - - - - - <% end %> - -
<%= _("Name") %><%= _("Type") %><%= _("Status") %>
<%= link_to(compute.name, compute) %><%= compute.provider_friendly_name %>
><%= spinner %>
- <% end %> + <%= data = @compute_resources.map {|compute| {:id => {:name => compute.name, :id => compute.id}, + :type => compute.provider_friendly_name }} %> + <%= mount_react_component('AboutComputeTable', '#compute_resources', data.to_json) %>
<% end %>
- <% if @plugins.empty? %> -

<%= _("No plugins found") %>

- <% else %> - - - - - - - - - - - <% @plugins.each do |plugin| %> - - - - - - - <% end %> - -
<%= _("Name") %><%= _("Description") %><%= _("Author") %><%= _("Version") %>
<%= plugin.url.blank? ? plugin.name : link_to(plugin.name, plugin.url, :rel=>'external')%><%= _(plugin.description) %><%= plugin.author_url.blank? ? plugin.author : link_to(plugin.author, plugin.author_url)%><%= plugin.version %>
- <% end %> + <%= data = @plugins.map {|plugin| {:name => plugin.name, :description => plugin.description, + :author => plugin.author, :version => plugin.version }}%> + <%= mount_react_component('AboutPluginTable', '#plugins', data.to_json) %>
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 64b1f006887d..1015c2f6e252 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -44,7 +44,6 @@ hidden_values password_strength proxy_status - about parameter_override) javascript += FastGettext.default_available_locales.map { |loc| "locale/#{loc}/app" } diff --git a/config/routes.rb b/config/routes.rb index 104303a40e78..33ca59985315 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -404,7 +404,7 @@ post 'template_selected' post 'cluster_selected' get 'resource_pools' - post 'ping' + get 'ping' put 'associate' put 'refresh_cache' end diff --git a/package.json b/package.json index 4cd7476feec4..1b1b3eb97669 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "react-numeric-input": "^2.0.7", "react-onclickoutside": "^6.6.2", "react-redux": "^5.0.2", + "react-table": "^6.7.4", "redux": "^3.6.0", "redux-form": "^7.0.3", "redux-form-validators": "^2.0.1", diff --git a/webpack/assets/javascripts/react_app/common/helpers.js b/webpack/assets/javascripts/react_app/common/helpers.js index c192c8260ee2..04e34407728e 100644 --- a/webpack/assets/javascripts/react_app/common/helpers.js +++ b/webpack/assets/javascripts/react_app/common/helpers.js @@ -6,4 +6,12 @@ export default { }); }, noop: Function.prototype, // empty function + urlBuilder(controller, action, id = undefined) { + return `/${controller}/${id ? `${id}/` : ''}${action}`; + }, + getQueryParam(query) { + const urlParams = new URLSearchParams(window.location.search); + + return urlParams.get(query); + }, }; diff --git a/webpack/assets/javascripts/react_app/components/about/compute/index.js b/webpack/assets/javascripts/react_app/components/about/compute/index.js new file mode 100644 index 000000000000..395ed38dea2f --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/compute/index.js @@ -0,0 +1,45 @@ +import React from 'react'; +import Status from '../../common/status'; +import Table from '../../common/table'; +import helpers from '../../../common/helpers'; + +class AboutComputeTable extends React.Component { + render() { + const columns = [ + { + Header: 'Name', + accessor: 'id', + Cell: props => ( + + {props.value.name} + + ), + }, + { + Header: 'Type', + accessor: 'type', + }, + { + Header: 'Status', + accessor: 'id', + Cell: props => ( + + ), + }, + ]; + + return ( + + ); + } +} +export default AboutComputeTable; diff --git a/webpack/assets/javascripts/react_app/components/about/plugin/index.js b/webpack/assets/javascripts/react_app/components/about/plugin/index.js new file mode 100644 index 000000000000..f14ec51b8c5c --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/plugin/index.js @@ -0,0 +1,30 @@ +import React from 'react'; +import Table from '../../common/table'; + +class AboutPluginTable extends React.Component { + render() { + const columns = [ + { + Header: 'Name', + accessor: 'name', + }, + { + Header: 'Description', + accessor: 'description', + }, + { + Header: 'Author', + accessor: 'author', + }, + { + Header: 'Version', + accessor: 'version', + }, + ]; + + return ( +
+ ); + } +} +export default AboutPluginTable; diff --git a/webpack/assets/javascripts/react_app/components/about/provider/index.js b/webpack/assets/javascripts/react_app/components/about/provider/index.js new file mode 100644 index 000000000000..a2eb06c64061 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/provider/index.js @@ -0,0 +1,24 @@ +import React from 'react'; +import Table from '../../common/table'; +import Icon from '../../common/Icon'; + +class AboutProviderTable extends React.Component { + render() { + const columns = [ + { + Header: 'Provider', + accessor: 'provider', + }, + { + Header: 'Installed', + accessor: 'status', + Cell: data => , + }, + ]; + + return ( +
+ ); + } +} +export default AboutProviderTable; diff --git a/webpack/assets/javascripts/react_app/components/about/proxies/index.js b/webpack/assets/javascripts/react_app/components/about/proxies/index.js new file mode 100644 index 000000000000..39a0f83fa8a6 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/proxies/index.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import Status from '../../common/status'; +import { simpleLoader } from '../../common/Loader'; +import Table from '../../common/table'; +import helpers from '../../../common/helpers'; + +class AboutProxyTable extends React.Component { + render() { + const columns = [ + { + Header: 'Name', + accessor: 'id', + width: 100, + Cell: data => ( + + {data.value.name} + + ), + }, + { + Header: 'Features', + accessor: 'features', + width: 250, + }, + { + Header: 'Status', + accessor: 'id', + width: 50, + Cell: data => ( + + ), + }, + { + Header: 'Version', + accessor: 'id', + width: 100, + Cell: data => ( + + {this.props.status[`proxy_${data.value.id}`] + ? this.props.status[`proxy_${data.value.id}`].message.version + : simpleLoader('xs')} + + ), + }, + ]; + + return ( +
+ ); + } +} +const mapStateToProps = ({ status }) => ({ status }); + +export default connect(mapStateToProps)(AboutProxyTable); diff --git a/webpack/assets/javascripts/react_app/components/common/status/index.js b/webpack/assets/javascripts/react_app/components/common/status/index.js new file mode 100644 index 000000000000..f4fa10ad9f51 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/status/index.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { simpleLoader } from '../../common/Loader'; +import * as StatusAction from '../../../redux/actions/status/'; +import Icon from '../../common/Icon'; + +class Status extends React.Component { + componentDidMount() { + const { data: { id, url }, getStatus } = this.props; + + getStatus({ id, url }); + } + + render() { + const { + status, message, error, success, + } = this.props.status; + + if (status || success !== undefined) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + return simpleLoader('xs'); + } +} + +const mapStateToProps = (state, ownProps) => ({ + status: state.status[ownProps.data.id] || {}, +}); + +export default connect(mapStateToProps, StatusAction)(Status); diff --git a/webpack/assets/javascripts/react_app/components/common/table/index.js b/webpack/assets/javascripts/react_app/components/common/table/index.js new file mode 100644 index 000000000000..2ad51a460cfe --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/table/index.js @@ -0,0 +1,54 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import 'react-table/react-table.css'; +import helpers from '../../../common/helpers'; + +const SETTING_PER_PAGE = 10; // TODO: use foreman per_page settings +const BLANK_ROWS = 3; +const TOOLTIP_SIZE = 15; + +class Table extends React.Component { + isEmpty() { + return this.props.data.length === 0; + } + + render() { + const { columns, data, showPagination } = this.props; + const rows = this.isEmpty() ? BLANK_ROWS : data.length; + + return ( + { + const rowData = rowInfo ? rowInfo.original[column.id] : undefined; + + return { + title: + typeof rowData === 'string' && rowData.length > TOOLTIP_SIZE + ? rowData + : undefined, + style: { + textAlign: 'center', + }, + }; + }} + /> + ); + } +} +export default Table; diff --git a/webpack/assets/javascripts/react_app/components/componentRegistry.js b/webpack/assets/javascripts/react_app/components/componentRegistry.js index 972df748cd49..807d8d116e04 100644 --- a/webpack/assets/javascripts/react_app/components/componentRegistry.js +++ b/webpack/assets/javascripts/react_app/components/componentRegistry.js @@ -8,6 +8,10 @@ import PowerStatus from './hosts/powerStatus/'; import NotificationContainer from './notifications/'; import ToastsList from './toastNotifications/'; import StorageContainer from './hosts/storage/vmware/'; +import AboutComputeTable from './about/compute'; +import AboutProxyTable from './about/proxies'; +import AboutProviderTable from './about/provider'; +import AboutPluginTable from './about/plugin'; const componentRegistry = { registry: {}, @@ -62,6 +66,10 @@ const coreComponets = [ { name: 'NotificationContainer', type: NotificationContainer }, { name: 'ToastNotifications', type: ToastsList, data: false }, { name: 'StorageContainer', type: StorageContainer }, + { name: 'AboutComputeTable', type: AboutComputeTable }, + { name: 'AboutProxyTable', type: AboutProxyTable }, + { name: 'AboutProviderTable', type: AboutProviderTable }, + { name: 'AboutPluginTable', type: AboutPluginTable }, ]; componentRegistry.registerMultiple(coreComponets); diff --git a/webpack/assets/javascripts/react_app/redux/actions/status/index.js b/webpack/assets/javascripts/react_app/redux/actions/status/index.js new file mode 100644 index 000000000000..172e8491a460 --- /dev/null +++ b/webpack/assets/javascripts/react_app/redux/actions/status/index.js @@ -0,0 +1,12 @@ +import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE } from '../../consts'; +import { ajaxRequestAction } from '../common'; + +export const getStatus = ({ id, url }) => dispatch => + ajaxRequestAction({ + dispatch, + requestAction: STATUS_REQUEST, + successAction: STATUS_SUCCESS, + failedAction: STATUS_FAILURE, + url, + item: { id }, + }); diff --git a/webpack/assets/javascripts/react_app/redux/consts.js b/webpack/assets/javascripts/react_app/redux/consts.js index a97a3f115bb4..f5e7ec51f1ff 100644 --- a/webpack/assets/javascripts/react_app/redux/consts.js +++ b/webpack/assets/javascripts/react_app/redux/consts.js @@ -24,3 +24,6 @@ export const NOTIFICATIONS_MARK_GROUP_AS_READ = 'NOTIFICATIONS_MARK_GROUP_AS_READ'; export const NOTIFICATIONS_POLLING_STARTED = 'NOTIFICATIONS_POLLING_STARTED'; +export const STATUS_REQUEST = 'STATUS_REQUEST'; +export const STATUS_SUCCESS = 'STATUS_SUCCESS'; +export const STATUS_FAILURE = 'STATUS_FAILURE'; diff --git a/webpack/assets/javascripts/react_app/redux/reducers/index.js b/webpack/assets/javascripts/react_app/redux/reducers/index.js index 0379a999f9b0..ce2509f04429 100644 --- a/webpack/assets/javascripts/react_app/redux/reducers/index.js +++ b/webpack/assets/javascripts/react_app/redux/reducers/index.js @@ -3,10 +3,12 @@ import statistics from './statistics'; import hosts from './hosts'; import notifications from './notifications/'; import toasts from './toasts'; +import status from './status'; export default combineReducers({ statistics, hosts, notifications, toasts, + status, }); diff --git a/webpack/assets/javascripts/react_app/redux/reducers/status/index.js b/webpack/assets/javascripts/react_app/redux/reducers/status/index.js new file mode 100644 index 000000000000..66faf35a494e --- /dev/null +++ b/webpack/assets/javascripts/react_app/redux/reducers/status/index.js @@ -0,0 +1,19 @@ +import Immutable from 'seamless-immutable'; +import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE } from '../../consts'; + +const initialState = Immutable({}); + +export default (state = initialState, action) => { + const { payload } = action; + + switch (action.type) { + case STATUS_REQUEST: + return state; + case STATUS_SUCCESS: + return state.set(payload.id, { ...payload }); + case STATUS_FAILURE: + return state.set(payload.item.id, { error: payload.error }); + default: + return state; + } +};