diff --git a/app/controllers/api/v2/hosts_monitoring_results_controller.rb b/app/controllers/api/v2/hosts_monitoring_results_controller.rb new file mode 100644 index 0000000..40d3261 --- /dev/null +++ b/app/controllers/api/v2/hosts_monitoring_results_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Api + module V2 + class HostsMonitoringResultsController < ::Api::V2::BaseController + include ::Api::Version2 + + api :GET, "/hosts/:host_id/monitoring/results", N_('Get the monitoring results') + param :host_id, :identifier_dottable, required: true + def index + @monitoring_results = resource_scope + end + + private + + def resource_class + MonitoringResult + end + + def resource_scope(*args) + # TODO: only show for hosts with monitoring enabled? + resource_class.authorized(:view_monitoring_results).where(host_id: params[:host_id]) + end + end + end +end diff --git a/app/models/monitoring_result.rb b/app/models/monitoring_result.rb index ed41202..2e9704a 100644 --- a/app/models/monitoring_result.rb +++ b/app/models/monitoring_result.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class MonitoringResult < ApplicationRecord + include Authorizable + enum :result => { :ok => 0, :warning => 1, :critical => 2, :unknown => 3 } belongs_to_host diff --git a/app/views/api/v2/hosts_monitoring_results/base.json.rabl b/app/views/api/v2/hosts_monitoring_results/base.json.rabl new file mode 100644 index 0000000..bad17e9 --- /dev/null +++ b/app/views/api/v2/hosts_monitoring_results/base.json.rabl @@ -0,0 +1,7 @@ +object @monitoring_result + +attributes :id, :service, :status, :result, :downtime, :acknowledged, :timestamp + +node :status_label do |result| + result.to_label +end diff --git a/app/views/api/v2/hosts_monitoring_results/index.json.rabl b/app/views/api/v2/hosts_monitoring_results/index.json.rabl new file mode 100644 index 0000000..0987252 --- /dev/null +++ b/app/views/api/v2/hosts_monitoring_results/index.json.rabl @@ -0,0 +1,3 @@ +collection @monitoring_results + +extends "api/v2/hosts_monitoring_results/base" diff --git a/config/routes.rb b/config/routes.rb index 9e1598c..d3fd4c3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,9 +5,16 @@ scope '(:apiv)', :module => :v2, :defaults => { :apiv => 'v2' }, :apiv => /v1|v2/, - :constraints => ApiConstraints.new(:version => 2) do + :constraints => ApiConstraints.new(:version => 2, default: true) do resources :monitoring_results, :only => [:create] resources :downtime, :only => [:create] + resources :hosts, param: :host_id, only: [] do + member do + scope 'monitoring' do + resources :results, controller: :hosts_monitoring_results, as: :host_monitoring_results, only: [:index] + end + end + end end end diff --git a/lib/foreman_monitoring/engine.rb b/lib/foreman_monitoring/engine.rb index 9502ea2..78ecad4 100644 --- a/lib/foreman_monitoring/engine.rb +++ b/lib/foreman_monitoring/engine.rb @@ -22,6 +22,8 @@ class Engine < ::Rails::Engine Foreman::Plugin.register :foreman_monitoring do requires_foreman '>= 3.0' + register_global_js_file 'fills' + settings do category(:monitoring, N_('Monitoring')) do setting('monitoring_affect_global_status', diff --git a/package.json b/package.json new file mode 100644 index 0000000..1828503 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "foreman_monitoring", + "version": "1.0.0", + "description": "DESCRIPTION", + "main": "index.js", + "scripts": { + "lint": "tfm-lint --plugin -d /webpack", + "test": "tfm-test --plugin", + "test:watch": "tfm-test --plugin --watchAll", + "test:current": "tfm-test --plugin --watch", + "publish-coverage": "tfm-publish-coverage", + "create-react-component": "yo react-domain" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/theforeman/foreman_monitoring.git" + }, + "bugs": { + "url": "http://projects.theforeman.org/projects/foreman_monitoring/issues" + }, + "peerDependencies": { + "@theforeman/vendor": ">= 12.1.0" + }, + "devDependencies": { + "@babel/core": "^7.7.0", + "@sheerun/mutationobserver-shim": "^0.3.3", + "@theforeman/builder": ">= 10.1.0", + "@theforeman/eslint-plugin-foreman": ">= 10.1.0", + "@theforeman/find-foreman": ">= 10.1.0", + "@theforeman/test": ">= 10.1.0", + "@theforeman/vendor-dev": ">= 10.1.0", + "babel-eslint": "^10.0.3", + "eslint": "^6.7.2", + "jed": "^1.1.1", + "prettier": "^1.19.1", + "stylelint-config-standard": "^18.0.0", + "stylelint": "^9.3.0" + } +} diff --git a/webpack/fills_index.js b/webpack/fills_index.js new file mode 100644 index 0000000..6779286 --- /dev/null +++ b/webpack/fills_index.js @@ -0,0 +1,3 @@ +import { registerFills } from './src/Extends/Fills'; + +registerFills(); diff --git a/webpack/index.js b/webpack/index.js new file mode 100644 index 0000000..e69de29 diff --git a/webpack/src/Extends/Fills/index.js b/webpack/src/Extends/Fills/index.js new file mode 100644 index 0000000..706f3b1 --- /dev/null +++ b/webpack/src/Extends/Fills/index.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill'; +import MonitoringTab from '../Host/MonitoringTab'; + +const fills = [ + { + slot: 'host-details-page-tabs', + name: 'Monitoring', + component: props => , + weight: 500, + metadata: { title: __('Monitoring') }, + }, +]; + +export const registerFills = () => { + fills.forEach(({ slot, name, component: Component, weight, metadata }) => + addGlobalFill( + slot, + name, + , + weight, + metadata + ) + ); +}; diff --git a/webpack/src/Extends/Host/MonitoringTab/index.js b/webpack/src/Extends/Host/MonitoringTab/index.js new file mode 100644 index 0000000..81cb6a6 --- /dev/null +++ b/webpack/src/Extends/Host/MonitoringTab/index.js @@ -0,0 +1,132 @@ +import React, { createElement } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import { + DescriptionList, + DescriptionListTerm, + DescriptionListGroup, + DescriptionListDescription, + Grid, + GridItem, +} from '@patternfly/react-core'; +import { + CheckCircleIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + QuestionCircleIcon, +} from '@patternfly/react-icons'; +import { Table, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; +import { STATUS } from 'foremanReact/constants'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { useAPI } from 'foremanReact/common/hooks/API/APIHooks'; +import PermissionDenied from 'foremanReact/components/PermissionDenied'; +import Loading from 'foremanReact/components/Loading'; +import SkeletonLoader from 'foremanReact/components/common/SkeletonLoader'; +import DefaultLoaderEmptyState from 'foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState'; +import CardTemplate from 'foremanReact/components/HostDetails/Templates/CardItem/CardTemplate'; + +const STATUS_ICONS = { + 'ok': CheckCircleIcon, + 'warning': ExclamationTriangleIcon, + 'critical': ExclamationCircleIcon, +}; + +const STATUS_STYLES = { + 'ok': 'ok', + 'warning': 'warn', + 'critical': 'critical', +}; + +const status_icon = (result_status) => { + const cls = STATUS_ICONS[result_status] || QuestionCircleIcon; + const style = STATUS_STYLES[result_status] || 'question'; + return createElement(cls, { className: `status-${style}` }); +}; + +const ErrorHandler = ({ response }) => { + // In case of an error, response is an Error object + if (response.response?.status === 403) { + return ; + } + // TODO +} + +const MonitoringResults = ({ hostId }) => { + const { + response, + status, + } = useAPI('get', `/api/hosts/${hostId}/monitoring/results`, `get-monitoring-results-${hostId}`); + + return ( + errorNode=> + + + + + + + + + {response?.results?.map((result) => ( + + + + + ))} + +
{__('Service')}{__('Status')}
{result.service}{status_icon(result.status)} {result.status_label}
+
+ ); +}; + +MonitoringResults.propTypes = { + hostId: PropTypes.number.isRequired, +}; + +const MonitoringTab = ({ + response: { + id: hostId, + monitoring_proxy_id: monitoringProxyId, + monitoring_proxy_name: monitoringProxyName, + }, + status, +}) => ( + + + + + + {__('Monitoring Proxy')} + + } + status={status} + > + {monitoringProxyId && {monitoringProxyName}} + + + + + + + + } + status={status} + > + {hostId && monitoringProxyId && } + + + +); + +MonitoringTab.propTypes = { + response: PropTypes.object, + status: PropTypes.string, +}; +MonitoringTab.defaultProps = { + response: {}, + status: STATUS.PENDING, +}; + +export default MonitoringTab;