Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/controllers/api/v2/hosts_monitoring_results_controller.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions app/models/monitoring_result.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 7 additions & 0 deletions app/views/api/v2/hosts_monitoring_results/base.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
object @monitoring_result

attributes :id, :service, :status, :result, :downtime, :acknowledged, :timestamp

node :status_label do |result|
result.to_label
end
3 changes: 3 additions & 0 deletions app/views/api/v2/hosts_monitoring_results/index.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
collection @monitoring_results

extends "api/v2/hosts_monitoring_results/base"
9 changes: 8 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions lib/foreman_monitoring/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
39 changes: 39 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
3 changes: 3 additions & 0 deletions webpack/fills_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { registerFills } from './src/Extends/Fills';

registerFills();
Empty file added webpack/index.js
Empty file.
26 changes: 26 additions & 0 deletions webpack/src/Extends/Fills/index.js
Original file line number Diff line number Diff line change
@@ -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 => <MonitoringTab {...props} />,
weight: 500,
metadata: { title: __('Monitoring') },
},
];

export const registerFills = () => {
fills.forEach(({ slot, name, component: Component, weight, metadata }) =>
addGlobalFill(
slot,
name,
<Component key={`monitoring-fill-${name}`} />,
weight,
metadata
)
);
};
132 changes: 132 additions & 0 deletions webpack/src/Extends/Host/MonitoringTab/index.js
Original file line number Diff line number Diff line change
@@ -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 <PermissionDenied missingPermissions={['view_monitoring_results']} />;
}
// TODO
}

const MonitoringResults = ({ hostId }) => {
const {
response,
status,
} = useAPI('get', `/api/hosts/${hostId}/monitoring/results`, `get-monitoring-results-${hostId}`);

return (
<SkeletonLoader status={status} customSkeleton=<Loading /> errorNode=<ErrorHandler response={response} />>
<Table variant="compact" aria-label={__("Monitoring Results")}>
<Thead>
<Tr>
<Th>{__('Service')}</Th>
<Th>{__('Status')}</Th>
</Tr>
</Thead>
<Tbody>
{response?.results?.map((result) => (
<Tr key={`monitoring-result-${result.id}`}>
<Td>{result.service}</Td>
<Td>{status_icon(result.status)} {result.status_label}</Td>
</Tr>
))}
</Tbody>
</Table>
</SkeletonLoader>
);
};

MonitoringResults.propTypes = {
hostId: PropTypes.number.isRequired,
};

const MonitoringTab = ({
response: {
id: hostId,
monitoring_proxy_id: monitoringProxyId,
monitoring_proxy_name: monitoringProxyName,
},
status,
}) => (
<Grid hasGutter>
<GridItem span={4}>
<CardTemplate header={__('Monitoring details')}>
<DescriptionList isCompact>
<DescriptionListGroup>
<DescriptionListTerm>{__('Monitoring Proxy')}</DescriptionListTerm>
<DescriptionListDescription>
<SkeletonLoader
emptyState={<DefaultLoaderEmptyState />}
status={status}
>
{monitoringProxyId && <a href={`/smart_proxies/${monitoringProxyId}`}>{monitoringProxyName}</a>}
</SkeletonLoader>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</CardTemplate>
</GridItem>
<GridItem span={8}>
<SkeletonLoader
emptyState={<DefaultLoaderEmptyState />}
status={status}
>
{hostId && monitoringProxyId && <MonitoringResults hostId={hostId} />}
</SkeletonLoader>
</GridItem>
</Grid>
);

MonitoringTab.propTypes = {
response: PropTypes.object,
status: PropTypes.string,
};
MonitoringTab.defaultProps = {
response: {},
status: STATUS.PENDING,
};

export default MonitoringTab;