Retire monasca-ui repository

This repository is being retired as part of the Monasca project
retirement. The project content has been replaced with a retirement
notice.

Needed-By: I3cb522ce8f51424b64e93c1efaf0dfd1781cd5ac
Change-Id: Idf7496644af9c20b807a0b9367d0bcd07966d371
Signed-off-by: Goutham Pacha Ravi <gouthampravi@gmail.com>
This commit is contained in:
Goutham Pacha Ravi
2025-08-11 22:15:22 -07:00
parent 32b63fb6e6
commit 1298103538
126 changed files with 7 additions and 11613 deletions

View File

@@ -1,6 +0,0 @@
- project:
templates:
- check-requirements
- horizon-non-primary-django-jobs
- openstack-cover-jobs-horizon
- openstack-python3-jobs-horizon

View File

@@ -1,19 +0,0 @@
The source repository for this project can be found at:
https://opendev.org/openstack/monasca-ui
Pull requests submitted through GitHub are not monitored.
To start contributing to OpenStack, follow the steps in the contribution guide
to set up and use Gerrit:
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
Bugs should be filed on Storyboard:
https://storyboard.openstack.org/#!/project/875
For more specific information about contributing to this repository, see the
Monasca contributor guide:
https://docs.openstack.org/monasca-api/latest/contributor/contributing.html

View File

@@ -1,148 +1,9 @@
==========
Monasca UI
==========
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/monasca-ui.svg
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
.. Change things from this point on
Monasca UI is implemented as a Horizon plugin that adds panels to
Horizon. It is installed into devstack by the monasca-api plugin.
DevStack Deployment Set Up
==========================
- ``cd /opt/stack/horizon``
- Install OpenStack upper-constraints requirements
``pip install -c https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt -r requirements.txt``
- Clone Monasca UI:
``git clone https://opendev.org/openstack/monasca-ui.git``
- Add ``git+https://opendev.org/openstack/monasca-ui.git`` to
``requirements.txt``.
- Install Monasca UI required packages:
``pip install -r requirements.txt`` (monasca-client packages will be installed)
- Edit ``openstack_dashboard/settings.py`` to include the following two
lines:
- ``import monitoring.enabled``
- ``monitoring.enabled,`` (Add this line to the
``settings_utils.update_dashboards`` list)
- Link Monasca UI into Horizon:
::
ln -sf $(pwd)/../monasca-ui/monitoring/enabled/_50_admin_add_monitoring_panel.py \
$(pwd)/openstack_dashboard/enabled/_50_admin_add_monitoring_panel.py
ln -sf $(pwd)/../monasca-ui/monitoring/conf/monitoring_policy.yaml \
$(pwd)/openstack_dashboard/conf/monitoring_policy.yaml
ln -sfF $(pwd)/../monasca-ui/monitoring $(pwd)/monitoring
- Collect static files, run tests
::
python manage.py collectstatic --noinput
python manage.py compress
./run_tests.sh
- Restart apache service ``service apache2 restart``
Development Environment Set Up
==============================
Get the Code
------------
::
git clone https://opendev.org/openstack/monasca-ui.git # clone monasca-ui
git clone https://opendev.org/openstack/horizon.git # clone horizon
git clone https://github.com/monasca/grafana.git # clone grafana
git clone https://github.com/openstack/monasca-grafana-datasource.git # clone grafana plugins
Set up Horizon
--------------
Since Monasca UI is a Horizon plugin, the first step is to get their
development environment set up.
::
cd horizon
./run_tests.sh
cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
Pro Tip: Make sure you have Horizon running correctly before proceeding.
For more details visit: https://docs.openstack.org/horizon/latest/#setup
Set up Monasca-UI
-----------------
- Edit ``openstack_dashboard/local/local_settings.py`` to modify the
``OPENSTACK_HOST`` IP address to point to devstack.
- Add ``monasca-client`` to ``requirements.txt``. Get the latest
version from: https://pypi.org/project/python-monascaclient
- Link monasca into Horizon:
::
ln -sf $(pwd)/../monasca-ui/monitoring/enabled/_50_admin_add_monitoring_panel.py \
$(pwd)/openstack_dashboard/enabled/_50_admin_add_monitoring_panel.py
ln -sf $(pwd)/../monasca-ui/monitoring/conf/monitoring_policy.yaml \
$(pwd)/openstack_dashboard/conf/monitoring_policy.yaml
ln -sfF $(pwd)/../monasca-ui/monitoring $(pwd)/monitoring
./run_tests #load monasca-client into virtualenv
Set up Grafana 4.1
------------------
- The grafana4 branch of grafana is stable, as is master in
monasca-grafana-datasource.
- Copy ``monasca-grafana-datasource/`` into
``grafana/plugins/monasca-grafana-datasource/``.
- Use the grafana docs to build and deploy grafana:
- https://grafana.com/docs/project/building_from_source/
- https://grafana.com/docs/installation/configuration/
- Copy ``monasca-ui/grafana-dashboards/*`` to ``/public/dashboards/``
in your grafana deployment.
- Set ``GRAFANA_URL`` in the Horizon settings.
Start Server
------------
::
./run_tests.sh --runserver
Style checks
------------
To check if the code follows python coding style, run the following
command from the root directory of this project:
::
$ tox -e pep8
Coverage checks
---------------
To measure the code coverage, run the following command from the root
directory of this project:
::
$ tox -e cover
Unit tests
----------
To run all the unit test cases, run the following command from the root
directory of this project:
::
$ tox -e py3
For any further questions, please email openstack-discuss@lists.openstack.org
or join #openstack-dev on OFTC.

View File

@@ -1,2 +0,0 @@
[python: **.py]
[django: **/templates/**.html]

View File

@@ -1,2 +0,0 @@
[javascript: **.js]
[angular: **/static/**.html]

4
debian/changelog vendored
View File

@@ -1,4 +0,0 @@
monitoring-plugin (0.0.1) precise; urgency=low
* Initial Package creation

1
debian/compat vendored
View File

@@ -1 +0,0 @@
7

15
debian/control vendored
View File

@@ -1,15 +0,0 @@
Source: monitoring
Section: python
Priority: optional
Maintainer: HPCloud Monitoring <hpcs-mon@hp.com>
Build-Depends: debhelper (>= 7),
python (>= 2.6.6-3~),
python-setuptools
Standards-Version: 3.9.3
X-Python-Version: >= 2.6
Package: monitoring-plugin
Architecture: all
Section: python
Depends: ${misc:Depends}, ${python:Depends}, libpython2.7, python-pkg-resources, python-pbr, python-monclient
Description:Monitoring Plugin for Horizon

4
debian/copyright vendored
View File

@@ -1,4 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: *
Copyright: 2014, HP
License: Proprietary

4
debian/rules vendored
View File

@@ -1,4 +0,0 @@
#!/usr/bin/make -f
%:
dh $@ --with python2

View File

@@ -1,99 +0,0 @@
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (in the ARGS variable)
*
* Return a dashboard object, or a function
*/
'use strict';
// accessible variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
// Setup some variables
var dashboard;
// All url parameters are available via the ARGS object
var ARGS;
// Setup the metric dimensions.
var dimensions = [];
for (var key in ARGS) {
var isDimParam = key.startsWith("dim_");
if (isDimParam) {
var value = ARGS[key];
var dim = {
"key": key.substring(4),
"value": value
};
dimensions.push(dim);
}
}
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
};
// Set a title
dashboard.title = 'Alarm drilldown';
// Set default time
// time can be overridden in the url using from/to parameters, but this is
// handled automatically in grafana core during dashboard initialization
dashboard.time = {
from: "now-6h",
to: "now"
};
var rows = 1;
var metricName = '';
var hostname = '';
if(!_.isUndefined(ARGS.rows)) {
rows = parseInt(ARGS.rows, 10);
}
if(!_.isUndefined(ARGS.metric)) {
metricName = ARGS.metric;
}
if(!_.isUndefined(ARGS.hostname)) {
hostname = ARGS.hostname;
}
for (var i = 0; i < rows; i++) {
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: metricName,
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
targets: [
{
"aggregator": "avg",
"alias": hostname,
"dimensions": dimensions,
"metric": metricName,
"period": "300",
}
],
tooltip: {
shared: true
}
}
]
});
}
return dashboard;

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env python3
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from django.core.management import execute_from_command_line
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"monitoring.test.settings")
execute_from_command_line(sys.argv)

View File

View File

@@ -1,17 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
URL_PREFIX = 'horizon:monitoring:alarmdefs:'
TEMPLATE_PREFIX = 'monitoring/alarmdefs/'

View File

@@ -1,279 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2017 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from itertools import chain
import json
from django.template.loader import get_template
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from monitoring.alarmdefs import constants
from monitoring import api
def _get_metrics_call(request, offset=None):
return api.monitor.metrics_list(request, offset=offset)\
if offset else api.monitor.metrics_list(request)
def _get_metrics(request):
metrics_aggregation = _get_metrics_call(request)
if not metrics_aggregation:
return []
# offset defined as the id of last metric.
offset = metrics_aggregation[-1]['id']
while True:
metrics_batch = _get_metrics_call(request, offset)
if not metrics_batch:
break
metrics_aggregation += metrics_batch
offset = metrics_batch[-1]['id']
return json.dumps(metrics_aggregation)
def _get_notifications(request):
notifications = api.monitor.notification_list(request)
return [(notification['id'], notification) for notification in notifications]
class ExpressionWidget(forms.Widget):
func = json.dumps(
[('min', force_str(_('min'))), ('max', force_str(_('max'))),
('sum', force_str(_('sum'))), ('count', force_str(_('count'))),
('avg', force_str(_('avg'))), ('last', force_str(_('last')))]
)
comparators = [['>', '>'], ['>=', '>='], ['<', '<'], ['<=', '<=']]
operators = json.dumps(
[('AND', force_str(_('AND'))), ('OR', force_str(_('OR')))]
)
def __init__(self, initial, attrs=None):
super(ExpressionWidget, self).__init__(attrs)
self.initial = initial
def render(self, name, value, attrs=None, renderer=None):
final_attrs = self.build_attrs(attrs, {'name': name})
t = get_template(constants.TEMPLATE_PREFIX + 'expression_field.html')
local_attrs = {
'func': ExpressionWidget.func,
'comparators': ExpressionWidget.comparators,
'operators': ExpressionWidget.operators,
'metrics': self.metrics
}
local_attrs.update(final_attrs)
return t.render(local_attrs)
class ExpressionField(forms.CharField):
def _get_metrics(self):
return self._metrics
def _set_metrics(self, value):
self._metrics = self.widget.metrics = value
metrics = property(_get_metrics, _set_metrics)
class MatchByWidget(forms.Widget):
def __init__(self, initial, attrs=None):
super(MatchByWidget, self).__init__(attrs)
self.initial = initial
def render(self, name, value, attrs=None, renderer=None):
final_attrs = self.build_attrs(attrs, {'name': name})
t = get_template(constants.TEMPLATE_PREFIX + 'match_by_field.html')
local_attrs = {'service': ''}
local_attrs.update(final_attrs)
return t.render(local_attrs)
class NotificationField(forms.MultiValueField):
def __init__(self, *args, **kwargs):
super(NotificationField, self).__init__(fields=(), *args, **kwargs)
def _get_choices(self):
return self._choices
def _set_choices(self, value):
# Setting choices also sets the choices on the widget.
# choices can be any iterable, but we call list() on it because
# it will be consumed more than once.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices)
def compress(self, data_list):
return data_list
def clean(self, value):
return value
class NotificationCreateWidget(forms.Select):
def __init__(self, *args, **kwargs):
super(NotificationCreateWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=(), renderer=None):
final_attrs = self.build_attrs(attrs, {'name': name})
tpl = get_template(constants.TEMPLATE_PREFIX + 'notification_field.html')
selected = {}
for item in value if value else []:
selected[item['id']] = {'alarm': item['alarm'],
'ok': item['ok'],
'undetermined': item['undetermined']}
data = []
for pk, notification in chain(self.choices, choices):
nt_label = notification['name']
nt_address = notification['address']
nt_type = notification['type']
if pk in selected:
actions = selected[pk]
data.append((pk, nt_label, nt_type, nt_address, actions['alarm'],
actions['ok'], actions['undetermined'], True))
else:
data.append((pk, nt_label, nt_type, nt_address, True, True, True, False))
local_attrs = {'data': json.dumps(data)}
local_attrs.update(final_attrs)
return tpl.render(local_attrs)
def value_from_datadict(self, data, files, name):
return [{"id": _id} for _id in data.getlist(name)]
class EditAlarmForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(EditAlarmForm, self).__init__(request, *args, **kwargs)
self._init_fields(readOnly=False)
self.set_notification_choices(request)
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def _init_fields(self, readOnly=False, create=False, initial=None):
required = True
textWidget = None
choiceWidget = forms.Select
if create:
expressionWidget = ExpressionWidget(initial)
matchByWidget = MatchByWidget(initial)
notificationWidget = NotificationCreateWidget()
else:
expressionWidget = textWidget
matchByWidget = forms.TextInput(attrs={'readonly': 'readonly'})
notificationWidget = NotificationCreateWidget()
self.fields['name'] = forms.CharField(label=_("Name"),
required=required,
max_length=250,
widget=textWidget,
help_text=_("An unique name of the alarm."))
self.fields['expression'] = forms.CharField(label=_("Expression"),
required=required,
widget=expressionWidget,
help_text=_("An alarm expression."))
self.fields['match_by'] = forms.CharField(label=_("Match by"),
required=False,
widget=matchByWidget,
help_text=_("The metric dimensions used "
"to create unique alarms."))
self.fields['description'] = forms.CharField(label=_("Description"),
required=False,
widget=textWidget,
help_text=_("A description of an alarm."))
sev_choices = [("LOW", _("Low")),
("MEDIUM", _("Medium")),
("HIGH", _("High")),
("CRITICAL", _("Critical"))]
self.fields['severity'] = forms.ChoiceField(label=_("Severity"),
choices=sev_choices,
initial=sev_choices[0],
widget=choiceWidget,
required=False,
help_text=_("Severity of an alarm. "
"Must be either LOW, MEDIUM, HIGH "
"or CRITICAL. Default is LOW."))
if not create:
self.fields['actions_enabled'] = \
forms.BooleanField(label=_("Notifications Enabled"),
required=False,
initial=True)
self.fields['notifications'] = NotificationField(
label=_("Notifications"),
required=False,
widget=notificationWidget,
help_text=_("Notification methods. "
"Notifications can be sent when an alarm state transition occurs."))
self.fields['alarm_actions'] = NotificationField(
label=_("Alarm Actions"),
widget=forms.MultipleHiddenInput())
self.fields['ok_actions'] = NotificationField(
label=_("OK Actions"),
widget=forms.MultipleHiddenInput())
self.fields['undetermined_actions'] = NotificationField(
label=_("Undetermined Actions"),
widget=forms.MultipleHiddenInput())
def set_notification_choices(self, request):
try:
notifications = api.monitor.notification_list(request)
except Exception:
notifications = []
exceptions.handle(request,
_('Unable to retrieve notifications.'))
notification_choices = [
(notification['id'], notification) for notification in notifications]
self.fields['notifications'].choices = notification_choices
def handle(self, request, data):
try:
alarm_def = api.monitor.alarmdef_get(request, self.initial['id'])
api.monitor.alarmdef_update(
request,
alarm_id=self.initial['id'],
severity=data['severity'],
name=data['name'],
expression=data['expression'],
description=data['description'],
match_by=alarm_def['match_by'],
actions_enabled=data['actions_enabled'],
alarm_actions=data['alarm_actions'],
ok_actions=data['ok_actions'],
undetermined_actions=data['undetermined_actions'],
)
messages.success(request,
_('Alarm definition has been updated.'))
except Exception:
exceptions.handle(request,
_('Unable to update alarm definition.'))
return False
return True

View File

@@ -1,27 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
import horizon
from monitoring import dashboard
class AlarmDefinitions(horizon.Panel):
name = _("Alarm Definitions")
slug = 'alarmdefs'
dashboard.Monitoring.register(AlarmDefinitions)

View File

@@ -1,110 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from monitoring.alarmdefs import constants
from monitoring import api
class CreateAlarm(tables.LinkAction):
name = "create_alarm"
verbose_name = _("Create Alarm Definition")
classes = ("ajax-modal", "btn-create")
icon = "plus"
policy_rules = (("alarm", "alarm:create"),)
ajax = True
def get_link_url(self):
return reverse(constants.URL_PREFIX + 'alarm_create', args=())
def allowed(self, request, datum=None):
return True
class EditAlarm(tables.LinkAction):
name = "edit_alarm"
verbose_name = _("Edit Alarm Definition")
classes = ("ajax-modal", "btn-create")
def get_link_url(self, datum):
return reverse(constants.URL_PREFIX + 'alarm_edit', args=(datum['id'], ))
def allowed(self, request, datum=None):
return True
class DeleteAlarm(tables.DeleteAction):
name = "delete_alarm"
verbose_name = _("Delete Alarm Definition")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete Alarm Definition",
"Delete Alarm Definitions",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Deleted Alarm Definition",
"Deleted Alarm Definitions",
count
)
def allowed(self, request, datum=None):
return True
def delete(self, request, obj_id):
api.monitor.alarmdef_delete(request, obj_id)
class AlarmsFilterAction(tables.FilterAction):
def filter(self, table, alarms, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [alarm for alarm in alarms
if q in alarm['name'].lower()]
class AlarmsTable(tables.DataTable):
target = tables.Column('name', verbose_name=_('Name'),
link=constants.URL_PREFIX + 'alarm_detail',
)
description = tables.Column('description', verbose_name=_('Description'))
enabled = tables.Column('actions_enabled',
verbose_name=_('Notifications Enabled'))
def get_object_id(self, obj):
return obj['id']
def get_object_display(self, obj):
return obj['name']
class Meta(object):
name = "alarms"
verbose_name = _("Alarm Definitions")
row_actions = (EditAlarm,
DeleteAlarm,
)
table_actions = (CreateAlarm,
AlarmsFilterAction,
DeleteAlarm,
)

View File

@@ -1,23 +0,0 @@
{% load i18n %}
{% block help_message %}
<div class="clearfix center-block">
<p class="text-info">
{% blocktrans %}
The Name field is used to identify the alarm definition.
{% endblocktrans %}
</p>
<p class="text-info">
{% blocktrans %}
The Description field can be used to describe alarm definition's
purpose.
{% endblocktrans %}
</p>
<p class="text-info">
{% blocktrans %}
The Severity field allows to specify the importance of alarm
definition.
{% endblocktrans %}
</p>
</div>
{% endblock %}

View File

@@ -1,19 +0,0 @@
{% load i18n %}
{% block help_message %}
<div class="clearfix">
<p class="text-info">
{% blocktrans %}
Each alarm definition is defined by its expression composed out of
mathematical function, metric, time, times ,threshold and comparator for
metric's value and the threshold. Time is the number of seconds for the measurement
to be done. They can only be in a multiple of 60. Times is how many times in a row
that expression must be true before triggering the alarm. Both time and times are
optional and default to 60 and 1 respectively. Additionally it is possible to narrow
evaluation of the alarm to certain entities by choosing their
dimensions. The deterministic alarms never enter UNDETERMINED state.
Use them for metrics that are received sporadically.
{% endblocktrans %}
</p>
</div>
{% endblock %}

View File

@@ -1,18 +0,0 @@
{% load i18n %}
{% block help_message %}
<div class="clearfix">
<p class="text-info">
{% blocktrans %}
The Notifications field contains the list of Notifications that should
be sent when transitioning to another state.
{% endblocktrans %}
</p>
<p class="text-info">
{% blocktrans %}
If for some transitions notifications should not be sent they can be
disabled.
{% endblocktrans %}
</p>
</div>
{% endblock %}

View File

@@ -1,47 +0,0 @@
{% load i18n sizeformat %}
<div class="info detail">
<h4>{% trans "Info" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ alarm.name|default:_("None") }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ alarm.description }}</dd>
<dt>{% trans "Expression" %}</dt>
<dd>{{ alarm.expression }}</dd>
<dt>{% trans "Deterministic" %}</dt>
<dd>{{ alarm.deterministic }}</dd>
<dt>{% trans "Severity" %}</dt>
<dd>{{ alarm.severity }}</dd>
<dt>{% trans "Notifications Enabled" %}</dt>
<dd>{{ alarm.actions_enabled }}</dd>
</dl>
</div>
<div class="specs detail">
<h4>{% trans "Notifications" %}</h4>
<hr class="header_rule">
<table class="table table-condensed">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Address" %}</th>
<th>{% trans "Alarm" %}</th>
<th>{% trans "OK" %}</th>
<th>{% trans "Undetermined" %}</th>
</tr>
</thead>
{% for row in alarm.notifications %}
<tr>
<td>{{ row.name }}</td>
<td>{{ row.type }}</td>
<td>{{ row.address }}</td>
<td>{{ row.alarm }}</td>
<td>{{ row.ok }}</td>
<td>{{ row.undetermined }}</td>
</tr>
{% endfor %}
</table>
</div>

View File

@@ -1,29 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}edit_alarm_form{% endblock %}
{% block form_action %}{{ action_url }}{% endblock %}
{% block modal-header %}{% trans "Edit Alarm Definition" %}{% endblock %}
{% block modal-body %}
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
The Name field is used to identify the alarm definition.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Expression field which if true, triggers a notification to be sent.
See <a href="https://github.com/openstack/monasca-api/blob/master/docs/monasca-api-spec.md#alarm-definition-expressions" target="_blank">Alarm Expressions</a> for how to write an expression.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Notifications field contains the list of Notifications that should be sent when transitioning to another ALARM state.
{% endblocktrans %}</p>
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% endblock %}
{% block modal-footer %}
<a href="{{ cancel_url }}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
<input class="btn btn-primary" type="submit" value="{% trans "Save" %}" />
{% endblock %}

View File

@@ -1,35 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% load compress %}
{% block title %}{% trans 'Alarm Definitions' %}{% endblock %}
{% block css %}
{% include "_stylesheets.html" %}
<link href='{{ STATIC_URL }}monitoring/css/ng-tags-input.css' type="text/css" rel="stylesheet"/>
{% endblock %}
{% block breadcrumb_nav %}
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate">{% trans "Monitoring" %}</li>
<li class="breadcrumb-item-truncate active">{% trans "Alarm Definitions" %}</li>
</ol>
{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Alarm Definitions") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
<div class="pagination">
<span class="step-links">
{% if prev_page_offset != None %}
<a href="?page_offset={{ prev_page_offset }}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Previous Page' %}</a>
{% endif %}
{% if page_offset %}
<a href="?page_offset={{ page_offset}}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Next Page' %}</a>
{% endif %}
</span>
</div>
{% endblock %}

View File

@@ -1,23 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Alarm Definition Details' %}{% endblock %}
{% block breadcrumb_nav %}
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate">{% trans "Monitoring" %}</li>
<li class="breadcrumb-item-truncate"><a href="/dashboard/monitoring/alarmdefs/">{% trans "Alarm Definitions" %}</a></li>
<li class="breadcrumb-item-truncate active">{{ alarm.name|default:_("None") }}</li>
</ol>
{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Alarm Definition Details") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include 'monitoring/alarmdefs/_detail.html' %}
</div>
</div>
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Edit Alarm Definitions' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Edit Alarm Definitions" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/alarmdefs/_edit.html' %}
{% endblock %}

View File

@@ -1,4 +0,0 @@
<mon-alarm-expression metrics="{{ metrics|default:'[]' }}"
functions="{{ func }}"
comparators="{{ comparators }}"
operators="{{ operators }}"></mon-alarm-expression>

View File

@@ -1,15 +0,0 @@
{% load i18n %}
<div ng-controller="alarmMatchByController as ctrl">
<input type="hidden" name="{{ name }}" id="id_{{ name }}"/>
<tags-input id="dimkey-chooser"
ng-model="ctrl.matchByTags"
placeholder="{% trans 'Add a match by' %}"
add-from-autocomplete-only="true"
on-tag-added="ctrl.saveDimKey()"
on-tag-removed="ctrl.saveDimKey()">
<auto-complete source="ctrl.possibleDimKeys($query)"
max-results-to-show="30"
min-length="1">
</auto-complete>
</tags-input>
</div>

View File

@@ -1,51 +0,0 @@
{% load i18n %}
<div ng-controller="alarmNotificationFieldController as ctrl" ng-init="ctrl.init('{{ data }}')">
<div ng-if="ctrl.empty">
{% trans 'No notifications available.' %} (<a href="{% url 'horizon:monitoring:notifications:index' %}">{% trans 'Add' %}</a>)
</div>
<table ng-if="ctrl.list.length" class="table table-condensed" id="notification_table">
<thead>
<tr>
<th class="name">{% trans 'Name' %}</th>
<th class="type">{% trans 'Type' %}</th>
<th class="address">{% trans 'Address' %}</th>
<th class="alarm">{% trans 'Alarm' %}</th>
<th class="ok">{% trans 'OK' %}</th>
<th class="undetermined">{% trans 'Undetermined' %}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="notify in ctrl.list">
<td class="name">{$ notify.name $}</td>
<td class="type">{$ notify.type $}</td>
<td class="address">{$ notify.address $}</td>
<td class="alarm">
<input type="checkbox" ng-model="notify.alarm">
<input type="hidden" name="alarm_actions" value="{$ notify.id $}" ng-if="notify.alarm"/>
</td>
<td class="ok">
<input type="checkbox" ng-model="notify.ok">
<input type="hidden" name="ok_actions" value="{$ notify.id $}" ng-if="notify.ok"/>
</td>
<td class="undetermined">
<input type="checkbox" ng-model="notify.undetermined" ng-disabled="ctrl.isDeterministic">
<input type="hidden" name="undetermined_actions" value="{$ notify.id $}" ng-if="notify.undetermined"/>
</td>
<td>
<a href="#" title="{% trans 'Remove' %}" ng-click="ctrl.remove(notify.id)">X</a>
</td>
</tr>
</tbody>
</table>
<div ng-if="ctrl.select.options.length">
<select ng-model="ctrl.select.model" ng-options="item.id as item.label for item in ctrl.select.options">
<option value="">{% trans "Select Notification" %}</option>
</select>
<a href="#" title="{% trans 'Add' %}" class="btn" ng-click="ctrl.add()">{% trans 'Add' %}</a>
</div>
</div>

View File

@@ -1,15 +0,0 @@
<noscript><h3>{{ step }}</h3></noscript>
<div class="container-fluid">
<div class="row">
<!-- hide if window gets very small to save some space -->
<div class="col-sm-12 col-md-12 col-lg-12 hidden-xs">
{{ step.get_help_text }}
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
{% include "horizon/common/_form_fields.html" %}
</div>
</div>
</div>

View File

@@ -1,114 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2016-2017 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from unittest.mock import patch
from monitoring.alarmdefs import constants
from monitoring.alarmdefs import views
from monitoring.alarmdefs import workflows
from monitoring.test import helpers
INDEX_URL = reverse(constants.URL_PREFIX + 'index')
CREATE_URL = reverse(constants.URL_PREFIX + 'alarm_create', args=())
DETAIL_URL = reverse(constants.URL_PREFIX + 'alarm_detail', args=('12345',))
EDIT_URL = reverse(constants.URL_PREFIX + 'alarm_edit', args=('12345',))
class AlarmDefinitionsTest(helpers.TestCase):
def test_alarmdefs_get(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarmdef_list'],
'alarmdef_list.return_value': [],
}) as mock:
res = self.client.get(INDEX_URL)
self.assertEqual(mock.alarmdef_list.call_count, 2)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/alarm.html')
def test_alarmdefs_create(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['notification_list', 'metrics_list'],
'notification_list.return_value': [],
'metrics_list.return_value': [],
}) as mock:
res = self.client.get(CREATE_URL)
self.assertEqual(mock.notification_list.call_count, 1)
self.assertEqual(mock.metrics_list.call_count, 1)
workflow = res.context['workflow']
self.assertTemplateUsed(res, views.AlarmCreateView.template_name)
self.assertEqual(res.context['workflow'].name,
workflows.AlarmDefinitionWorkflow.name)
self.assertEqual(
[repr(step) for step in workflow.steps],
['<SetDetailsStep: setalarmdefinitionaction>',
'<SetExpressionStep: setalarmdefinitionexpressionaction>',
'<SetNotificationsStep: setalarmnotificationsaction>'])
# verify steps
step = workflow.get_step('setalarmdefinitionaction')
self.assertIsNotNone(step)
step = workflow.get_step('setalarmdefinitionexpressionaction')
self.assertIsNotNone(step)
step = workflow.get_step('setalarmnotificationsaction')
self.assertIsNotNone(step)
self.assertContains(res, '<select class="form-control" '
'id="id_severity"')
self.assertContains(res, '<mon-alarm-expression')
self.assertContains(res, '<input type="hidden" name="alarm_actions"')
self.assertContains(res, '<input type="hidden" name="ok_actions"')
self.assertContains(res, '<input type="hidden" '
'name="undetermined_actions"')
def test_alarmdefs_detail(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarmdef_get'],
'alarmdef_get.return_value': {
'alarm_actions': [],
'ok_actions': [],
'undetermined_actions': [],
'match_by': [],
}
}) as mock:
res = self.client.get(DETAIL_URL)
self.assertEqual(mock.alarmdef_get.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/_detail.html')
def test_alarmdefs_edit(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarmdef_get'],
'alarmdef_get.return_value': {
'alarm_actions': [],
'ok_actions': [],
'undetermined_actions': [],
'match_by': [],
}
}) as mock:
res = self.client.get(EDIT_URL)
self.assertEqual(mock.alarmdef_get.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/alarmdefs/_edit.html')

View File

@@ -1,29 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import re_path
from monitoring.alarmdefs import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^alarm/create$',
views.AlarmCreateView.as_view(),
name='alarm_create'),
re_path(r'^(?P<id>[^/]+)/alarm_detail/$',
views.AlarmDetailView.as_view(),
name='alarm_detail'),
re_path(r'^alarm/(?P<id>[^/]+)/alarm_edit/$',
views.AlarmEditView.as_view(),
name='alarm_edit')
]

View File

@@ -1,264 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.contrib import messages
from django.core.paginator import EmptyPage
from django.core.paginator import Paginator
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon.utils import functions as utils
from horizon import workflows
from monascaclient import exc
from monitoring.alarmdefs import constants
from monitoring.alarmdefs import forms as alarm_forms
from monitoring.alarmdefs import tables as alarm_tables
from monitoring.alarmdefs import workflows as alarm_workflows
from monitoring import api
from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
PREV_PAGE_LIMIT = 100
class IndexView(tables.DataTableView):
table_class = alarm_tables.AlarmsTable
template_name = constants.TEMPLATE_PREFIX + 'alarm.html'
def get_data(self):
page_offset = self.request.GET.get('page_offset')
results = []
if page_offset is None:
page_offset = 0
limit = utils.get_page_size(self.request)
try:
results = api.monitor.alarmdef_list(self.request, page_offset, limit)
paginator = Paginator(results, limit)
results = paginator.page(1)
except EmptyPage:
results = paginator.page(paginator.num_pages)
except Exception as ex:
LOG.exception(str(ex))
messages.error(self.request, _("Could not retrieve alarm definitions"))
return results
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(IndexView, self).get_context_data(**kwargs)
num_results = 0
contacts = []
prev_page_stack = []
page_offset = self.request.GET.get('page_offset')
if 'prev_page_stack' in self.request.session:
prev_page_stack = self.request.session['prev_page_stack']
if page_offset is None:
page_offset = 0
prev_page_stack = []
else:
page_offset = int(page_offset)
limit = utils.get_page_size(self.request)
try:
# To judge whether there is next page, get limit + 1
results = api.monitor.alarmdef_list(self.request, page_offset,
limit + 1)
num_results = len(results)
paginator = Paginator(results, limit)
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve alarm definitions"))
return context
context["contacts"] = contacts
if num_results < limit + 1:
context["page_offset"] = None
else:
context["page_offset"] = page_offset + limit
if page_offset in prev_page_stack:
index = prev_page_stack.index(page_offset)
prev_page_stack = prev_page_stack[0:index]
prev_page_offset = prev_page_stack[-1] if prev_page_stack else None
if prev_page_offset is not None:
context["prev_page_offset"] = prev_page_offset
if len(prev_page_stack) > PREV_PAGE_LIMIT:
del prev_page_stack[0]
prev_page_stack.append(page_offset)
self.request.session['prev_page_stack'] = prev_page_stack
return context
class AlarmCreateView(workflows.WorkflowView):
workflow_class = alarm_workflows.AlarmDefinitionWorkflow
def transform_alarm_data(obj):
obj['match_by'] = ','.join(obj['match_by'])
return obj
class AlarmDetailView(TemplateView):
template_name = constants.TEMPLATE_PREFIX + 'detail.html'
def get_object(self):
id = self.kwargs['id']
try:
if hasattr(self, "_object"):
return self._object
self._object = None
self._object = api.monitor.alarmdef_get(self.request, id)
notifications = []
# Fetch the notification object for each alarm_actions
all_actions = set(self._object["alarm_actions"] +
self._object["ok_actions"] +
self._object["undetermined_actions"])
for id in all_actions:
try:
notification = api.monitor.notification_get(
self.request,
id)
notification['alarm'] = False
notification['ok'] = False
notification['undetermined'] = False
notifications.append(notification)
# except exceptions.NOT_FOUND:
except exc.HttpError:
msg = _("Notification %s has already been deleted.") % id
notifications.append({"id": id,
"name": str(msg),
"type": "",
"address": ""})
for notification in notifications:
if notification['id'] in self._object["alarm_actions"]:
notification['alarm'] = True
if notification['id'] in self._object["ok_actions"]:
notification['ok'] = True
if notification['id'] in self._object["undetermined_actions"]:
notification['undetermined'] = True
self._object["notifications"] = notifications
return self._object
except Exception:
redirect = self.get_success_url()
exceptions.handle(self.request,
_('Unable to retrieve alarm details.'),
redirect=redirect)
return None
def get_initial(self):
self.alarm = self.get_object()
return transform_alarm_data(self.alarm)
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(AlarmDetailView, self).get_context_data(**kwargs)
self.get_initial()
context["alarm"] = self.alarm
context["cancel_url"] = self.get_success_url()
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index')
class AlarmEditView(forms.ModalFormView):
form_class = alarm_forms.EditAlarmForm
template_name = constants.TEMPLATE_PREFIX + 'edit.html'
def get_object(self):
id = self.kwargs['id']
try:
if hasattr(self, "_object"):
return self._object
self._object = None
self._object = api.monitor.alarmdef_get(self.request, id)
notifications = []
# Fetch the notification object for each alarm_actions
all_actions = set(self._object["alarm_actions"] +
self._object["ok_actions"] +
self._object["undetermined_actions"])
for id in all_actions:
try:
notification = api.monitor.notification_get(
self.request,
id)
notification['alarm'] = False
notification['ok'] = False
notification['undetermined'] = False
notifications.append(notification)
# except exceptions.NOT_FOUND:
except exc.HttpError:
msg = _("Notification %s has already been deleted.") % id
messages.warning(self.request, msg)
for notification in notifications:
if notification['id'] in self._object["alarm_actions"]:
notification['alarm'] = True
if notification['id'] in self._object["ok_actions"]:
notification['ok'] = True
if notification['id'] in self._object["undetermined_actions"]:
notification['undetermined'] = True
del self._object["alarm_actions"]
del self._object["ok_actions"]
del self._object["undetermined_actions"]
self._object["notifications"] = notifications
return self._object
except Exception:
redirect = self.get_success_url()
exceptions.handle(self.request,
_('Unable to retrieve alarm details.'),
redirect=redirect)
return None
def get_initial(self):
self.alarm = self.get_object()
return transform_alarm_data(self.alarm)
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(AlarmEditView, self).get_context_data(**kwargs)
id = self.kwargs['id']
context["cancel_url"] = self.get_success_url()
context["action_url"] = reverse(constants.URL_PREFIX + 'alarm_edit',
args=(id,))
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index',
args=())

View File

@@ -1,230 +0,0 @@
# Copyright 2016 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
from django.views.decorators.debug import sensitive_variables
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
from horizon import workflows
from monitoring.alarmdefs import constants
from monitoring.alarmdefs import forms as ad_forms
from monitoring import api
class SetAlarmNotificationsAction(workflows.Action):
notifications = ad_forms.NotificationField(
label=_('Notifications'),
required=False,
widget=ad_forms.NotificationCreateWidget(),
help_text=_('Notification methods. '
'Notifications can be sent when an alarm '
'state transition occurs.'))
alarm_actions = ad_forms.NotificationField(
label=_("Alarm Actions"),
required=False,
widget=forms.MultipleHiddenInput()
)
ok_actions = ad_forms.NotificationField(
label=_("OK Actions"),
required=False,
widget=forms.MultipleHiddenInput()
)
undetermined_actions = ad_forms.NotificationField(
label=_("Undetermined Actions"),
required=False,
widget=forms.MultipleHiddenInput()
)
class Meta(object):
name = _('Notifications')
help_text_template = ("monitoring/alarmdefs/"
"_create_ad_notification_help.html")
def __init__(self, request, context, *args, **kwargs):
super(SetAlarmNotificationsAction, self).__init__(
request, context, *args, **kwargs
)
try:
notifications = ad_forms._get_notifications(request)
self.fields['notifications'].choices = notifications
except Exception:
exceptions.handle(request,
_('Unable to retrieve notifications.'))
_SEVERITY_CHOICES = [("LOW", _("Low")),
("MEDIUM", _("Medium")),
("HIGH", _("High")),
("CRITICAL", _("Critical"))]
class SetAlarmDefinitionAction(workflows.Action):
name = forms.CharField(label=_('Name'),
required=True,
max_length=250,
help_text=_('An unique name of the alarm.'))
description = forms.CharField(label=_('Description'),
required=False,
help_text=_('A description of an alarm.'))
severity = forms.ChoiceField(label=_('Severity'),
choices=_SEVERITY_CHOICES,
initial=_SEVERITY_CHOICES[0],
widget=forms.SelectWidget,
required=False,
help_text=_('Severity of an alarm. Must be '
'either LOW, MEDIUM, HIGH '
'or CRITICAL. Default is LOW.'))
class Meta(object):
name = _('Details')
help_text_template = ("monitoring/alarmdefs/"
"_create_ad_details_help.html")
def clean(self):
cleaned_data = super(SetAlarmDefinitionAction, self).clean()
alarm_def_name = cleaned_data.get('name', '').strip()
if not alarm_def_name:
return
is_name_valid = self._is_alarm_def_name_unique_validator(
alarm_def_name)
if not is_name_valid:
self.add_error('name',
_('Alarm definition with %s name already exists')
% alarm_def_name)
def _is_alarm_def_name_unique_validator(self, value):
try:
ret = self._get_alarm_def_by_name(value)
return not (ret and len(ret))
except Exception:
exceptions.handle(request=self.request,
message=_('Failed to validate name'),
ignore=True)
return True
@memoized.memoized_method
def _get_alarm_def_by_name(self, value):
return api.monitor.alarmdef_get_by_name(self.request, value)
class SetAlarmDefinitionExpressionAction(workflows.Action):
expression = ad_forms.ExpressionField(label=_("Expression"),
required=True,
widget=ad_forms.ExpressionWidget(''),
help_text=_(
'An alarm expression.'))
match_by = forms.CharField(label=_('Match by'),
required=False,
widget=ad_forms.MatchByWidget(''),
help_text=_('The metric dimensions used '
'to create unique alarms.'))
class Meta(object):
name = _('Expression')
help_text_template = ("monitoring/alarmdefs/"
"_create_ad_expression_help.html")
def __init__(self, request, context, *args, **kwargs):
super(SetAlarmDefinitionExpressionAction, self).__init__(request,
context,
*args,
**kwargs)
try:
self.fields['expression'].metrics = ad_forms._get_metrics(request)
except Exception:
exceptions.handle(request, _('Unable to retrieve metrics'))
class SetDetailsStep(workflows.Step):
action_class = SetAlarmDefinitionAction
contributes = ('name', 'description', 'severity')
template_name = 'monitoring/alarmdefs/workflow_step.html'
class SetExpressionStep(workflows.Step):
action_class = SetAlarmDefinitionExpressionAction
contributes = ('expression', 'match_by')
template_name = 'monitoring/alarmdefs/workflow_step.html'
def contribute(self, data, context):
context = (super(SetExpressionStep, self)
.contribute(data, context))
if 'expression' in data and data['expression']:
context['expression'] = data['expression'].strip()
if 'match_by' in data and data['match_by']:
context['match_by'] = context['match_by'].split(',')
else:
context['match_by'] = []
return context
class SetNotificationsStep(workflows.Step):
action_class = SetAlarmNotificationsAction
contributes = ('alarm_actions', 'ok_actions', 'undetermined_actions')
template_name = 'monitoring/alarmdefs/workflow_step.html'
class AlarmDefinitionWorkflow(workflows.Workflow):
slug = 'create_alarm_definition'
name = _('Create Alarm Definition')
finalize_button_name = _('Create Alarm Definition')
success_message = _('Alarm definition %s has been created')
failure_message = _('Unable to create alarm definition %s')
success_url = constants.URL_PREFIX + 'index'
wizard = True
default_steps = (
SetDetailsStep,
SetExpressionStep,
SetNotificationsStep
)
def format_status_message(self, message):
name = self.context.get('name', _('Unknown name'))
return message % name
@sensitive_variables('alarm_actions',
'ok_actions',
'undetermined_actions')
def handle(self, request, context):
try:
api.monitor.alarmdef_create(
request,
name=context['name'],
expression=context['expression'],
description=context['description'],
severity=context['severity'],
match_by=context['match_by'],
alarm_actions=context['alarm_actions'],
ok_actions=context['ok_actions'],
undetermined_actions=context['undetermined_actions'],
)
except Exception:
exceptions.handle(request,
_('Unable to create alarm definition.'),
escalate=True)
return False
return True

View File

@@ -1,25 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
URL_PREFIX = 'horizon:monitoring:alarms:'
TEMPLATE_PREFIX = 'monitoring/alarms/'
prefix = settings.STATIC_URL or ''
CRITICAL_ICON = prefix + 'monitoring/img/critical-icon.png'
WARNING_ICON = prefix + 'monitoring/img/warning-icon.png'
OK_ICON = prefix + 'monitoring/img/ok-icon.png'
UNKNOWN_ICON = prefix + 'monitoring/img/unknown-icon.png'
NOTFOUND_ICON = prefix + 'monitoring/img/notfound-icon.png'

View File

@@ -1,303 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2017 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
from django import forms as django_forms
from django.template.loader import get_template
from django.utils import html
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from monitoring.alarms import constants
from monitoring import api
class ExpressionWidget(forms.Widget):
def __init__(self, initial, attrs):
super(ExpressionWidget, self).__init__(attrs)
self.initial = initial
def render(self, name, value, attrs):
final_attrs = self.build_attrs(attrs, {'name': name})
if value:
dim = value
else:
if 'all' in self.initial['service']:
dim = ''
else:
dim = next(("%s=%s" % (k, v) for k, v in self.initial.items()), '')
t = get_template(constants.TEMPLATE_PREFIX + 'expression_field.html')
local_attrs = {'service': dim}
local_attrs.update(final_attrs)
return t.render(local_attrs)
class SimpleExpressionWidget(django_forms.MultiWidget):
def __init__(self, initial, attrs=None):
comparators = [('>', '>'), ('>=', '>='), ('<', '<'), ('<=', '<=')]
func = [('min', _('min')), ('max', _('max')), ('sum', _('sum')),
('count', _('count')), ('avg', _('avg'))]
_widgets = (
django_forms.widgets.Select(attrs=attrs, choices=func),
ExpressionWidget(initial, attrs={}),
django_forms.widgets.Select(attrs=attrs, choices=comparators),
django_forms.widgets.TextInput(),
)
super(SimpleExpressionWidget, self).__init__(_widgets, attrs)
def decompress(self, expr):
if expr:
return re.search(r'^(\w+)\((.*)\) ([<>=]*) (.*)$', expr).groups()
else:
return [None, None, None, None]
def format_output(self, rendered_widgets):
return ''.join(rendered_widgets)
def value_from_datadict(self, data, files, name):
values = [
widget.value_from_datadict(data, files, name + '_%s' % i)
for i, widget in enumerate(self.widgets)]
try:
expression = '%s(%s) %s %s' % (values[0],
values[1],
values[2],
values[3])
except ValueError:
return ''
else:
return expression
class NotificationField(forms.MultiValueField):
def __init__(self, *args, **kwargs):
super(NotificationField, self).__init__(fields=(), *args, **kwargs)
def _get_choices(self):
return self._choices
def _set_choices(self, value):
# Setting choices also sets the choices on the widget.
# choices can be any iterable, but we call list() on it because
# it will be consumed more than once.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices)
def compress(self, data_list):
return data_list
def clean(self, value):
return value
class NotificationCreateWidget(forms.Select):
def __init__(self, *args, **kwargs):
super(NotificationCreateWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=()):
output = '<table id="notification_table" ' + \
'class="table table-condensed">'
output += '<thead><tr><th>%s</th></tr></thead>' % \
str(_("Name"))
if value:
idx = 1
for notification in value:
output += '<tr><td>'
output += ('<select id="id_notifications_%d" ' +
'name="notifications_%d"> ') % (idx, idx)
options = self.render_options(
choices,
[notification['id']])
if options:
output += options
output += '</select>'
output += '<td><a href="" id="remove_notif_button">X</a></td>'
output += '</td></tr>'
idx += 1
else:
output += '<tr><td>'
output += '<select id="id_notifications_1" '
output += 'name="notifications_1"> '
options = self.render_options(choices, [value])
if options:
output += options
output += '</select>'
output += '<td><a href="" id="remove_notif_button">X</a></td>'
output += '</td></tr>'
output += '</table>'
label = str(_("+ Add more"))
output += '<a href="" id="add_notification_button">%s</a>' % (label)
return html.format_html(output)
def value_from_datadict(self, data, files, name):
notifications = []
i = 0
while True:
i += 1
notification_id = "%s_%d" % (name, i)
if notification_id in data:
if len(data[notification_id]) > 0:
notifications.append({"id":
data[notification_id]})
else:
break
return notifications
class BaseAlarmForm(forms.SelfHandlingForm):
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def _init_fields(self, readOnly=False, create=False, initial=None):
required = True
textWidget = None
textAreaWidget = forms.Textarea(attrs={'class': 'large-text-area'})
choiceWidget = forms.Select
if create:
expressionWidget = SimpleExpressionWidget(initial)
notificationWidget = NotificationCreateWidget()
else:
expressionWidget = textAreaWidget
notificationWidget = NotificationCreateWidget()
self.fields['name'] = forms.CharField(label=_("Name"),
required=required,
max_length=250,
widget=textWidget)
self.fields['expression'] = forms.CharField(label=_("Expression"),
required=required,
widget=expressionWidget)
self.fields['description'] = forms.CharField(label=_("Description"),
required=False,
widget=textAreaWidget)
sev_choices = [("LOW", _("Low")),
("MEDIUM", _("Medium")),
("HIGH", _("High")),
("CRITICAL", _("Critical"))]
self.fields['severity'] = forms.ChoiceField(label=_("Severity"),
choices=sev_choices,
widget=choiceWidget,
required=False)
self.fields['state'] = forms.CharField(label=_("State"),
required=False,
widget=textWidget)
self.fields['actions_enabled'] = \
forms.BooleanField(label=_("Notifications Enabled"),
required=False,
initial=True)
self.fields['notifications'] = NotificationField(
label=_("Notifications"),
required=False,
widget=notificationWidget)
def set_notification_choices(self, request):
try:
notifications = api.monitor.notification_list(request)
except Exception:
notifications = []
exceptions.handle(request,
_('Unable to retrieve notifications.'))
notification_choices = [(notification['id'], notification['name'])
for notification in notifications]
if notification_choices:
if len(notification_choices) > 1:
notification_choices.insert(
0, ("", str(_("Select Notification"))))
else:
notification_choices.insert(
0, ("", str(_("No notifications available."))))
self.fields['notifications'].choices = notification_choices
def clean_expression(self):
data = self.cleaned_data['expression']
value = data.split(' ')[2]
if not value.isdigit():
raise forms.ValidationError("Value must be an integer")
# Always return the cleaned data, whether you have changed it or
# not.
return data
class CreateAlarmForm(BaseAlarmForm):
def __init__(self, request, *args, **kwargs):
super(CreateAlarmForm, self).__init__(request, *args, **kwargs)
super(CreateAlarmForm, self)._init_fields(readOnly=False, create=True,
initial=kwargs['initial'])
super(CreateAlarmForm, self).set_notification_choices(request)
self.fields.pop('state')
def handle(self, request, data):
try:
alarm_actions = [notification.get('id')
for notification in data['notifications']]
api.monitor.alarm_create(
request,
name=data['name'],
expression=data['expression'],
description=data['description'],
severity=data['severity'],
alarm_actions=alarm_actions,
ok_actions=alarm_actions,
undetermined_actions=alarm_actions,
)
messages.success(request,
_('Alarm has been created successfully.'))
except Exception:
exceptions.handle(request, _('Unable to create the alarm.'))
return False
return True
class EditAlarmForm(BaseAlarmForm):
def __init__(self, request, *args, **kwargs):
super(EditAlarmForm, self).__init__(request, *args, **kwargs)
super(EditAlarmForm, self)._init_fields(readOnly=False)
super(EditAlarmForm, self).set_notification_choices(request)
self.fields.pop('state')
def handle(self, request, data):
try:
alarm_actions = []
if data['notifications']:
alarm_actions = [notification.get('id')
for notification in data['notifications']]
api.monitor.alarm_update(
request,
alarm_id=self.initial['id'],
state=self.initial['state'],
severity=data['severity'],
name=data['name'],
expression=data['expression'],
description=data['description'],
actions_enabled=data['actions_enabled'],
alarm_actions=alarm_actions,
ok_actions=alarm_actions,
undetermined_actions=alarm_actions,
)
messages.success(request,
_('Alarm has been edited successfully.'))
except Exception:
exceptions.handle(request, _('Unable to edit the alarm.'))
return False
return True

View File

@@ -1,27 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
import horizon
from monitoring import dashboard
class Alarms(horizon.Panel):
name = _("Alarms")
slug = 'alarms'
dashboard.Monitoring.register(Alarms)

View File

@@ -1,288 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2016 Cray Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
from django.conf import settings
from django import template
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import tables
from monitoring.alarms import constants
from monitoring import api
from monitoring.overview import constants as ov_constants
STATUS = ["OK", "WARNING", "CRITICAL", "UNKNOWN"]
def get_status(index):
if index < len(STATUS):
return STATUS[index]
else:
return "UNKNOWN: %d" % index
def show_status(data):
status = data.upper()
img_tag = '<img src="{img}" title="{label}" class="status-icon" />{label}'
if status == 'CRITICAL':
return img_tag.format(img=constants.CRITICAL_ICON, label=status)
if status in ('LOW', 'MEDIUM', 'HIGH'):
return img_tag.format(img=constants.WARNING_ICON, label=status)
if status == 'OK':
return img_tag.format(img=constants.OK_ICON, label=status)
if status == 'UNKNOWN' or status == 'UNDETERMINED':
return img_tag.format(img=constants.UNKNOWN_ICON, label=status)
return status
def show_severity(data):
severity = data['alarm_definition']['severity']
state = data['state']
if state == 'ALARM':
return severity.upper()
else:
return state.upper()
def show_alarm_id(data):
return data['id']
def show_metric_names(data):
names = set(metric['name'] for metric in data['metrics'])
return ', '.join(names)
def show_def_name(data):
return data['alarm_definition']['name']
def show_def_severity(data):
return data['alarm_definition']['severity']
def show_metric_dimensions(data):
if len(data['metrics']) > 1:
commondimensions = data['metrics'][0]['dimensions']
for metric in data['metrics'][1:]:
for k in tuple(commondimensions):
if k not in metric['dimensions'].keys() or \
commondimensions[k] != metric['dimensions'][k]:
del commondimensions[k]
return ','.join(["%s=%s" % (n, v) for n, v
in commondimensions.items()])
else:
return ','.join(["%s=%s" % (n, v) for n, v
in data['metrics'][0]['dimensions'].items()])
def get_service(data):
if len(data['metrics']) == 1 and 'service'in\
data['metrics'][0]['dimensions']:
return data['metrics'][0]['dimensions']['service']
else:
return ''
class ShowAlarmHistory(tables.LinkAction):
name = 'history'
verbose_name = _('Show History')
classes = ('btn-edit',)
def get_link_url(self, datum):
return reverse(constants.URL_PREFIX + 'history',
args=(datum['alarm_definition']['id'], datum['id'], ))
def allowed(self, request, datum=None):
return True
class CreateAlarm(tables.LinkAction):
name = "create_alarm"
verbose_name = _("Create Alarm")
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("alarm", "alarm:create"),)
ajax = True
def get_link_url(self):
return reverse(constants.URL_PREFIX + 'alarm_create',
args=(self.table.kwargs['service'],))
def allowed(self, request, datum=None):
return True
class EditAlarm(tables.LinkAction):
name = "edit_alarm"
verbose_name = _("Edit Alarm")
classes = ("ajax-modal", "btn-create")
def get_link_url(self, datum):
return reverse(constants.URL_PREFIX + 'alarm_edit',
args=(self.table.kwargs['service'], datum['id'], ))
def allowed(self, request, datum=None):
return True
class GraphMetric(tables.LinkAction):
name = "graph_alarm"
verbose_name = _("Graph Metric")
icon = "dashboard"
def render(self):
self.attrs['target'] = 'newtab'
return super(self, GraphMetric).render()
def get_link_url(self, datum):
url = ''
query = ''
self.attrs['target'] = '_blank'
try:
region = self.table.request.user.services_region
grafana_url = getattr(settings, 'GRAFANA_URL').get(region, '')
url = grafana_url + \
'/dashboard/script/drilldown.js'
metric = datum['metrics'][0]['name']
dimensions = datum['metrics'][0].get('dimensions', {})
query = "?metric=%s" % metric
for key, value in dimensions.items():
query += "&%s=%s" % (key, value)
except AttributeError:
# Catches case where Grafana 2 is not enabled.
name = datum['metrics'][0]['name']
threshold = json.dumps(datum['metrics'])
endpoint = str(reverse_lazy(ov_constants.URL_PREFIX + 'proxy'))
endpoint = self.table.request.build_absolute_uri(endpoint)
url = '/grafana/index.html#/dashboard/script/detail.js'
query = "?name=%s&threshold=%s&api=%s" % \
(name, threshold, endpoint)
return url + query
def allowed(self, request, datum=None):
return (getattr(settings, 'GRAFANA_URL', None) is not None and
datum['metrics'])
class ShowAlarmDefinition(tables.LinkAction):
name = "show_alarm_definition"
verbose_name = _("Show Alarm Definition")
def get_link_url(self, datum=None):
url = 'horizon:monitoring:alarmdefs:alarm_detail'
args = (datum['alarm_definition']['id'],)
return reverse_lazy(url, args=args)
class DeleteAlarm(tables.DeleteAction):
name = "delete_alarm"
verbose_name = _("Delete Alarm")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete Alarm",
"Delete Alarms",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Deleted Alarm",
"Deleted Alarms",
count
)
def allowed(self, request, datum=None):
return True
def delete(self, request, obj_id):
api.monitor.alarm_delete(request, obj_id)
class AlarmsFilterAction(tables.LinkAction):
name = "filter"
verbose_name = _("Filter Alarms")
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("alarm", "alarm:filter"),)
ajax = True
def get_link_url(self):
return reverse(constants.URL_PREFIX + 'alarm_filter', args=())
def allowed(self, request, datum=None):
return True
class AlarmsTable(tables.DataTable):
state = tables.Column(transform=show_severity, verbose_name=_('Status'),
status_choices={(show_status('OK'), True)},
filters=[show_status, template.defaultfilters.safe])
name = tables.Column(transform=show_def_name, verbose_name=_('Name'))
alarmId = tables.Column(transform=show_alarm_id, verbose_name=_('Alarm Id'))
metrics = tables.Column(transform=show_metric_names,
verbose_name=_('Metric Names'))
dimensions = tables.Column(transform=show_metric_dimensions,
verbose_name=_('Metric Dimensions'))
def get_object_id(self, obj):
return obj['id']
def get_object_display(self, obj):
return obj['id']
class Meta(object):
name = "alarms"
verbose_name = _("Alarms")
row_actions = (GraphMetric,
ShowAlarmHistory,
ShowAlarmDefinition,
DeleteAlarm,
)
table_actions = (AlarmsFilterAction,
DeleteAlarm,
)
def show_timestamp(data):
return data['timestamp'].replace('.000Z', '').replace('T', ' ')
class AlarmHistoryTable(tables.DataTable):
timestamp = tables.Column(transform=show_timestamp,
verbose_name=_('Timestamp'),
attrs={"data-type": "timestamp"})
old_state = tables.Column('old_state', verbose_name=_('Old State'))
new_state = tables.Column('new_state', verbose_name=_('New State'))
alarmDimensions = tables.Column(transform=show_metric_dimensions,
verbose_name=_('Alarm Metric Dimensions'))
reason = tables.Column('reason', verbose_name=_('Reason'))
# reason_data = tables.Column('reason_data', verbose_name=_('Reason Data'))
def get_object_id(self, obj):
return obj['alarm_id'] + obj['timestamp']
class Meta(object):
name = "history"
verbose_name = _("Alarm History")

View File

@@ -1,67 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-header %}{% trans "Filter Alarms" %}{% endblock %}
{% block modal-body %}
<script>
function isEmpty(str) {
return (!str || 0 === str.length);
}
function sendID() {
var location = "{{ alarm_url }}";
var filterID = document.forms["form0"]["filterID"].value;
if (!isEmpty(filterID)) {
window.location = location + "id=" + filterID;
} else {
window.location = location;
}
}
function sendDimensions() {
var dimension = document.forms["form1"]["dimension"].value;
var metric = document.forms["form1"]["metric"].value;
var location = "{{ alarm_url }}";
var param = '';
if (!isEmpty(metric)) {
param = 'metric=' + metric;
if (!isEmpty(dimension)) {
param = param + ',';
}
}
if (!isEmpty(dimension)) {
param = param + dimension;
}
if (!isEmpty(param)) {
param = btoa(param).replace(/\+/g, '-').replace(/\//g, '_');
location = location + 'b64:' + param;
}
window.location = location;
}
</script>
<div class = "filter_form" >
<form></form>
<form method="GET" class="form-group" style="width:400px" onsubmit="sendID()" name="form0">
<h3>{% trans 'Alarm ID' %}</h3>
{% trans 'Value' %}: <input class="form-control" name="filterID" placeholder="54fdec2f-ae34-4dba-9c85-7b4ad67eea03">
<button type="submit" class="btn btn-default">{% trans 'Submit' %}</button>
</form>
<hr>
<hr>
<form method="GET" class="form-group" style="width:400px" onsubmit="sendDimensions()" name="form1">
<h3>{% trans 'Dimension(s)' %}</h3>
{% trans 'Key' %}[={% trans 'Value' %}]
<input class="form-control" name="dimension" placeholder="hostname=devstack, service=mysql" data-toggle="tooltip" data-placement="right" title="Example: hostname=devstack, service=mysql">
<br />
{% trans 'Metric Name' %}: <input class="form-control" name="metric" placeholder="http_status">
<button type="submit" class="btn btn-default">{% trans 'Submit' %}</button>
</form>
</div>
{% endblock %}
{% block modal-footer %}
{% endblock %}

View File

@@ -1,34 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Alarms' %}{% endblock %}
{% block page_header %}
{% if service != 'all'%}
<div class="page-header">
<h2>{% trans 'Alarms for ' %} {{ service }}</h2>
</div>
{% else %}
{% include "horizon/common/_page_header.html" with title=_("All Alarms") %}
{% endif %}
{% endblock page_header %}
{% block breadcrumb_nav %}
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate">{% trans "Monitoring" %}</li>
<li class="breadcrumb-item-truncate active">{% trans "Alarms" %}</li>
</ol>
{% endblock %}
{% block main %}
{{ table.render }}
<div class="pagination">
<span class="step-links">
{% if prev_page_offset != None %}
<a href="?page_offset={{ prev_page_offset }}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Previous Page' %}</a>
{% endif %}
{% if page_offset %}
<a href="?page_offset={{ page_offset}}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Next Page' %}</a>
{% endif %}
</span>
</div>
{% endblock %}

View File

@@ -1,79 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Alarm History' %}{% endblock %}
{% block page_header %}
<div class="page-header">
<h2>{% trans 'Alarm History' %}</h2>
</div>
<h4>{% trans "Alarm Details" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "Name" %}</dt>
<dd>{{ alarm.alarm_definition.name|default:_("None") }}</dd>
<dt>{% trans "Associated Metrics" %}</dt>
<dd><ul>
{% for metric in alarm.metrics %}
{% if metric.dimensions %}
<li>
{{ metric.name }}
{
{% for k, v in metric.dimensions.items %}
{{ k }}={{ v }}
{% if not forloop.last %}
{{ ',' }}
{% endif %}
{% endfor %}
}
</li>
{% else %}
<li>{{ metric.name }}</li>
{% endif %}
{% endfor %}
</ul></dd>
</dl>
{% endblock page_header %}
{% block main %}
<div class="row" ng-controller="timestampPickerController">
<div class="col-sm-3">
<select id="timestampFormatDropdownAlarmHistory"
name="ts_mode"
class="form-control"
ng-model="currentFormat"
ng-init="setUp('{{ timestamp_selected | safe }}')">
<option value="">{% trans "---Please select---" %}</option>
{% for key,label in timestamp_formats %}
<option value="{{ key }}" title="{{ label }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<span class="label label-primary"
ng-if="currentOffset">{$ currentOffset $}h</span>
</div>
{{ table.render }}
<div class="pagination">
<span class="step-links">
{% if prev_page_offset != None %}
<a href="?page_offset={{ prev_page_offset }}&amp;ts_mode={{ timestamp_selected }}&amp;ts_offset={{ timestamp_offset|urlencode }}"
class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Previous Page' %}</a>
{% endif %}
{% if page_offset %}
<a href="?page_offset={{ page_offset }}&amp;ts_mode={{ timestamp_selected }}&amp;ts_offset={{ timestamp_offset|urlencode }}"
class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Next Page' %}</a>
{% endif %}
</span>
</div>
<style>
.status-icon {
vertical-align: top;
margin-right: 2px;
}
</style>
{% endblock %}

View File

@@ -1,51 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Measurements for Alarms' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Measurements that Triggered Alarm" %}
{% endblock page_header %}
{% block manage_overview %}
{% block page_title %}
<div class="page_title table_header">
<div>
<h3>
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="showspinner">
{% trans "Service Health" %}</a> : {{ region_name }} - &gt;
{% trans "Service :" %} {{ service }}
</h3>
</div>
<div class="table_actions">
<a href="{% url 'horizon:noc_tools:service_health:index' %}" class="close showspinner">&times;</a>
</div>
</div>
{% endblock page_title %}
{% endblock %}
{% block main %}
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Time', 'Response Time', 'Threshold'],
['8:42', 1000, 1200],
['8:43', 1170, 1200],
['8:44', 1190, 1200],
['8:45', 1230, 1200]
]);
var options = {
title: 'API Response Time'
};
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<div id="chart_div" style="width: 900px; height: 500px;"></div>
{% endblock %}

View File

@@ -1,10 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Filter Alarms" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/alarms/_filter.html' %}
{% endblock %}

View File

@@ -1,68 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from unittest.mock import patch
from monitoring.alarms import constants
from monitoring.alarms import tables
from monitoring.test import helpers
INDEX_URL = reverse(
constants.URL_PREFIX + 'index')
ALARMS_URL_BY_DIMENSION = reverse(
constants.URL_PREFIX + 'alarm', args=('nova',))
ALARMS_URL = reverse(
constants.URL_PREFIX + 'alarm', args=('all',))
class AlarmsTest(helpers.TestCase):
def test_alarms_get_by_dimension(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarm_list_by_dimension'],
'alarm_list_by_dimension.return_value': [],
}) as mock:
res = self.client.get(ALARMS_URL_BY_DIMENSION)
self.assertEqual(mock.alarm_list_by_dimension.call_count, 2)
self.assertTemplateUsed(
res, 'monitoring/alarms/alarm.html')
def test_alarms_get(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['alarm_list'],
'alarm_list.return_value': [],
}) as mock:
res = self.client.get(ALARMS_URL)
self.assertEqual(mock.alarm_list.call_count, 2)
self.assertTemplateUsed(
res, 'monitoring/alarms/alarm.html')
def test_metric_conversion_single(self):
res = tables.show_metric_names({"metrics": [{"name": "mem.used_bytes"}]})
self.assertEqual(res, "mem.used_bytes")
def test_metric_conversion_multiple(self):
res = tables.show_metric_names({"metrics": [{"name": "mem.used_bytes"},
{"name": "mem.total_bytes"}]})
table_res = res.split(', ')
self.assertEqual(len(table_res), 2)
self.assertTrue("mem.used_bytes" in table_res)
self.assertTrue("mem.total_bytes" in table_res)
def test_metric_conversion_unique(self):
res = tables.show_metric_names({"metrics": [{"name": "mem.used_bytes"},
{"name": "mem.used_bytes"}]})
self.assertEqual(res, "mem.used_bytes")

View File

@@ -1,32 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import re_path
from monitoring.alarms import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^alarm/filter/$',
views.AlarmFilterView.as_view(),
name='alarm_filter'),
re_path(r'^alarm/(?P<service>[^/]+)/$',
views.AlarmServiceView.as_view(),
name='alarm'),
re_path(r'^alarm/$',
views.AlarmServiceView.as_view(),
name='alarm_all'),
re_path(r'^history/(?P<name>[^/]+)/(?P<id>[^/]+)$',
views.AlarmHistoryView.as_view(),
name='history')
]

View File

@@ -1,402 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
from datetime import timedelta
import logging
from django.conf import settings
from django.contrib import messages
from django.core.paginator import EmptyPage
from django.core.paginator import Paginator
from django.shortcuts import redirect
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon.utils import functions as utils
from monitoring.alarms import constants
from monitoring.alarms import forms as alarm_forms
from monitoring.alarms import tables as alarm_tables
from monitoring import api
from openstack_dashboard import policy
LOG = logging.getLogger(__name__)
SERVICES = getattr(settings, 'MONITORING_SERVICES', [])
PREV_PAGE_LIMIT = 100
def get_icon(status):
if status == 'chicklet-success':
return constants.OK_ICON
if status == 'chicklet-error':
return constants.CRITICAL_ICON
if status == 'chicklet-warning':
return constants.WARNING_ICON
if status == 'chicklet-unknown':
return constants.UNKNOWN_ICON
if status == 'chicklet-notfound':
return constants.NOTFOUND_ICON
priorities = [
{'status': 'chicklet-success', 'severity': 'OK'},
{'status': 'chicklet-unknown', 'severity': 'UNDETERMINED'},
{'status': 'chicklet-warning', 'severity': 'LOW'},
{'status': 'chicklet-warning', 'severity': 'MEDIUM'},
{'status': 'chicklet-warning', 'severity': 'HIGH'},
{'status': 'chicklet-error', 'severity': 'CRITICAL'},
]
index_by_severity = {d['severity']: i for i, d in enumerate(priorities)}
alarm_history_default_ts_format = 'utc'
alarm_history_ts_formats = (
('utc', _('UTC'),),
('bl', _('Browser local'),),
)
default_service = 'all'
def get_status(alarms):
if not alarms:
return 'chicklet-notfound'
status_index = 0
for a in alarms:
severity = alarm_tables.show_severity(a)
severity_index = index_by_severity.get(severity, None)
status_index = max(status_index, severity_index)
return priorities[status_index]['status']
def generate_status(request):
try:
alarms = api.monitor.alarm_list(request)
except Exception as e:
messages.error(request,
_('Unable to list alarms: %s') % str(e))
alarms = []
alarms_by_service = {}
for a in alarms:
service = alarm_tables.show_service(a)
service_alarms = alarms_by_service.setdefault(service, [])
service_alarms.append(a)
for row in SERVICES:
row['name'] = str(row['name'])
for service in row['services']:
service_alarms = alarms_by_service.get(service['name'], [])
service['class'] = get_status(service_alarms)
service['icon'] = get_icon(service['class'])
service['display'] = str(service['display'])
return SERVICES
class IndexView(View):
def dispatch(self, request, *args, **kwargs):
return redirect(constants.URL_PREFIX + 'alarm', service='all')
class AlarmServiceView(tables.DataTableView):
table_class = alarm_tables.AlarmsTable
template_name = constants.TEMPLATE_PREFIX + 'alarm.html'
def dispatch(self, *args, **kwargs):
if 'service' in kwargs:
self.service = kwargs['service']
del kwargs['service']
else:
self.service = default_service
return super(AlarmServiceView, self).dispatch(*args, **kwargs)
def get_data(self):
page_offset = self.request.GET.get('page_offset')
contacts = []
if page_offset is None:
page_offset = 0
limit = utils.get_page_size(self.request)
if self.service == default_service:
try:
results = api.monitor.alarm_list(self.request, page_offset,
limit)
paginator = Paginator(results, limit)
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
return contacts
else:
if self.service[:2] == 'id':
try:
name, value = self.service.split("=")
results = [api.monitor.alarm_show(self.request, value)]
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
results = []
return results
else:
try:
if self.service[:3] == 'b64':
name, value = self.service.split(":")
self.service = base64.urlsafe_b64decode(str(value)).decode('utf-8')
results = api.monitor.alarm_list_by_dimension(self.request,
self.service,
page_offset,
limit)
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
results = []
return results
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(AlarmServiceView, self).get_context_data(**kwargs)
results = []
num_results = 0 # make sure variable is set
prev_page_stack = []
page_offset = self.request.GET.get('page_offset')
if 'prev_page_stack' in self.request.session:
prev_page_stack = self.request.session['prev_page_stack']
if page_offset is None:
page_offset = 0
prev_page_stack = []
else:
page_offset = int(page_offset)
limit = utils.get_page_size(self.request)
if self.service == 'all':
try:
# To judge whether there is next page, get limit + 1
results = api.monitor.alarm_list(self.request, page_offset,
limit + 1)
num_results = len(results)
paginator = Paginator(results, limit)
results = paginator.page(1)
except EmptyPage:
results = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
else:
if self.service[:2] == 'id':
try:
name, value = self.service.split("=")
results = [api.monitor.alarm_show(self.request, value)]
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
results = []
else:
try:
# To judge whether there is next page, get limit + 1
results = api.monitor.alarm_list_by_dimension(self.request,
self.service,
page_offset,
limit + 1)
num_results = len(results)
paginator = Paginator(results, limit)
results = paginator.page(1)
except EmptyPage:
results = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve alarms"))
results = []
context["contacts"] = results
context["service"] = self.service
if num_results < limit + 1:
context["page_offset"] = None
else:
context["page_offset"] = page_offset + limit
if page_offset in prev_page_stack:
index = prev_page_stack.index(page_offset)
prev_page_stack = prev_page_stack[0:index]
prev_page_offset = prev_page_stack[-1] if prev_page_stack else None
if prev_page_offset is not None:
context["prev_page_offset"] = prev_page_offset
if len(prev_page_stack) > PREV_PAGE_LIMIT:
del prev_page_stack[0]
prev_page_stack.append(page_offset)
self.request.session['prev_page_stack'] = prev_page_stack
return context
def transform_alarm_history(results, name, ts_mode, ts_offset):
new_list = []
def get_ts_val(val):
if ts_mode == 'bl':
offset = int((ts_offset or '0').replace('+', ''))
dt_val = parse_datetime(val) + timedelta(hours=offset)
dt_val_formatter = dt_val.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
return dt_val_formatter.replace('000Z', '')
elif ts_mode != 'utc':
raise ValueError('%s is not supported timestamp format' % ts_mode)
else:
return val # utc case
for item in results:
new_list.append({'alarm_id': item['alarm_id'],
'name': name,
'old_state': item['old_state'],
'new_state': item['new_state'],
'timestamp': get_ts_val(item['timestamp']),
'reason': item['reason'],
'metrics': item['metrics'],
'reason_data': item['reason_data']})
return new_list
class AlarmHistoryView(tables.DataTableView):
table_class = alarm_tables.AlarmHistoryTable
template_name = constants.TEMPLATE_PREFIX + 'alarm_history.html'
def dispatch(self, *args, **kwargs):
return super(AlarmHistoryView, self).dispatch(*args, **kwargs)
def get_data(self):
page_offset = self.request.GET.get('page_offset')
ts_mode = self.request.GET.get('ts_mode')
ts_offset = self.request.GET.get('ts_offset')
contacts = []
object_id = self.kwargs['id']
name = self.kwargs['name']
if not ts_mode:
ts_mode = alarm_history_default_ts_format
if not page_offset:
page_offset = 0
limit = utils.get_page_size(self.request)
try:
results = api.monitor.alarm_history(self.request, object_id, page_offset, limit)
paginator = Paginator(results, limit)
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request,
_("Could not retrieve alarm history for %s") % object_id)
try:
return transform_alarm_history(contacts, name, ts_mode, ts_offset)
except ValueError as err:
LOG.warning('Failed to transform alarm history due to %s' %
err.message)
messages.warning(self.request, _('Failed to present alarm '
'history'))
return []
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(AlarmHistoryView, self).get_context_data(**kwargs)
object_id = kwargs['id']
ts_mode = self.request.GET.get('ts_mode')
ts_offset = self.request.GET.get('ts_offset')
try:
alarm = api.monitor.alarm_get(self.request, object_id)
except Exception:
messages.error(self.request,
_("Could not retrieve alarm for %s") % object_id)
context['alarm'] = alarm
num_results = 0
contacts = []
prev_page_stack = []
page_offset = self.request.GET.get('page_offset')
limit = utils.get_page_size(self.request)
if 'prev_page_stack' in self.request.session:
prev_page_stack = self.request.session['prev_page_stack']
if page_offset is None:
page_offset = 0
prev_page_stack = []
try:
# To judge whether there is next page, get limit + 1
results = api.monitor.alarm_history(self.request, object_id, page_offset,
limit + 1)
num_results = len(results)
paginator = Paginator(results, limit)
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request,
_("Could not retrieve alarm history for %s") % object_id)
return context
context["contacts"] = contacts
context['timestamp_formats'] = alarm_history_ts_formats
context['timestamp_selected'] = ts_mode or ''
context['timestamp_offset'] = ts_offset or 0
if num_results < limit + 1:
context["page_offset"] = None
else:
context["page_offset"] = contacts.object_list[-1]["id"]
if page_offset in prev_page_stack:
index = prev_page_stack.index(page_offset)
prev_page_stack = prev_page_stack[0:index]
prev_page_offset = prev_page_stack[-1] if prev_page_stack else None
if prev_page_offset is not None:
context["prev_page_offset"] = prev_page_offset
if len(prev_page_stack) > PREV_PAGE_LIMIT:
del prev_page_stack[0]
prev_page_stack.append(str(page_offset))
self.request.session['prev_page_stack'] = prev_page_stack
return context
class AlarmFilterView(forms.ModalFormView):
template_name = constants.TEMPLATE_PREFIX + 'filter.html'
form_class = alarm_forms.CreateAlarmForm
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(AlarmFilterView, self).get_context_data(**kwargs)
context["cancel_url"] = self.get_success_url()
context["action_url"] = reverse(constants.URL_PREFIX + 'alarm_filter',
args=())
context["alarm_url"] = reverse_lazy(constants.URL_PREFIX + 'alarm_all',
args=())
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index',
args=())

View File

@@ -1,5 +0,0 @@
from monitoring.api import monitor
__all__ = [
"monitor"
]

View File

@@ -1,111 +0,0 @@
# Copyright 2017 Fujitsu LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from horizon import exceptions
from horizon.utils import memoized
from oslo_log import log as logging
from openstack_dashboard.api import base
from monascaclient import client as mon_client
from monitoring.config import local_settings as settings
LOG = logging.getLogger(__name__)
INSECURE = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
CACERT = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
KEYSTONE_SERVICE = 'identity'
MONITORING_SERVICE = getattr(settings, 'MONITORING_SERVICE_TYPE', 'monitoring')
VERSIONS = base.APIVersionManager(
MONITORING_SERVICE,
preferred_version=getattr(settings,
'OPENSTACK_API_VERSIONS',
{}).get(MONITORING_SERVICE, 2.0)
)
VERSIONS.load_supported_version(2.0, {'client': mon_client, 'version': '2_0'})
def _get_endpoint(request):
try:
endpoint = base.url_for(request,
service_type=settings.MONITORING_SERVICE_TYPE,
endpoint_type=settings.MONITORING_ENDPOINT_TYPE)
except exceptions.ServiceCatalogException:
endpoint = 'http://127.0.0.1:8070/v2.0'
LOG.warning('Monasca API location could not be found in Service '
'Catalog, using default: {0}'.format(endpoint))
return endpoint
def _get_auth_params_from_request(request):
"""Extracts the properties from the request object needed by the monascaclient call below.
These will be used to memoize the calls to monascaclient
"""
LOG.debug('Extracting intel from request')
return (
request.user.user_domain_id,
request.user.token.id,
request.user.tenant_id,
request.user.token.project.get('domain_id'),
base.url_for(request, MONITORING_SERVICE),
base.url_for(request, KEYSTONE_SERVICE)
)
def _get_to_verify(insecure, cacert):
to_verify = cacert
if insecure:
to_verify = False
return to_verify
@memoized.memoized
def monascaclient(request, version=None):
(
user_domain_id,
token_id,
project_id,
project_domain_id,
monasca_url,
auth_url
) = _get_auth_params_from_request(request)
# NOTE(trebskit) this is bit hacky, we should
# go straight into using numbers as version representation
version = (VERSIONS.get_active_version()['version']
if not version else version)
LOG.debug('Monasca::Client <Url: %s> <Version: %s>'
% (monasca_url, version))
to_verify = _get_to_verify(INSECURE, CACERT)
c = mon_client.Client(api_version=version,
token=token_id,
project_id=project_id,
user_domain_id=user_domain_id,
project_domain_id=project_domain_id,
verify=to_verify,
auth_url=auth_url,
endpoint=monasca_url)
return c

View File

@@ -1,202 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
from openstack_dashboard.contrib.developer.profiler import api as profiler
from monitoring.api import client
LOG = log.getLogger(__name__)
@profiler.trace
def alarm_list(request, offset=0, limit=10000, marker=None, paginate=False):
result = client.monascaclient(request).alarms.list(offset=offset,
limit=limit,
sort_by='alarm_definition_name')
return result['elements'] if type(result) is dict else result
@profiler.trace
def alarm_list_by_dimension(request, dimensions, offset=0, limit=10000,
marker=None, paginate=False):
dim_dict = {}
metric = None
dimensions = dimensions.split(",")
for item in dimensions:
if '=' in item:
name, value = item.split('=')
if name == 'metric':
metric = value
else:
dim_dict[name] = value
else:
dim_dict[item] = None
if metric:
result = client.monascaclient(request).alarms.list(offset=offset,
limit=limit,
metric_dimensions=dim_dict,
metric_name=metric)
else:
result = client.monascaclient(request).alarms.list(offset=offset,
limit=limit,
metric_dimensions=dim_dict)
return result['elements'] if type(result) is dict else result
@profiler.trace
def alarm_show(request, alarm_id):
result = client.monascaclient(request).alarms.get(alarm_id=alarm_id)
return result
@profiler.trace
def alarm_delete(request, alarm_id):
return client.monascaclient(request).alarms.delete(alarm_id=alarm_id)
@profiler.trace
def alarm_history(request, alarm_id, offset=0, limit=10000):
result = client.monascaclient(request).alarms.history(alarm_id=alarm_id,
offset=offset,
limit=limit)
return result['elements'] if type(result) is dict else result
@profiler.trace
def alarm_get(request, alarm_id):
return client.monascaclient(request).alarms.get(alarm_id=alarm_id)
@profiler.trace
def alarm_patch(request, **kwargs):
return client.monascaclient(request).alarms.patch(**kwargs)
@profiler.trace
def alarmdef_list(request, offset=0, limit=10000, marker=None, paginate=False):
result = client.monascaclient(request).alarm_definitions.list(offset=offset,
limit=limit,
sort_by='name')
return result['elements'] if type(result) is dict else result
@profiler.trace
def alarmdef_list_by_service(request, service_name, marker=None,
paginate=False):
service_dim = {'service': service_name}
result = client.monascaclient(request).alarm_definitions.list(
dimensions=service_dim)
return result['elements'] if type(result) is dict else result
@profiler.trace
def alarmdef_delete(request, alarm_id):
return client.monascaclient(request).alarm_definitions.delete(
alarm_id=alarm_id)
@profiler.trace
def alarmdef_history(request, alarm_id):
return client.monascaclient(request).alarm_definitions.history(
alarm_id=alarm_id)
@profiler.trace
def alarmdef_get(request, alarm_id):
return client.monascaclient(request).alarm_definitions.get(alarm_id=alarm_id)
@profiler.trace
def alarmdef_get_by_name(request, name):
return client.monascaclient(request).alarm_definitions.list(
name=name,
limit=1
)
@profiler.trace
def alarmdef_create(request, **kwargs):
return client.monascaclient(request).alarm_definitions.create(**kwargs)
@profiler.trace
def alarmdef_update(request, **kwargs):
return client.monascaclient(request).alarm_definitions.update(**kwargs)
@profiler.trace
def alarmdef_patch(request, **kwargs):
return client.monascaclient(request).alarm_definitions.patch(**kwargs)
@profiler.trace
def notification_list(request, offset=0, limit=10000, marker=None,
paginate=False):
result = client.monascaclient(request).notifications.list(offset=offset,
limit=limit,
sort_by='name')
return result['elements'] if type(result) is dict else result
@profiler.trace
def notification_delete(request, notification_id):
return client.monascaclient(request).notifications.delete(
notification_id=notification_id)
@profiler.trace
def notification_get(request, notification_id):
return (client.monascaclient(request).notifications.
get(notification_id=notification_id))
@profiler.trace
def notification_create(request, **kwargs):
return client.monascaclient(request).notifications.create(**kwargs)
@profiler.trace
def notification_update(request, notification_id, **kwargs):
return (client.monascaclient(request).notifications.
update(notification_id=notification_id, **kwargs))
@profiler.trace
def notification_type_list(request, **kwargs):
result = client.monascaclient(request).notificationtypes.list(**kwargs)
return result['elements'] if type(result) is dict else result
@profiler.trace
def metrics_list(request, **kwargs):
result = client.monascaclient(request).metrics.list(**kwargs)
return result['elements'] if type(result) is dict else result
@profiler.trace
def metrics_measurement_list(request, **kwargs):
result = client.monascaclient(request).metrics.list_measurements(**kwargs)
return result['elements'] if type(result) is dict else result
@profiler.trace
def metrics_stat_list(request, **kwargs):
result = client.monascaclient(request).metrics.list_statistics(**kwargs)
return result['elements'] if type(result) is dict else result
@profiler.trace
def metrics_dimension_value_list(request, **kwargs):
result = client.monascaclient(request).metrics.list_dimension_values(**kwargs)
return result['elements'] if type(result) is dict else result

View File

@@ -1,4 +0,0 @@
"monasca_user_role": "role:monasca-user"
"default": "@"
"monitoring:monitoring": "rule:monasca_user_role"
"monitoring:kibana_access": "rule:monasca_user_role"

View File

@@ -1,121 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
from django.utils.translation import gettext_lazy as _
# Service group names (global across all projects):
MONITORING_SERVICES_GROUPS = [
{'name': _('OpenStack Services'), 'groupBy': 'service'},
{'name': _('Servers'), 'groupBy': 'hostname'}
]
# Services being monitored
MONITORING_SERVICES = getattr(
settings,
'MONITORING_SERVICES_GROUPS',
MONITORING_SERVICES_GROUPS
)
#
# Per project service groups. If in this form,
# '*' will be applied to all projects not explicitly listed.
#
# Note the above form (flat) is supported for backward compatibility.
#
# MONITORING_SERVICES_GROUPS = [
# {'admin': [
# {'name': _('OpenStack Services'), 'groupBy': 'service'},
# {'name': _('Servers'), 'groupBy': 'hostname'}]},
# {'*': [
# {'name': _('Services'), 'groupBy': 'service'},
# {'name': _('Instances'), 'groupBy': 'hostname'}]},
# ]
MONITORING_SERVICE_VERSION = getattr(
settings, 'MONITORING_SERVICE_VERSION', '2_0'
)
MONITORING_SERVICE_TYPE = getattr(
settings, 'MONITORING_SERVICE_TYPE', 'monitoring'
)
MONITORING_ENDPOINT_TYPE = getattr(
# NOTE(trebskit) # will default to OPENSTACK_ENDPOINT_TYPE
settings, 'MONITORING_ENDPOINT_TYPE', None
)
# Grafana button titles/file names (global across all projects):
GRAFANA_LINKS = []
DASHBOARDS = getattr(settings, 'GRAFANA_LINKS', GRAFANA_LINKS)
#
# Horizon will link to the grafana home page when using Grafana2.
# For any Grafana version additional links to specific dashboards can be
# created in two formats.
# Flat:
# GRAFANA_LINKS = [ {'title': _('Dashboard'), 'path': 'openstack', 'raw': False} ]
#
# Per project: '*' will be applied to all projects not explicitly listed.
# GRAFANA_LINKS = [
# {'admin': [
# {'title': _('Dashboard'), 'path': 'openstack', 'raw': False}]},
# {'*': [
# {'title': _('OpenStack Dashboard'), 'path': 'project', 'raw': False}]}
# ]
#
# If GRAFANA_URL is specified, the dashboard file name/raw URL must be
# specified through the 'path' attribute as shown above.
#
# Flat:
# GRAFANA_LINKS = [ {'title': _('Dashboard'), 'fileName': 'openstack.json', 'raw': False} ]
#
# GRAFANA_LINKS = [
# {'admin': [
# {'fileName': _('Dashboard'), 'fileName': 'openstack.json', 'raw': False}]},
# {'*': [
# {'title': _('OpenStack Dashboard'), 'fileName': 'project.json': False}]}
# ]
#
# If GRAFANA_URL is unspecified the dashboard file name must be specified
# through the fileName attribute.
#
# Both with and without GRAFANA_URL, the links have an optional 'raw' attribute
# which defaults to False if unspecified. If it is False, the value of 'path'
# (or 'fileName', respectively) is interpreted as a dashboard name and a link
# to the dashboard based on the dashboard's name will be generated. If it is
# True, the value of 'path' or 'fileName' will be treated as a URL to be used
# verbatim.
GRAFANA_URL = getattr(settings, 'GRAFANA_URL', None)
# If GRAFANA_URL is specified, an additional link will be shown that points to
# Grafana's list of dashboards. If you do not wish this, set SHOW_GRAFANA_HOME
# to False (by default this setting is True and the link will thus be shown).
SHOW_GRAFANA_HOME = getattr(settings, 'SHOW_GRAFANA_HOME', True)
ENABLE_LOG_MANAGEMENT_BUTTON = getattr(settings, 'ENABLE_LOG_MANAGEMENT_BUTTON', True)
ENABLE_EVENT_MANAGEMENT_BUTTON = getattr(settings, 'ENABLE_EVENT_MANAGEMENT_BUTTON', False)
KIBANA_POLICY_RULE = getattr(settings, 'KIBANA_POLICY_RULE',
'monitoring:kibana_access')
KIBANA_POLICY_SCOPE = getattr(settings, 'KIBANA_POLICY_SCOPE',
'monitoring')
KIBANA_HOST = getattr(settings, 'KIBANA_HOST', 'http://192.168.10.6:5601/')
OPENSTACK_SSL_NO_VERIFY = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
OPENSTACK_SSL_CACERT = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
POLICY_FILES = getattr(settings, 'POLICY_FILES', {})
POLICY_FILES.update({'monitoring': 'monitoring_policy.yaml',}) # noqa
setattr(settings, 'POLICY_FILES', POLICY_FILES)

View File

@@ -1,30 +0,0 @@
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
from monitoring.config import local_settings as settings
import horizon
class Monitoring(horizon.Dashboard):
name = _("Monitoring")
slug = "monitoring"
panels = ('overview', 'alarmdefs', 'alarms', 'notifications',)
default_panel = 'overview'
policy_rules = (("monitoring", "monitoring:monitoring"),)
permissions = (('openstack.services.' + settings.MONITORING_SERVICE_TYPE),)
horizon.register(Monitoring)

View File

@@ -1,15 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
PANEL = 'notifications'
PANEL_DASHBOARD = 'monitoring'
REMOVE_PANEL = True

View File

@@ -1,45 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from monascaclient import exc
DASHBOARD = "monitoring"
# A list of applications to be added to INSTALLED_APPS.
ADD_INSTALLED_APPS = ['monitoring']
# A list of angular modules to be added as dependencies to horizon app.
ADD_ANGULAR_MODULES = ['monitoringApp']
# A list of javascript files to be included for all pages
ADD_JS_FILES = ['monitoring/js/app.js',
'monitoring/js/filters.js',
'monitoring/js/controllers.js',
'monitoring/js/directives.js',
'monitoring/js/services.js',
'monitoring/js/ng-tags-input.js']
ADD_SCSS_FILES = [
'monitoring/css/alarm-create.scss']
# A dictionary of exception classes to be added to HORIZON['exceptions'].
_RECOVERABLE_ERRORS = (exc.UnprocessableEntity, exc.Conflict,
exc.BadRequest, exc.ConnectionError,
exc.Forbidden, exc.InternalServerError)
_NOT_FOUND_ERRORS = (exc.NotFound,)
_UNAUTHORIZED_ERRORS = (exc.Unauthorized,)
ADD_EXCEPTIONS = {
'recoverable': _RECOVERABLE_ERRORS,
'not_found': _NOT_FOUND_ERRORS,
'unauthorized': _UNAUTHORIZED_ERRORS,
}

View File

@@ -1,576 +0,0 @@
# Frank Kloeker <eumel@arcor.de>, 2018. #zanata
# Robert Simai <robert.simai@suse.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2022-07-06 20:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-04-23 02:51+0000\n"
"Last-Translator: Robert Simai <robert.simai@suse.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" Das Feld Name dient zur Identifizierung der Alarmdefinition."
msgid ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgstr ""
"\n"
" Wenn für einige Übergänge keine Benachrichtigungen gesendet werden "
"sollen,\n"
" können sie deaktiviert werden."
msgid ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgstr ""
"\n"
" Das Feld Beschreibung kann verwendet werden, um den Zweck der "
"Alarmdefinition zu beschreiben."
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" Das Feld Name dient zur Identifizierung der Alarmdefinition."
msgid ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgstr ""
"\n"
" Das Feld Benachrichtigungen enthält die Liste der Benachrichtigungen, "
"die beim\n"
" Übergang in einen anderen Status gesendet werden sollen."
msgid ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgstr ""
"\n"
" Das Feld Schweregrad ermöglicht es, die Wichtigkeit der Alarmdefinition "
"anzugeben."
msgid ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook urls).\n"
" "
msgstr ""
"\n"
" Ein Wert ungleich Null im Zeitraumfeld gibt an, wie häufig eine "
"Benachrichtigung\n"
" erneut gesendet werden soll (nur gültig für Webhook-URLs)."
msgid ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgstr ""
"\n"
" Das Feld Adresse zeigt die E-Mail-Adresse, URL oder den PagerDuty-\n"
" Dienstschlüssel an, der benachrichtigt werden soll."
msgid ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgstr ""
"\n"
" Das Expression-Feld, das, wenn es zutrifft, eine Benachrichtigung "
"auslöst, die\n"
" gesendet werden soll. Informationen zum Schreiben eines Ausdrucks "
"finden Sie\n"
" unter <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarmausdrücke</a> ."
msgid ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgstr ""
"\n"
" Das Feld Name wird verwendet, um die Benachrichtigungsmethode zu\n"
" identifizieren."
msgid ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgstr ""
"\n"
" Das Feld Benachrichtigungen enthält die Liste der Benachrichtigungen, "
"die beim\n"
" Übergang in einen anderen ALARM-Status gesendet werden sollen."
msgid ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgstr ""
"\n"
" Das Feld Typ gibt an, wie die Benachrichtigung gesendet wird, wenn ein "
"Alarm\n"
" ausgelöst wird."
msgid "+ Add more"
msgstr "+ Fügen Sie weitere hinzu"
msgid "---Please select---"
msgstr "---Bitte auswählen---"
msgid "A description of an alarm."
msgstr "Eine Beschreibung eines Alarms."
msgid "A descriptive name of the notification method."
msgstr "Ein beschreibender Name der Benachrichtigungsmethode."
msgid "AND"
msgstr "UND"
msgid "Add"
msgstr "Hinzufügen"
msgid "Add a match by"
msgstr "Fügen Sie eine Übereinstimmung hinzu"
msgid "Address"
msgstr "Adresse"
msgid "Address must contain a valid URL address."
msgstr "Die Adresse muss eine gültige URL-Adresse enthalten."
msgid "Address must contain a valid email address."
msgstr "Die Adresse muss eine gültige E-Mail-Adresse enthalten."
msgid "Alarm"
msgstr "Alarm"
msgid "Alarm Actions"
msgstr "Alarmaktionen"
msgid "Alarm Definition Details"
msgstr "Details zur Alarmdefinition"
msgid "Alarm Definitions"
msgstr "Alarmdefinitionen"
msgid "Alarm Details"
msgstr "Alarmdetails"
msgid "Alarm History"
msgstr "Alarmhistorie"
msgid "Alarm ID"
msgstr "Alarm-ID"
msgid "Alarm Id"
msgstr "Alarm-ID"
msgid "Alarm Metric Dimensions"
msgstr "Alarm Metrische Abmessungen"
#, python-format
msgid "Alarm definition %s has been created"
msgstr "Die Alarmdefinition %s wurde erstellt"
msgid "Alarm definition has been updated."
msgstr "Die Alarmdefinition wurde aktualisiert."
#, python-format
msgid "Alarm definition with %s name already exists"
msgstr "Die Alarmdefinition mit dem Namen %s ist bereits vorhanden"
msgid "Alarm has been created successfully."
msgstr "Der Alarm wurde erfolgreich erstellt."
msgid "Alarm has been edited successfully."
msgstr "Der Alarm wurde erfolgreich bearbeitet."
msgid "Alarms"
msgstr "Alarm"
msgid "Alarms for "
msgstr "Alarme für"
msgid "All Alarms"
msgstr "Alle Alarme"
msgid "An alarm expression."
msgstr "Ein Alarmausdruck."
msgid "An unique name of the alarm."
msgstr "Ein eindeutiger Name des Alarms."
msgid "Associated Metrics"
msgstr "Verbundene Metriken"
msgid "Browser local"
msgstr "Browser lokal"
msgid "Cancel"
msgstr "Stornieren"
msgid "Close"
msgstr "Schließen"
msgid "Could not retrieve alarm definitions"
msgstr "Alarmdefinitionen konnten nicht abgerufen werden"
#, python-format
msgid "Could not retrieve alarm for %s"
msgstr "Der Alarm für %s konnte nicht abgerufen werden"
#, python-format
msgid "Could not retrieve alarm history for %s"
msgstr "Der Alarmverlauf für %s konnte nicht abgerufen werden"
msgid "Could not retrieve alarms"
msgstr "Alarme konnten nicht abgerufen werden"
msgid "Could not retrieve notifications"
msgstr "Benachrichtigungen konnten nicht abgerufen werden"
msgid "Create Alarm"
msgstr "Alarm erstellen"
msgid "Create Alarm Definition"
msgstr "Alarmdefinition erstellen"
msgid "Create Notification"
msgstr "Benachrichtigung erstellen"
msgid "Create Notification Method"
msgstr "Benachrichtigungsmethode erstellen"
msgid "Critical"
msgstr "Kritisch"
msgid "Delete Alarm"
msgid_plural "Delete Alarms"
msgstr[0] "Lösche Alarm"
msgstr[1] "Lösche Alarme"
msgid "Delete Alarm Definition"
msgid_plural "Delete Alarm Definitions"
msgstr[0] "Lösche Alarmdefinition"
msgstr[1] "Lösche Alarmdefinitionen"
msgid "Delete Notification"
msgid_plural "Delete Notifications"
msgstr[0] "Lösche Notifikation"
msgstr[1] "Lösche Notifikationen"
msgid "Deleted Alarm"
msgid_plural "Deleted Alarms"
msgstr[0] "Gelöschter Alarm"
msgstr[1] "Gelöschte Alarme"
msgid "Deleted Alarm Definition"
msgid_plural "Deleted Alarm Definitions"
msgstr[0] "Gelöschte Alarmdefinition"
msgstr[1] "Gelöschte Alarmdefinitionen"
msgid "Deleted Notification"
msgid_plural "Deleted Notifications"
msgstr[0] "Gelöschte Notifikation"
msgstr[1] "Gelöschte Notifikationen"
msgid "Description"
msgstr "Beschreibung"
msgid "Details"
msgstr "Einzelheiten"
msgid "Deterministic"
msgstr "Deterministisch"
msgid "Dimension(s)"
msgstr "Maß(e)"
msgid "Edit Alarm"
msgstr "Alarm bearbeiten"
msgid "Edit Alarm Definition"
msgstr "Alarmdefinition bearbeiten"
msgid "Edit Alarm Definitions"
msgstr "Bearbeiten Sie Alarmdefinitionen"
msgid "Edit Notification"
msgstr "Benachrichtigung bearbeiten"
msgid "Expression"
msgstr "Ausdruck"
msgid "Failed to present alarm history"
msgstr "Alarmverlauf konnte nicht angezeigt werden"
msgid "Failed to validate name"
msgstr "Der Name konnte nicht validiert werden"
msgid "Filter Alarms"
msgstr "Filteralarme"
msgid "Graph Metric"
msgstr "Grafische Metrik"
msgid "High"
msgstr "Hoch"
msgid "Info"
msgstr "Info"
msgid "Key"
msgstr "Schlüssel"
msgid "Low"
msgstr "Niedrig"
msgid "Match by"
msgstr "Übereinstimmung mit"
msgid "Measurements for Alarms"
msgstr "Messungen für Alarme"
msgid "Medium"
msgstr "Mittel"
msgid "Metric Dimensions"
msgstr "Metrische Abmessungen"
msgid "Metric Name"
msgstr "Metrischer Name"
msgid "Metric Names"
msgstr "Metrik-Bezeichnungen"
msgid "Monitoring"
msgstr "Überwachung"
msgid "Name"
msgstr "Name"
msgid "New State"
msgstr "Neuer Status"
msgid "Next Page"
msgstr "Nächste Seite"
msgid "No notifications available."
msgstr "Keine Benachrichtigungen verfügbar."
msgid "None"
msgstr "Keiner"
#, python-format
msgid "Notification %s has already been deleted."
msgstr "Die Benachrichtigung %s wurde bereits gelöscht."
msgid "Notification Method Details"
msgstr "Details zur Benachrichtigungsmethode"
msgid "Notification has been edited successfully."
msgstr "Die Benachrichtigung wurde erfolgreich bearbeitet."
msgid "Notification method has been created successfully."
msgstr "Die Benachrichtigungsmethode wurde erfolgreich erstellt."
msgid ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgstr ""
"Benachrichtigungsmethoden. Benachrichtigungen können gesendet werden, wenn "
"ein Alarmstatusübergang auftritt."
msgid "Notifications"
msgstr "Benachrichtigungen"
msgid "Notifications Enabled"
msgstr "Benachrichtigungen aktiviert"
msgid "OK"
msgstr "OK"
msgid "OK Actions"
msgstr "OK Aktionen"
msgid "OR"
msgstr "ODER"
msgid "Old State"
msgstr "Alter Status"
msgid "OpenStack Services"
msgstr "OpenStack-Dienste"
msgid "Overview"
msgstr "Überblick"
msgid "Period"
msgstr "Zeitraum"
msgid "Period must be zero except for type webhook."
msgstr "Die Periode muss null sein, außer für den Typ webhook."
msgid "Previous Page"
msgstr "Vorherige Seite"
msgid "Reason"
msgstr "Grund"
msgid "Remove"
msgstr "Löschen"
msgid "Save"
msgstr "Speichern"
msgid "Save Notification"
msgstr "Benachrichtigung speichern"
msgid "Select Notification"
msgstr "Wählen Sie Benachrichtigung"
msgid "Servers"
msgstr "Server"
msgid "Service :"
msgstr "Dienst :"
msgid "Service Health"
msgstr "Dienste-Gesundheit"
msgid "Severity"
msgstr "Schwere"
msgid ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgstr ""
"Schweregrad eines Alarms. Muss entweder LOW, MEDIUM, HIGH oder CRITICAL "
"sein. Standard ist LOW."
msgid "Show Alarm Definition"
msgstr "Alarmdefinition anzeigen"
msgid "Show History"
msgstr "Zeige die Geschichte"
msgid "State"
msgstr "Zustand"
msgid "Status"
msgstr "Status"
msgid "Submit"
msgstr "einreichen"
msgid "The email/url address to notify."
msgstr "Die E-Mail-/URL-Adresse, die benachrichtigt werden soll."
msgid "The metric dimensions used to create unique alarms."
msgstr ""
"Die metrischen Dimensionen, die zum Erstellen von eindeutigen Alarmen "
"verwendet werden."
msgid "The notification period."
msgstr "Der Benachrichtigungszeitraum"
msgid "The type of notification method (i.e. email)."
msgstr "Der Typ der Benachrichtigungsmethode (z.B. E-Mail)."
msgid "Timestamp"
msgstr "Zeitstempel"
msgid "Type"
msgstr "Typ"
msgid "UTC"
msgstr "koordinierte Weltzeit"
#, python-format
msgid "Unable to create alarm definition %s"
msgstr "Die Alarmdefinition %s konnte nicht erstellt werden"
#, python-format
msgid "Unable to list alarms: %s"
msgstr "Alarme können nicht aufgelistet werden: %s"
msgid "Unable to retrieve alarm details."
msgstr "Die Alarmdetails können nicht abgerufen werden."
msgid "Unable to retrieve metrics"
msgstr "Die Messwerte konnten nicht abgerufen werden"
msgid "Unable to retrieve notification details."
msgstr "Die Benachrichtigungsdetails konnten nicht abgerufen werden."
msgid "Undetermined"
msgstr "Unbestimmt"
msgid "Undetermined Actions"
msgstr "Unbestimmte Aktionen"
msgid "Unknown name"
msgstr "Unbekannter Name"
#, python-format
msgid "User %s does not have sufficient privileges to access Kibana"
msgstr ""
"Benutzer %s verfügt nicht über ausreichende Berechtigungen zum Zugriff auf "
"Kibana"
msgid "Value"
msgstr "Wert"
msgid "avg"
msgstr "Durchschnitt"
msgid "count"
msgstr "Anzahl"
msgid "last"
msgstr "letzte"
msgid "max"
msgstr "max"
msgid "min"
msgstr "min"
msgid "sum"
msgstr "sum"

View File

@@ -1,66 +0,0 @@
# Frank Kloeker <eumel@arcor.de>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-03-06 16:04+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-10 09:29+0000\n"
"Last-Translator: Frank Kloeker <eumel@arcor.de>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "Add"
msgstr "Hinzufügen"
msgid "Add a dimension"
msgstr "Fügen Sie eine Dimension hinzu"
msgid "Comparator"
msgstr "Vergleicher"
msgid "Deterministic"
msgstr "Deterministisch"
msgid "Down"
msgstr "Nieder"
msgid "Edit"
msgstr "Bearbeiten"
msgid "Function"
msgstr "Funktion"
msgid "Matching Metrics"
msgstr "Übereinstimmende Metriken"
msgid "Metric"
msgstr "Metrik"
msgid "No metric available"
msgstr "Keine Metrik verfügbar"
msgid "Operators"
msgstr "Betreiber"
msgid "Remove"
msgstr "Löschen"
msgid "Submit"
msgstr "Einreichen"
msgid "Threshold"
msgstr "Schwelle"
msgid "Up"
msgstr "Oben"
msgid "dimensions"
msgstr "Maße"
msgid "name"
msgstr "Name"

View File

@@ -1,644 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
# Andi Chandler <andi@gowling.com>, 2017. #zanata
# Andi Chandler <andi@gowling.com>, 2018. #zanata
# Andi Chandler <andi@gowling.com>, 2020. #zanata
# Andi Chandler <andi@gowling.com>, 2022. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2022-07-06 20:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2022-05-25 09:28+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgid ""
"\n"
" Each alarm definition is defined by its expression composed out of\n"
" mathematical function, metric, time, times ,threshold and "
"comparator for\n"
" metric's value and the threshold. Time is the number of seconds for "
"the measurement\n"
" to be done. They can only be in a multiple of 60. Times is how many "
"times in a row\n"
" that expression must be true before triggering the alarm. Both time "
"and times are\n"
" optional and default to 60 and 1 respectively. Additionally it is "
"possible to narrow\n"
" evaluation of the alarm to certain entities by choosing their\n"
" dimensions. The deterministic alarms never enter UNDETERMINED "
"state.\n"
" Use them for metrics that are received sporadically.\n"
" "
msgstr ""
"\n"
" Each alarm definition is defined by its expression composed of\n"
" mathematical function, metric, time, times, threshold and "
"comparator for\n"
" the metric's value and the threshold. Time is the number of seconds "
"for the measurement\n"
" to be done. They can only be in a multiple of 60. Times is how many "
"times in a row\n"
" that expression must be true before triggering the alarm. Both time "
"and times are\n"
" optional and default to 60 and 1 respectively. Additionally, it is "
"possible to narrow\n"
" the evaluation of the alarm to certain entities by choosing their\n"
" dimensions. The deterministic alarms never enter an UNDETERMINED "
"state.\n"
" Use them for metrics that are received sporadically.\n"
" "
msgid ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgstr ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgid ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgstr ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgstr ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgid ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgstr ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgid ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook urls).\n"
" "
msgstr ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook URLs).\n"
" "
msgid ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgstr ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgid ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgstr ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgid ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgstr ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgstr ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgid ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgstr ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgid "+ Add more"
msgstr "+ Add more"
msgid "---Please select---"
msgstr "---Please select---"
msgid "A description of an alarm."
msgstr "A description of an alarm."
msgid "A descriptive name of the notification method."
msgstr "A descriptive name of the notification method."
msgid "AND"
msgstr "AND"
msgid "Add"
msgstr "Add"
msgid "Add a match by"
msgstr "Add a match by"
msgid "Address"
msgstr "Address"
msgid "Address must contain a valid URL address."
msgstr "Address must contain a valid URL address."
msgid "Address must contain a valid email address."
msgstr "Address must contain a valid email address."
msgid "Alarm"
msgstr "Alarm"
msgid "Alarm Actions"
msgstr "Alarm Actions"
msgid "Alarm Definition Details"
msgstr "Alarm Definition Details"
msgid "Alarm Definitions"
msgstr "Alarm Definitions"
msgid "Alarm Details"
msgstr "Alarm Details"
msgid "Alarm History"
msgstr "Alarm History"
msgid "Alarm ID"
msgstr "Alarm ID"
msgid "Alarm Id"
msgstr "Alarm Id"
msgid "Alarm Metric Dimensions"
msgstr "Alarm Metric Dimensions"
#, python-format
msgid "Alarm definition %s has been created"
msgstr "Alarm definition %s has been created"
msgid "Alarm definition has been updated."
msgstr "Alarm definition has been updated."
#, python-format
msgid "Alarm definition with %s name already exists"
msgstr "Alarm definition with %s name already exists"
msgid "Alarm has been created successfully."
msgstr "Alarm has been created successfully."
msgid "Alarm has been edited successfully."
msgstr "Alarm has been edited successfully."
msgid "Alarms"
msgstr "Alarms"
msgid "Alarms for "
msgstr "Alarms for "
msgid "All Alarms"
msgstr "All Alarms"
msgid "An alarm expression."
msgstr "An alarm expression."
msgid "An unique name of the alarm."
msgstr "An unique name of the alarm."
msgid "Associated Metrics"
msgstr "Associated Metrics"
msgid "Browser local"
msgstr "Browser local"
msgid "Cancel"
msgstr "Cancel"
msgid "Close"
msgstr "Close"
msgid "Could not retrieve alarm definitions"
msgstr "Could not retrieve alarm definitions"
#, python-format
msgid "Could not retrieve alarm for %s"
msgstr "Could not retrieve alarm for %s"
#, python-format
msgid "Could not retrieve alarm history for %s"
msgstr "Could not retrieve alarm history for %s"
msgid "Could not retrieve alarms"
msgstr "Could not retrieve alarms"
msgid "Could not retrieve notifications"
msgstr "Could not retrieve notifications"
msgid "Create Alarm"
msgstr "Create Alarm"
msgid "Create Alarm Definition"
msgstr "Create Alarm Definition"
msgid "Create Notification"
msgstr "Create Notification"
msgid "Create Notification Method"
msgstr "Create Notification Method"
msgid "Critical"
msgstr "Critical"
msgid "Delete Alarm"
msgid_plural "Delete Alarms"
msgstr[0] "Delete Alarm"
msgstr[1] "Delete Alarms"
msgid "Delete Alarm Definition"
msgid_plural "Delete Alarm Definitions"
msgstr[0] "Delete Alarm Definition"
msgstr[1] "Delete Alarm Definitions"
msgid "Delete Notification"
msgid_plural "Delete Notifications"
msgstr[0] "Delete Notification"
msgstr[1] "Delete Notifications"
msgid "Deleted Alarm"
msgid_plural "Deleted Alarms"
msgstr[0] "Deleted Alarm"
msgstr[1] "Deleted Alarms"
msgid "Deleted Alarm Definition"
msgid_plural "Deleted Alarm Definitions"
msgstr[0] "Deleted Alarm Definition"
msgstr[1] "Deleted Alarm Definitions"
msgid "Deleted Notification"
msgid_plural "Deleted Notifications"
msgstr[0] "Deleted Notification"
msgstr[1] "Deleted Notifications"
msgid "Description"
msgstr "Description"
msgid "Details"
msgstr "Details"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Dimension(s)"
msgstr "Dimension(s)"
msgid "Edit Alarm"
msgstr "Edit Alarm"
msgid "Edit Alarm Definition"
msgstr "Edit Alarm Definition"
msgid "Edit Alarm Definitions"
msgstr "Edit Alarm Definitions"
msgid "Edit Notification"
msgstr "Edit Notification"
msgid "Expression"
msgstr "Expression"
msgid "Failed to present alarm history"
msgstr "Failed to present alarm history"
msgid "Failed to validate name"
msgstr "Failed to validate name"
msgid "Filter Alarms"
msgstr "Filter Alarms"
msgid "Graph Metric"
msgstr "Graph Metric"
msgid "High"
msgstr "High"
msgid "Info"
msgstr "Info"
msgid "Key"
msgstr "Key"
msgid "Low"
msgstr "Low"
msgid "Match by"
msgstr "Match by"
msgid "Measurements for Alarms"
msgstr "Measurements for Alarms"
msgid "Medium"
msgstr "Medium"
msgid "Metric Dimensions"
msgstr "Metric Dimensions"
msgid "Metric Name"
msgstr "Metric Name"
msgid "Metric Names"
msgstr "Metric Names"
msgid "Monitoring"
msgstr "Monitoring"
msgid "Name"
msgstr "Name"
msgid "New State"
msgstr "New State"
msgid "Next Page"
msgstr "Next Page"
msgid "No notifications available."
msgstr "No notifications available."
msgid "None"
msgstr "None"
#, python-format
msgid "Notification %s has already been deleted."
msgstr "Notification %s has already been deleted."
msgid "Notification Method Details"
msgstr "Notification Method Details"
msgid "Notification has been edited successfully."
msgstr "Notification has been edited successfully."
msgid "Notification method has been created successfully."
msgstr "Notification method has been created successfully."
msgid ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgstr ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgid "Notifications"
msgstr "Notifications"
msgid "Notifications Enabled"
msgstr "Notifications Enabled"
msgid "OK"
msgstr "OK"
msgid "OK Actions"
msgstr "OK Actions"
msgid "OR"
msgstr "OR"
msgid "Old State"
msgstr "Old State"
msgid "OpenStack Services"
msgstr "OpenStack Services"
msgid "Overview"
msgstr "Overview"
msgid "Period"
msgstr "Period"
msgid "Period must be zero except for type webhook."
msgstr "Period must be zero except for type webhook."
msgid "Previous Page"
msgstr "Previous Page"
msgid "Reason"
msgstr "Reason"
msgid "Remove"
msgstr "Remove"
msgid "Save"
msgstr "Save"
msgid "Save Notification"
msgstr "Save Notification"
msgid "Select Notification"
msgstr "Select Notification"
msgid "Servers"
msgstr "Servers"
msgid "Service :"
msgstr "Service :"
msgid "Service Health"
msgstr "Service Health"
msgid "Severity"
msgstr "Severity"
msgid ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgstr ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgid "Show Alarm Definition"
msgstr "Show Alarm Definition"
msgid "Show History"
msgstr "Show History"
msgid "State"
msgstr "State"
msgid "Status"
msgstr "Status"
msgid "Submit"
msgstr "Submit"
msgid "The email/url address to notify."
msgstr "The email/URL address to notify."
msgid "The metric dimensions used to create unique alarms."
msgstr "The metric dimensions used to create unique alarms."
msgid "The notification period."
msgstr "The notification period."
msgid "The type of notification method (i.e. email)."
msgstr "The type of notification method (i.e. email)."
msgid "Timestamp"
msgstr "Timestamp"
msgid "Type"
msgstr "Type"
msgid "UTC"
msgstr "UTC"
#, python-format
msgid "Unable to create alarm definition %s"
msgstr "Unable to create alarm definition %s"
msgid "Unable to create alarm definition."
msgstr "Unable to create alarm definition."
msgid "Unable to create the alarm."
msgstr "Unable to create the alarm."
msgid "Unable to create the notification method."
msgstr "Unable to create the notification method."
msgid "Unable to delete notification."
msgstr "Unable to delete notification."
msgid "Unable to edit the alarm."
msgstr "Unable to edit the alarm."
msgid "Unable to edit the notification."
msgstr "Unable to edit the notification."
#, python-format
msgid "Unable to list alarms: %s"
msgstr "Unable to list alarms: %s"
msgid "Unable to retrieve alarm details."
msgstr "Unable to retrieve alarm details."
msgid "Unable to retrieve metrics"
msgstr "Unable to retrieve metrics"
msgid "Unable to retrieve notification details."
msgstr "Unable to retrieve notification details."
msgid "Unable to retrieve notifications."
msgstr "Unable to retrieve notifications."
msgid "Unable to update alarm definition."
msgstr "Unable to update alarm definition."
msgid "Undetermined"
msgstr "Undetermined"
msgid "Undetermined Actions"
msgstr "Undetermined Actions"
msgid "Unknown name"
msgstr "Unknown name"
#, python-format
msgid "User %s does not have sufficient privileges to access Kibana"
msgstr "User %s does not have sufficient privileges to access Kibana"
msgid "Value"
msgstr "Value"
msgid "avg"
msgstr "avg"
msgid "count"
msgstr "count"
msgid "last"
msgstr "last"
msgid "max"
msgstr "max"
msgid "min"
msgstr "min"
msgid "sum"
msgstr "sum"

View File

@@ -1,72 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-02-05 13:14+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-10-17 07:02+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "Add"
msgstr "Add"
msgid "Add a dimension"
msgstr "Add a dimension"
msgid "Comparator"
msgstr "Comparator"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Down"
msgstr "Down"
msgid "Edit"
msgstr "Edit"
msgid "Function"
msgstr "Function"
msgid "Matching Metrics"
msgstr "Matching Metrics"
msgid "Metric"
msgstr "Metric"
msgid "No metric available"
msgstr "No metric available"
msgid "Operators"
msgstr "Operators"
msgid "Remove"
msgstr "Remove"
msgid "Submit"
msgstr "Submit"
msgid "Threshold"
msgstr "Threshold"
msgid "Time"
msgstr "Time"
msgid "Times"
msgstr "Times"
msgid "Up"
msgstr "Up"
msgid "dimensions"
msgstr "dimensions"
msgid "name"
msgstr "name"

View File

@@ -1,562 +0,0 @@
# suhartono <cloudsuhartono@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2022-07-06 20:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-05-09 04:46+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
"Name field digunakan untuk mengidentifikasi definisi alarm. "
msgid ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgstr ""
"\n"
"Jika untuk beberapa notifikasi transisi seharusnya tidak terkirim, mereka "
"dapat di non aktif."
msgid ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgstr ""
"\n"
"Description field dapat digunakan untuk menjelaskan tujuan definisi alarm.\n"
" "
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
"Name field digunakan untuk mengidentifikasi definisi alarm. "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgstr ""
"\n"
"Notifications field berisi daftar Notifications yang seharusnya\n"
"         dikirim saat beralih ke status bagian lain.\n"
" "
msgid ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgstr ""
"\n"
"Severity field memungkinkan untuk menentukan pentingnya definisi alarm.\n"
" "
msgid ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook urls).\n"
" "
msgstr ""
"\n"
"Nilai non-zero pada Period field menunjukkan seberapa sering sebuah "
"notifikasi\n"
"       harus dikirim ulang (hanya berlaku untuk url webhook).\n"
" "
msgid ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgstr ""
"\n"
"Address field menunjukkan alamat email, URL, atau PagerDuty\n"
"       kunci layanan untuk diberitahu\n"
" "
msgid ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgstr ""
"\n"
"Expression field jika benar, memicu notifikasi untuk dikirim.\n"
"       Lihat <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> untuk bagaimana menulis sebuah "
"ekspresi.\n"
" "
msgid ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgstr ""
"\n"
"Name field digunakan untuk mengidentifikasi metode notifikasi. "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgstr ""
"\n"
"Notifications field berisi daftar Notifications yang harus dikirim saat "
"beralih ke status ALARM lainnya.\n"
" "
msgid ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgstr ""
"\n"
"Type field menunjukkan bagaimana notifikasi dikirim saat alarm dipicu. "
msgid "+ Add more"
msgstr "+ Add more"
msgid "---Please select---"
msgstr "---Please select---"
msgid "A description of an alarm."
msgstr "Penjelasan alarm"
msgid "A descriptive name of the notification method."
msgstr "Nama deskriptif dari metode notifikasi."
msgid "AND"
msgstr "AND"
msgid "Add"
msgstr "Add"
msgid "Add a match by"
msgstr "Tambahkan kecocokan dengan"
msgid "Address"
msgstr "Address"
msgid "Address must contain a valid URL address."
msgstr "Alamat harus berisi alamat URL yang valid."
msgid "Address must contain a valid email address."
msgstr "Alamat harus berisi alamat email yang valid."
msgid "Alarm"
msgstr "Alarm"
msgid "Alarm Actions"
msgstr "Alarm Actions"
msgid "Alarm Definition Details"
msgstr "Alarm Definition Details"
msgid "Alarm Definitions"
msgstr "Alarm Definitions"
msgid "Alarm Details"
msgstr "Alarm Details"
msgid "Alarm History"
msgstr "Alarm History"
msgid "Alarm ID"
msgstr "Alarm ID"
msgid "Alarm Id"
msgstr "Alarm Id"
msgid "Alarm Metric Dimensions"
msgstr "Alarm Metric Dimensions"
#, python-format
msgid "Alarm definition %s has been created"
msgstr "Alarm definition %s telah dibuat"
msgid "Alarm definition has been updated."
msgstr "Definisi alarm telah diperbarui."
#, python-format
msgid "Alarm definition with %s name already exists"
msgstr "Definisi alarm dengan nama %s sudah ada"
msgid "Alarm has been created successfully."
msgstr "Alarm telah berhasil dibuat."
msgid "Alarm has been edited successfully."
msgstr "Alarm telah berhasil diedit."
msgid "Alarms"
msgstr "Alarm"
msgid "Alarms for "
msgstr "Alarm untuk"
msgid "All Alarms"
msgstr "Semua Alarms"
msgid "An alarm expression."
msgstr "Sebuah ekspresi alarm."
msgid "An unique name of the alarm."
msgstr "Nama unik dari alarm."
msgid "Associated Metrics"
msgstr "Associated Metrics"
msgid "Browser local"
msgstr "Browser lokal"
msgid "Cancel"
msgstr "Cancel"
msgid "Close"
msgstr "Close"
msgid "Could not retrieve alarm definitions"
msgstr "Tidak dapat mengambil definisi alarm"
#, python-format
msgid "Could not retrieve alarm for %s"
msgstr "Tidak dapat mengambil alarm untuk %s"
#, python-format
msgid "Could not retrieve alarm history for %s"
msgstr "Tidak dapat mengambil riwayat alarm untuk %s"
msgid "Could not retrieve alarms"
msgstr "Tidak dapat mengambil alarm"
msgid "Could not retrieve notifications"
msgstr "Tidak dapat mengambil notifikasi"
msgid "Create Alarm"
msgstr "Create Alarm"
msgid "Create Alarm Definition"
msgstr "Create Alarm Definition"
msgid "Create Notification"
msgstr "Create Notification"
msgid "Create Notification Method"
msgstr "Create Notification Method"
msgid "Critical"
msgstr "Critical"
msgid "Delete Alarm"
msgid_plural "Delete Alarms"
msgstr[0] "Delete Alarm"
msgid "Delete Alarm Definition"
msgid_plural "Delete Alarm Definitions"
msgstr[0] "Delete Alarm Definition"
msgid "Delete Notification"
msgid_plural "Delete Notifications"
msgstr[0] "Delete Notification"
msgid "Deleted Alarm"
msgid_plural "Deleted Alarms"
msgstr[0] "Deleted Alarm"
msgid "Deleted Alarm Definition"
msgid_plural "Deleted Alarm Definitions"
msgstr[0] "Deleted Alarm Definition"
msgid "Deleted Notification"
msgid_plural "Deleted Notifications"
msgstr[0] "Deleted Notification"
msgid "Description"
msgstr "Deskripsi"
msgid "Details"
msgstr "Rincian"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Dimension(s)"
msgstr "Dimension(s)"
msgid "Edit Alarm"
msgstr "Edit Alarm"
msgid "Edit Alarm Definition"
msgstr "Edit Alarm Definition"
msgid "Edit Alarm Definitions"
msgstr "Edit Alarm Definitions"
msgid "Edit Notification"
msgstr "Edit Notification"
msgid "Expression"
msgstr "Ekspresi"
msgid "Failed to present alarm history"
msgstr "Gagal menampilkan riwayat alarm"
msgid "Failed to validate name"
msgstr "Gagal memvalidasi nama"
msgid "Filter Alarms"
msgstr "Filter Alarms"
msgid "Graph Metric"
msgstr "Graph Metric"
msgid "High"
msgstr "High (tinggi)"
msgid "Info"
msgstr "Info"
msgid "Key"
msgstr "Key"
msgid "Low"
msgstr "Low (rendah)"
msgid "Match by"
msgstr "Match by (cocok dengan)"
msgid "Measurements for Alarms"
msgstr "Measurements untuk Alarms"
msgid "Medium"
msgstr "Medium (sedang)"
msgid "Metric Dimensions"
msgstr "Metric Dimensions"
msgid "Metric Name"
msgstr "Metric Name"
msgid "Metric Names"
msgstr "Metric Names"
msgid "Monitoring"
msgstr "Monitoring"
msgid "Name"
msgstr "Name"
msgid "New State"
msgstr "New State"
msgid "Next Page"
msgstr "Next Page"
msgid "No notifications available."
msgstr "Tidak ada pemberitahuan yang tersedia."
msgid "None"
msgstr "None"
#, python-format
msgid "Notification %s has already been deleted."
msgstr "Notifikasi %s telah dihapus."
msgid "Notification Method Details"
msgstr "Notification Method Details"
msgid "Notification has been edited successfully."
msgstr "Notifikasi berhasil diedit."
msgid "Notification method has been created successfully."
msgstr "Metode notifikasi telah berhasil dibuat."
msgid ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgstr ""
"Metode notifikasi. Notifikasi dapat dikirim ketika transisi status alarm "
"terjadi."
msgid "Notifications"
msgstr "Notifikasi"
msgid "Notifications Enabled"
msgstr "Notifikasi Diaktifkan"
msgid "OK"
msgstr "OK"
msgid "OK Actions"
msgstr "OK Actions"
msgid "OR"
msgstr "OR"
msgid "Old State"
msgstr "Old State"
msgid "OpenStack Services"
msgstr "OpenStack Services"
msgid "Overview"
msgstr "Overview"
msgid "Period"
msgstr "Period"
msgid "Period must be zero except for type webhook."
msgstr "Periode harus nol kecuali untuk jenis webhook."
msgid "Previous Page"
msgstr "Previous Page"
msgid "Reason"
msgstr "Reason"
msgid "Remove"
msgstr "Remove"
msgid "Save"
msgstr "Save"
msgid "Save Notification"
msgstr "Save Notification"
msgid "Select Notification"
msgstr "Select Notification"
msgid "Servers"
msgstr "Servers"
msgid "Service :"
msgstr "Service :"
msgid "Service Health"
msgstr "Service Health"
msgid "Severity"
msgstr "Severity"
msgid ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgstr ""
"Tingkat keparahan alarm. Harus berada di LOW, MEDIUM, HIGH or CRITICAL. atau "
"KRITIS. Default-nya LOW."
msgid "Show Alarm Definition"
msgstr "Show Alarm Definition"
msgid "Show History"
msgstr "Show History"
msgid "State"
msgstr "State"
msgid "Status"
msgstr "Status"
msgid "Submit"
msgstr "Submit"
msgid "The email/url address to notify."
msgstr "Alamat email/url untuk diberitahu"
msgid "The metric dimensions used to create unique alarms."
msgstr "Dimensi metrik digunakan untuk membuat alarm unik."
msgid "The notification period."
msgstr "Periode notifikasi."
msgid "The type of notification method (i.e. email)."
msgstr "Jenis metode pemberitahuan (yaitu email)."
msgid "Timestamp"
msgstr "Timestamp"
msgid "Type"
msgstr "Type"
msgid "UTC"
msgstr "UTC"
#, python-format
msgid "Unable to create alarm definition %s"
msgstr "Tidak dapat membuat definisi alarm %s"
#, python-format
msgid "Unable to list alarms: %s"
msgstr "Tidak dapat menampilkan alarm: %s"
msgid "Unable to retrieve alarm details."
msgstr "Tidak dapat mengambil detail alarm."
msgid "Unable to retrieve metrics"
msgstr "Tidak dapat mengambil metrik"
msgid "Unable to retrieve notification details."
msgstr "Tidak dapat mengambil detail notifikasi."
msgid "Undetermined"
msgstr "Undetermined"
msgid "Undetermined Actions"
msgstr "Undetermined Actions"
msgid "Unknown name"
msgstr "Nama tidak dikenal"
#, python-format
msgid "User %s does not have sufficient privileges to access Kibana"
msgstr "Pengguna %s tidak memiliki hak yang cukup untuk mengakses Kibana"
msgid "Value"
msgstr "Value"
msgid "avg"
msgstr "avg"
msgid "count"
msgstr "count"
msgid "last"
msgstr "last"
msgid "max"
msgstr "max"
msgid "min"
msgstr "min"
msgid "sum"
msgstr "sum"

View File

@@ -1,66 +0,0 @@
# suhartono <cloudsuhartono@gmail.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-03-06 16:04+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-02-28 02:09+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Add"
msgstr "Add"
msgid "Add a dimension"
msgstr "Tambahkan dimensi"
msgid "Comparator"
msgstr "Comparator"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Down"
msgstr "Down"
msgid "Edit"
msgstr "Edit"
msgid "Function"
msgstr "Function"
msgid "Matching Metrics"
msgstr "Matching Metrics"
msgid "Metric"
msgstr "Metric"
msgid "No metric available"
msgstr "Tidak ada metrik yang tersedia"
msgid "Operators"
msgstr "Operators"
msgid "Remove"
msgstr "Remove"
msgid "Submit"
msgstr "Submit"
msgid "Threshold"
msgstr "Threshold"
msgid "Up"
msgstr "Up"
msgid "dimensions"
msgstr "dimensi"
msgid "name"
msgstr "nama"

View File

@@ -1,595 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Sayako Kondo <kondoh.sayako@jp.fujitsu.com>, 2016. #zanata
# Shinya Kawabata <s-kawabata@wx.jp.nec.com>, 2016. #zanata
# Shu Muto <shu.mutow@gmail.com>, 2016. #zanata
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2016. #zanata
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2017. #zanata
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2018. #zanata
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2021. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2022-07-06 20:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2021-03-12 09:09+0000\n"
"Last-Translator: haruki yamanashi <h.yamanashi@jp.fujitsu.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" 名前フィールドは、アラーム定義を特定するために使用されます。"
msgid ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgstr ""
"\n"
" 特定のアラーム状態遷移時の通知を無効にすることができます。\n"
" \n"
" "
msgid ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgstr ""
"\n"
" 説明フィールドはアラーム定義の用途を説明するために使用されます。\n"
" "
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" 名前フィールドはアラーム定義を識別するために使用されます。\n"
" "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgstr ""
"\n"
" 通知フィールドには、アラーム状態が遷移した時に送信される通知のリスト"
"が含まれます。\n"
" "
msgid ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgstr ""
"\n"
" 重大度フィールドはアラーム定義の重要性を特定するために使用されま"
"す。\n"
" "
msgid ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook urls).\n"
" "
msgstr ""
"\n"
" 期間フィールドは、値が0でない場合どの頻度で通知が再送されるかを示しま"
"す。(webhook の場合のみ有効です。)\n"
" "
msgid ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgstr ""
"\n"
" アドレスフィールドは通知を受け取るメールアドレス、URL、または "
"PagerDuty サービスキーを示します。\n"
" "
msgid ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgstr ""
"\n"
" 条件式フィールドは、その式が真となった場合に通知が発行されます。条件式"
"の記述方法は、 <a href=\"https://github.com/openstack/monasca-api/blob/"
"master/docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a>を参照してください。\n"
" "
msgid ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgstr ""
"\n"
" 名前フィールドは通知方法を識別するために使用されます。\n"
" "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgstr ""
"\n"
" 通知フィールドには、アラーム状態が遷移した時に送信される通知のリストが"
"含まれます。\n"
" "
msgid ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgstr ""
"\n"
" 種別フィールドはアラームが発生した時にどのように通知が行われるかを示し"
"ます。\n"
" "
msgid "+ Add more"
msgstr "+ さらに追加"
msgid "---Please select---"
msgstr "---選択してください---"
msgid "A description of an alarm."
msgstr "アラームの説明"
msgid "A descriptive name of the notification method."
msgstr "通知方法の名前"
msgid "AND"
msgstr "AND"
msgid "Add"
msgstr "追加"
msgid "Add a match by"
msgstr "マッチの追加"
msgid "Address"
msgstr "アドレス"
msgid "Address must contain a valid URL address."
msgstr "アドレスフィールドには、有効な URL を入力する必要があります。"
msgid "Address must contain a valid email address."
msgstr "アドレスフィールドには、有効なメールアドレスを入力する必要があります。"
msgid "Alarm"
msgstr "アラーム"
msgid "Alarm Actions"
msgstr "ALARM 時のアクション"
msgid "Alarm Definition Details"
msgstr "アラーム定義の詳細"
msgid "Alarm Definitions"
msgstr "アラーム定義"
msgid "Alarm Details"
msgstr "アラームの詳細"
msgid "Alarm History"
msgstr "アラーム履歴"
msgid "Alarm ID"
msgstr "アラーム ID"
msgid "Alarm Id"
msgstr "アラーム ID"
msgid "Alarm Metric Dimensions"
msgstr "ディメンション"
#, python-format
msgid "Alarm definition %s has been created"
msgstr "アラーム定義「%s」が作成されました"
msgid "Alarm definition has been updated."
msgstr "アラーム定義が正常に更新されました。"
#, python-format
msgid "Alarm definition with %s name already exists"
msgstr "アラーム定義「%s」は既に存在しています"
msgid "Alarm has been created successfully."
msgstr "アラームが正常に作成されました。"
msgid "Alarm has been edited successfully."
msgstr "アラームが正常に更新されました。"
msgid "Alarms"
msgstr "アラーム"
msgid "Alarms for "
msgstr "アラーム 対象:"
msgid "All Alarms"
msgstr "全アラーム"
msgid "An alarm expression."
msgstr "アラーム条件式"
msgid "An unique name of the alarm."
msgstr "アラームの名前"
msgid "Associated Metrics"
msgstr "関連するメトリック"
msgid "Browser local"
msgstr "ローカルタイム"
msgid "Cancel"
msgstr "取り消し"
msgid "Close"
msgstr "閉じる"
msgid "Could not retrieve alarm definitions"
msgstr "アラーム定義を取得できませんでした。"
#, python-format
msgid "Could not retrieve alarm for %s"
msgstr "アラームが取得できませんでした: %s"
#, python-format
msgid "Could not retrieve alarm history for %s"
msgstr "アラーム履歴が取得できませんでした: %s"
msgid "Could not retrieve alarms"
msgstr "アラームを取得できませんでした"
msgid "Could not retrieve notifications"
msgstr "通知を取得できませんでした"
msgid "Create Alarm"
msgstr "アラームの作成"
msgid "Create Alarm Definition"
msgstr "アラーム定義の作成"
msgid "Create Notification"
msgstr "通知の作成"
msgid "Create Notification Method"
msgstr "通知方法の作成"
msgid "Critical"
msgstr "Critical"
msgid "Delete Alarm"
msgid_plural "Delete Alarms"
msgstr[0] "アラームの削除"
msgid "Delete Alarm Definition"
msgid_plural "Delete Alarm Definitions"
msgstr[0] "アラーム定義の削除"
msgid "Delete Notification"
msgid_plural "Delete Notifications"
msgstr[0] "通知の削除"
msgid "Deleted Alarm"
msgid_plural "Deleted Alarms"
msgstr[0] "削除されたアラーム"
msgid "Deleted Alarm Definition"
msgid_plural "Deleted Alarm Definitions"
msgstr[0] "削除されたアラーム定義"
msgid "Deleted Notification"
msgid_plural "Deleted Notifications"
msgstr[0] "削除された通知"
msgid "Description"
msgstr "説明"
msgid "Details"
msgstr "詳細"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Dimension(s)"
msgstr "ディメンション"
msgid "Edit Alarm"
msgstr "アラームの編集"
msgid "Edit Alarm Definition"
msgstr "アラーム定義の編集"
msgid "Edit Alarm Definitions"
msgstr "アラーム定義の編集"
msgid "Edit Notification"
msgstr "通知の編集"
msgid "Expression"
msgstr "条件式"
msgid "Failed to present alarm history"
msgstr "アラーム履歴の表示に失敗しました"
msgid "Failed to validate name"
msgstr "検証に失敗しました"
msgid "Filter Alarms"
msgstr "アラームのフィルター"
msgid "Graph Metric"
msgstr "グラフの表示"
msgid "High"
msgstr "High"
msgid "Info"
msgstr "情報"
msgid "Key"
msgstr "キー"
msgid "Low"
msgstr "Low"
msgid "Match by"
msgstr "マッチ"
msgid "Measurements for Alarms"
msgstr "アラームの測定項目"
msgid "Medium"
msgstr "Medium"
msgid "Metric Dimensions"
msgstr "ディメンション"
msgid "Metric Name"
msgstr "メトリック名"
msgid "Metric Names"
msgstr "メトリック名"
msgid "Monitoring"
msgstr "モニタリング"
msgid "Name"
msgstr "名前"
msgid "New State"
msgstr "後状態"
msgid "Next Page"
msgstr "次のページ"
msgid "No notifications available."
msgstr "通知がありません。"
msgid "None"
msgstr "なし"
#, python-format
msgid "Notification %s has already been deleted."
msgstr "通知「%s」はすでに削除されています。"
msgid "Notification Method Details"
msgstr "通知方法の詳細"
msgid "Notification has been edited successfully."
msgstr "通知は正常に更新されました。"
msgid "Notification method has been created successfully."
msgstr "通知方法は正常に作成されました。"
msgid ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgstr "通知方法。アラーム状態遷移が起こると通知が送られます。"
msgid "Notifications"
msgstr "通知"
msgid "Notifications Enabled"
msgstr "通知の有効化"
msgid "OK"
msgstr "OK"
msgid "OK Actions"
msgstr "OK 時のアクション"
msgid "OR"
msgstr "OR"
msgid "Old State"
msgstr "前状態"
msgid "OpenStack Services"
msgstr "OpenStack サービス"
msgid "Overview"
msgstr "概要"
msgid "Period"
msgstr "期間"
msgid "Period must be zero except for type webhook."
msgstr "Webhook 以外のタイプを選択した場合、期間には0を指定してください。"
msgid "Previous Page"
msgstr "前のページ"
msgid "Reason"
msgstr "理由"
msgid "Remove"
msgstr "削除"
msgid "Save"
msgstr "保存"
msgid "Save Notification"
msgstr "通知の保存"
msgid "Select Notification"
msgstr "通知の選択"
msgid "Servers"
msgstr "サーバー"
msgid "Service :"
msgstr "サービス :"
msgid "Service Health"
msgstr "サービスヘルス"
msgid "Severity"
msgstr "重大度"
msgid ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgstr ""
"アラームの重大度。LOW、MEDIUM、HIGH、または CRITICAL のいずれかです。デフォル"
"トは LOW です。"
msgid "Show Alarm Definition"
msgstr "アラーム定義の表示"
msgid "Show History"
msgstr "履歴の表示"
msgid "State"
msgstr "状態"
msgid "Status"
msgstr "ステータス"
msgid "Submit"
msgstr "決定"
msgid "The email/url address to notify."
msgstr "通知先の メール/URL アドレス"
msgid "The metric dimensions used to create unique alarms."
msgstr "一意なアラームを生成するために使われるメトリックのディメンション"
msgid "The notification period."
msgstr "通知が再送される期間(webhook の場合のみ有効)"
msgid "The type of notification method (i.e. email)."
msgstr "通知方法の種別 (例 メール)"
msgid "Timestamp"
msgstr "タイムスタンプ"
msgid "Type"
msgstr "種別"
msgid "UTC"
msgstr "UTC"
#, python-format
msgid "Unable to create alarm definition %s"
msgstr "アラーム定義「%s」を作成できません"
msgid "Unable to create alarm definition."
msgstr "アラーム定義を作成できません。"
msgid "Unable to create the alarm."
msgstr "アラームを作成できません。"
msgid "Unable to create the notification method."
msgstr "通知方法を作成できません。"
msgid "Unable to delete notification."
msgstr "通知を削除できません。"
msgid "Unable to edit the alarm."
msgstr "アラームを編集できません。"
msgid "Unable to edit the notification."
msgstr "通知を編集できません。"
#, python-format
msgid "Unable to list alarms: %s"
msgstr "アラームの一覧を表示できません: %s"
msgid "Unable to retrieve alarm details."
msgstr "アラームの詳細を取得できません。"
msgid "Unable to retrieve metrics"
msgstr "メトリックを取得できません"
msgid "Unable to retrieve notification details."
msgstr "通知の詳細を取得できません"
msgid "Unable to retrieve notifications."
msgstr "通知を取得できませんでした。"
msgid "Unable to update alarm definition."
msgstr "アラーム定義を更新できません。"
msgid "Undetermined"
msgstr "Undetermined"
msgid "Undetermined Actions"
msgstr "UNDETERMINED 時のアクション"
msgid "Unknown name"
msgstr "不明"
#, python-format
msgid "User %s does not have sufficient privileges to access Kibana"
msgstr "ユーザー %s は Kibana にアクセスするための権限がありません"
msgid "Value"
msgstr "値"
msgid "avg"
msgstr "avg"
msgid "count"
msgstr "count"
msgid "last"
msgstr "last"
msgid "max"
msgstr "max"
msgid "min"
msgstr "min"
msgid "sum"
msgstr "sum"

View File

@@ -1,67 +0,0 @@
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2016. #zanata
# haruki yamanashi <h.yamanashi@jp.fujitsu.com>, 2018. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-18 08:05+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2018-01-24 05:46+0000\n"
"Last-Translator: haruki yamanashi <h.yamanashi@jp.fujitsu.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Add"
msgstr "追加"
msgid "Add a dimension"
msgstr "ディメンションの追加"
msgid "Comparator"
msgstr "比較演算子"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Down"
msgstr "Down"
msgid "Edit"
msgstr "編集"
msgid "Function"
msgstr "関数"
msgid "Matching Metrics"
msgstr "マッチしたメトリック"
msgid "Metric"
msgstr "メトリック"
msgid "No metric available"
msgstr "メトリックが取得できません"
msgid "Operators"
msgstr "演算子"
msgid "Remove"
msgstr "削除"
msgid "Submit"
msgstr "決定"
msgid "Threshold"
msgstr "閾値"
msgid "Up"
msgstr "Up"
msgid "dimensions"
msgstr "ディメンション"
msgid "name"
msgstr "名前"

View File

@@ -1,558 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2018. #zanata
# Sungjin Kang <gang.sungjin@gmail.com>, 2018. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2020. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2022-07-06 20:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2020-05-05 11:49+0000\n"
"Last-Translator: Ian Y. Choi <ianyrchoi@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko_KR\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
"이름 필드는 알림 정의를 식별하는데 사용됩니다."
msgid ""
"\n"
" If for some transitions notifications should not be sent they can "
"be\n"
" disabled.\n"
" "
msgstr ""
"\n"
"일부 전환의 경우 알림을 보내지 않아야 한다면 알림을 비활성화할 수 있습니다."
msgid ""
"\n"
" The Description field can be used to describe alarm definition's\n"
" purpose.\n"
" "
msgstr ""
"\n"
" 설명 필드는 알람 정의 목적을 설명하는데 사용할 수\n"
" 있습니다.\n"
" "
msgid ""
"\n"
" The Name field is used to identify the alarm definition.\n"
" "
msgstr ""
"\n"
" 이름 필드는 알림 정의를 식별하는데 사용됩니다.\n"
" "
msgid ""
"\n"
" The Notifications field contains the list of Notifications that "
"should\n"
" be sent when transitioning to another state.\n"
" "
msgstr ""
"\n"
"알림 필드에는 다른 상태로 전환할 때 전송해야 하는 알림 목록이 포함됩니다."
msgid ""
"\n"
" The Severity field allows to specify the importance of alarm\n"
" definition.\n"
" "
msgstr ""
"\n"
" Severity(심각도) 필드는 경보 정의에 대한 중요성을 \n"
" 지정할 수 있습니다.\n"
" "
msgid ""
"\n"
" A non-zero value in the period field indicates how frequently a "
"notification\n"
" should be resent (only valid for webhook urls).\n"
" "
msgstr ""
"\n"
"기간 필드에 0이 아닌 값은 알림이 얼마나 자주 응답해야 하는지를 나타냅니다(웹 "
"후크 URL에만 유효)."
msgid ""
"\n"
" The Address field indicates the email address, URL, or PagerDuty "
"service\n"
" key to be notified.\n"
" "
msgstr ""
"\n"
" 주소 필드는 알림을 받을 이메일 주소, URL 또는\n"
" PagerDuty 서비스 키를 나타냅니다.\n"
" "
msgid ""
"\n"
" The Expression field which if true, triggers a notification to be "
"sent.\n"
" See <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" "
"target=\"_blank\">Alarm Expressions</a> for how to write an expression.\n"
" "
msgstr ""
"\n"
"이 표현 필드는 True이면, 전송할 알림이 트리거됩니다.\n"
"자세한 내용은 <a href=\"https://github.com/openstack/monasca-api/blob/master/"
"docs/monasca-api-spec.md#alarm-definition-expressions\" target=\"_blank\">을 "
"참조하십시오. 알람 표현식 </a> 은 표현 작성방법에 관한 것입니다."
msgid ""
"\n"
" The Name field is used to identify the notification method.\n"
" "
msgstr ""
"\n"
"이름 필드는 알림 메서드를 식별하는데 사용됩니다."
msgid ""
"\n"
" The Notifications field contains the list of Notifications that should "
"be sent when transitioning to another ALARM state.\n"
" "
msgstr ""
"\n"
"알림 필드에는 다른 ALARM 상태로 전환할 때 전송해야 하는 알림 목록이 포함됩니"
"다."
msgid ""
"\n"
" The Type field indicates how the notification is sent when an alarm is "
"triggered.\n"
" "
msgstr ""
"\n"
"타입 필드는 알람이 트리거될 때 알림을 보내는 방법을 나타냅니다."
msgid "+ Add more"
msgstr "+ 더 추가"
msgid "---Please select---"
msgstr "---선택 해주세요---"
msgid "A description of an alarm."
msgstr "알람에 대한 설명입니다."
msgid "A descriptive name of the notification method."
msgstr "통지 방법에 대한 설명 이름."
msgid "AND"
msgstr "AND"
msgid "Add"
msgstr "추가"
msgid "Add a match by"
msgstr "일치 항목 추가"
msgid "Address"
msgstr "주소"
msgid "Address must contain a valid URL address."
msgstr "주소는 유효한 URL 주소를 포함해야 합니다."
msgid "Address must contain a valid email address."
msgstr "주소는 유효한 이메일 주소를 포함해야 합니다."
msgid "Alarm"
msgstr "알람"
msgid "Alarm Actions"
msgstr "알람 동작"
msgid "Alarm Definition Details"
msgstr "알람 정의 세부 사항"
msgid "Alarm Definitions"
msgstr "알람 정의"
msgid "Alarm Details"
msgstr "알람 상세 정보"
msgid "Alarm History"
msgstr "알람 이력"
msgid "Alarm ID"
msgstr "알람 ID"
msgid "Alarm Id"
msgstr "알람 Id"
msgid "Alarm Metric Dimensions"
msgstr "알람 메트릭 치수"
#, python-format
msgid "Alarm definition %s has been created"
msgstr "알람 정의 %s 가 생성되었습니다."
msgid "Alarm definition has been updated."
msgstr "알람 정의가 업데이트 되었습니다."
#, python-format
msgid "Alarm definition with %s name already exists"
msgstr "%s 이름으로 되어있는 알람 정의가 이미 있습니다."
msgid "Alarm has been created successfully."
msgstr "알람을 성공적으로 생성하였습니다."
msgid "Alarm has been edited successfully."
msgstr "알람을 성공적으로 수정하였습니다."
msgid "Alarms"
msgstr "알람"
msgid "Alarms for "
msgstr "에 대한 알람"
msgid "All Alarms"
msgstr "모든 알람"
msgid "An alarm expression."
msgstr "알람 표현식."
msgid "An unique name of the alarm."
msgstr "알람에 대한 고유한 이름."
msgid "Associated Metrics"
msgstr "연관된 메트릭"
msgid "Browser local"
msgstr "브라우저 로컬"
msgid "Cancel"
msgstr "취소"
msgid "Close"
msgstr "닫기"
msgid "Could not retrieve alarm definitions"
msgstr "알람 정의를 찾을 수 없습니다."
#, python-format
msgid "Could not retrieve alarm for %s"
msgstr "%s 에 대한 알람을 찾을 수 없습니다"
#, python-format
msgid "Could not retrieve alarm history for %s"
msgstr "%s 에 대한 알람 내역을 찾을 수 없습니다"
msgid "Could not retrieve alarms"
msgstr "알람을 찾지 못했습니다"
msgid "Could not retrieve notifications"
msgstr "알림을 가져올 수 없습니다"
msgid "Create Alarm"
msgstr "알람 생성"
msgid "Create Alarm Definition"
msgstr "알람 정의 생성"
msgid "Create Notification"
msgstr "알림 생성"
msgid "Create Notification Method"
msgstr "알림 메서드 생성"
msgid "Critical"
msgstr "Critical"
msgid "Delete Alarm"
msgid_plural "Delete Alarms"
msgstr[0] "알람 삭제"
msgid "Delete Alarm Definition"
msgid_plural "Delete Alarm Definitions"
msgstr[0] "알람 정의 삭제"
msgid "Delete Notification"
msgid_plural "Delete Notifications"
msgstr[0] "알림 삭제"
msgid "Deleted Alarm"
msgid_plural "Deleted Alarms"
msgstr[0] "알람 삭제됨"
msgid "Deleted Alarm Definition"
msgid_plural "Deleted Alarm Definitions"
msgstr[0] "삭제된 알람 정의"
msgid "Deleted Notification"
msgid_plural "Deleted Notifications"
msgstr[0] "알림 삭제됨"
msgid "Description"
msgstr "설명"
msgid "Details"
msgstr "상세 정보"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Dimension(s)"
msgstr "차원"
msgid "Edit Alarm"
msgstr "알람 수정"
msgid "Edit Alarm Definition"
msgstr "알람 정의 수정"
msgid "Edit Alarm Definitions"
msgstr "알람 정의 수정"
msgid "Edit Notification"
msgstr "알림 수정"
msgid "Expression"
msgstr "표현식"
msgid "Failed to present alarm history"
msgstr "알람 이력을 표시하는데 실패했습니다."
msgid "Failed to validate name"
msgstr "이름을 확인하지 못했습니다."
msgid "Filter Alarms"
msgstr "알람 필터"
msgid "Graph Metric"
msgstr "그래프 메트릭"
msgid "High"
msgstr "높음"
msgid "Info"
msgstr "정보"
msgid "Key"
msgstr "키"
msgid "Low"
msgstr "낮음"
msgid "Match by"
msgstr "일치"
msgid "Measurements for Alarms"
msgstr "알람에 대한 측정"
msgid "Medium"
msgstr "보통"
msgid "Metric Dimensions"
msgstr "메트릭 치수"
msgid "Metric Name"
msgstr "메트릭 이름"
msgid "Metric Names"
msgstr "메트릭 이름"
msgid "Monitoring"
msgstr "모니터링"
msgid "Name"
msgstr "이름"
msgid "New State"
msgstr "새 상태"
msgid "Next Page"
msgstr "다음 페이지"
msgid "No notifications available."
msgstr "사용가능한 알림이 없습니다."
msgid "None"
msgstr "None"
#, python-format
msgid "Notification %s has already been deleted."
msgstr "알림 %s 가 이미 삭제되었습니다."
msgid "Notification Method Details"
msgstr "알림 메소드 상세 정보"
msgid "Notification has been edited successfully."
msgstr "알림 수정을 성공적으로 완료하였습니다."
msgid "Notification method has been created successfully."
msgstr "통지 방법이 성공적으로 생성되었습니다."
msgid ""
"Notification methods. Notifications can be sent when an alarm state "
"transition occurs."
msgstr "알림 방식. 알림은 알람 상태 변경이 일어날 때 전송됩니다."
msgid "Notifications"
msgstr "알림"
msgid "Notifications Enabled"
msgstr "알림 활성화됨"
msgid "OK"
msgstr "OK"
msgid "OK Actions"
msgstr "OK 동작"
msgid "OR"
msgstr "OR"
msgid "Old State"
msgstr "이전 상태"
msgid "OpenStack Services"
msgstr "OpenStack 서비스"
msgid "Overview"
msgstr "개요"
msgid "Period"
msgstr "기간"
msgid "Period must be zero except for type webhook."
msgstr "주기는 타입 웹 후크를 제외하고 0이어야 합니다."
msgid "Previous Page"
msgstr "이전 페이지"
msgid "Reason"
msgstr "원인"
msgid "Remove"
msgstr "제거"
msgid "Save"
msgstr "저장"
msgid "Save Notification"
msgstr "알림 저장"
msgid "Select Notification"
msgstr "알림 선택"
msgid "Servers"
msgstr "서버"
msgid "Service :"
msgstr "서비스:"
msgid "Service Health"
msgstr "서비스 상태"
msgid "Severity"
msgstr "심각도"
msgid ""
"Severity of an alarm. Must be either LOW, MEDIUM, HIGH or CRITICAL. Default "
"is LOW."
msgstr ""
"알람에 대한 심각도입니다. LOW, MEDIUM, HIGH, CRITICAL 중 하나이어야 합니다. "
"디폴트는 LOW 입니다."
msgid "Show Alarm Definition"
msgstr "알람 정의 확인"
msgid "Show History"
msgstr "이력 확인"
msgid "State"
msgstr "상태"
msgid "Status"
msgstr "상태"
msgid "Submit"
msgstr "제출"
msgid "The email/url address to notify."
msgstr "통지할 이메일/url 주소"
msgid "The metric dimensions used to create unique alarms."
msgstr "유일한 알람을 생성하는데 사용하기 위한 계량적 부피"
msgid "The notification period."
msgstr "통지 주기"
msgid "The type of notification method (i.e. email)."
msgstr "알림 메소드 타입(i.e. email)."
msgid "Timestamp"
msgstr "타임스탬프"
msgid "Type"
msgstr "타입"
msgid "UTC"
msgstr "UTC"
#, python-format
msgid "Unable to create alarm definition %s"
msgstr "알람 정의 %s 를 생성할 수 없습니다."
#, python-format
msgid "Unable to list alarms: %s"
msgstr "알람 목록을 불러오지 못했습니다: %s"
msgid "Unable to retrieve alarm details."
msgstr "알람 세부 정보를 찾을 수 없습니다."
msgid "Unable to retrieve metrics"
msgstr "측정 항목을 찾을 수 없습니다."
msgid "Unable to retrieve notification details."
msgstr "알림 세부 정보를 가져올 수 없습니다."
msgid "Undetermined"
msgstr "정해지지 않음"
msgid "Undetermined Actions"
msgstr "정해지지 않은 동작"
msgid "Unknown name"
msgstr "알 수 없는 이름"
#, python-format
msgid "User %s does not have sufficient privileges to access Kibana"
msgstr "사용자 %s 에게 Kibana 에 접근하기 위한 충분한 권한이 없습니다."
msgid "Value"
msgstr "값"
msgid "avg"
msgstr "avg"
msgid "count"
msgstr "count"
msgid "last"
msgstr "last"
msgid "max"
msgstr "max"
msgid "min"
msgstr "min"
msgid "sum"
msgstr "sum"

View File

@@ -1,67 +0,0 @@
# Sungjin Kang <gang.sungjin@gmail.com>, 2018. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2020. #zanata
msgid ""
msgstr ""
"Project-Id-Version: monasca-ui VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2020-04-16 08:55+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2020-05-05 11:49+0000\n"
"Last-Translator: Ian Y. Choi <ianyrchoi@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko_KR\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Add"
msgstr "추가"
msgid "Add a dimension"
msgstr "차원 추가"
msgid "Comparator"
msgstr "Comparator"
msgid "Deterministic"
msgstr "Deterministic"
msgid "Down"
msgstr "Down"
msgid "Edit"
msgstr "편집"
msgid "Function"
msgstr "기능"
msgid "Matching Metrics"
msgstr "지표 일치"
msgid "Metric"
msgstr "메트릭"
msgid "No metric available"
msgstr "사용가능한 메트릭이 없습니다"
msgid "Operators"
msgstr "연산자"
msgid "Remove"
msgstr "제거"
msgid "Submit"
msgstr "제출"
msgid "Threshold"
msgstr "임계값"
msgid "Up"
msgstr "Up"
msgid "dimensions"
msgstr "dimensions"
msgid "name"
msgstr "name"

View File

@@ -1,21 +0,0 @@
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@@ -1,31 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.core import validators
from django.utils.translation import gettext_lazy as _
class NotificationType(object):
EMAIL = "EMAIL"
WEBHOOK = "WEBHOOK"
PAGERDUTY = "PAGERDUTY"
EMAIL_VALIDATOR = validators.EmailValidator(
message=_("Address must contain a valid email address."))
WEBHOOK_VALIDATOR = validators.URLValidator(
message=_("Address must contain a valid URL address."))
URL_PREFIX = 'horizon:monitoring:notifications:'
TEMPLATE_PREFIX = 'monitoring/notifications/'

View File

@@ -1,160 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.functional import cached_property # noqa
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from monitoring import api
from monitoring.notifications import constants
READONLY_TEXTINPUT = forms.TextInput(attrs={'readonly': 'readonly'})
class BaseNotificationMethodForm(forms.SelfHandlingForm):
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def _init_fields(self, readOnly=False, create=False):
required = True
textWidget = None
selectWidget = None
readOnlyTextInput = READONLY_TEXTINPUT
readOnlySelectInput = forms.Select(attrs={'disabled': 'disabled'})
if readOnly:
required = False
textWidget = readOnlyTextInput
selectWidget = readOnlySelectInput
choices = [(n['type'], n['type'].capitalize()) for n in self.notification_types]
choices = sorted(choices, key=lambda c: c[0])
period_choices = [(0, '0'), (60, '60')]
self.fields['name'] = forms.CharField(label=_("Name"),
required=required,
max_length="250",
widget=textWidget,
help_text=_("A descriptive name of "
"the notification method."))
self.fields['type'] = forms.ChoiceField(
label=_("Type"),
required=required,
widget=selectWidget,
choices=choices,
initial=constants.NotificationType.EMAIL,
help_text=_("The type of notification method (i.e. email)."))
self.fields['address'] = forms.CharField(label=_("Address"),
required=required,
max_length="512",
widget=textWidget,
help_text=_("The email/url address to notify."))
self.fields['period'] = forms.ChoiceField(label=_("Period"),
widget=selectWidget,
choices=period_choices,
initial=0,
required=required,
help_text=_("The notification period."))
def clean_period(self):
'''Check to make sure period is zero unless type is WEBHOOK.
For WEBHOOK period must be set to 0 or 60.
'''
data = self.cleaned_data
if data['type'] != constants.NotificationType.WEBHOOK and data['period'] != '0':
raise forms.ValidationError(
_('Period must be zero except for type webhook.'))
return data['period']
def clean_address(self):
'''Check to make sure address is the correct format depending on the type of notification.
'''
data = self.cleaned_data
if data['type'] == constants.NotificationType.EMAIL:
constants.EMAIL_VALIDATOR(data['address'])
elif data['type'] == constants.NotificationType.WEBHOOK:
constants.WEBHOOK_VALIDATOR(data['address'])
elif data['type'] == constants.NotificationType.PAGERDUTY:
pass
return data['address']
@cached_property
def notification_types(self):
return api.monitor.notification_type_list(self.request)
class CreateMethodForm(BaseNotificationMethodForm):
def __init__(self, request, *args, **kwargs):
super(CreateMethodForm, self).__init__(request, *args, **kwargs)
super(CreateMethodForm, self)._init_fields(readOnly=False)
def handle(self, request, data):
try:
api.monitor.notification_create(
request,
name=data['name'],
type=data['type'],
address=data['address'],
period=int(data['period']))
messages.success(request,
_('Notification method has been created '
'successfully.'))
except Exception:
exceptions.handle(request,
_('Unable to create the notification method.'))
return False
return True
class DetailMethodForm(BaseNotificationMethodForm):
def __init__(self, request, *args, **kwargs):
super(DetailMethodForm, self).__init__(request, *args, **kwargs)
super(DetailMethodForm, self)._init_fields(readOnly=True)
def handle(self, request, data):
return True
class EditMethodForm(BaseNotificationMethodForm):
def __init__(self, request, *args, **kwargs):
super(EditMethodForm, self).__init__(request, *args, **kwargs)
super(EditMethodForm, self)._init_fields(readOnly=False)
def handle(self, request, data):
try:
kwargs = {}
kwargs['notification_id'] = self.initial['id']
kwargs['name'] = data['name']
kwargs['type'] = data['type']
kwargs['address'] = data['address']
kwargs['period'] = int(data['period'])
api.monitor.notification_update(
request,
**kwargs
)
messages.success(request,
_('Notification has been edited successfully.'))
except Exception:
exceptions.handle(request,
_('Unable to edit the notification.'))
return False
return True

View File

@@ -1,26 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
import horizon
from monitoring import dashboard
class Notifications(horizon.Panel):
name = _("Notifications")
slug = 'notifications'
dashboard.Monitoring.register(Notifications)

View File

@@ -1,111 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext_lazy
from horizon import exceptions
from horizon import tables
from monitoring import api
from monitoring.notifications import constants
class DeleteNotification(tables.DeleteAction):
name = "delete_notification"
verbose_name = _("Delete Notification")
@staticmethod
def action_present(count):
return ngettext_lazy(
"Delete Notification",
"Delete Notifications",
count
)
@staticmethod
def action_past(count):
return ngettext_lazy(
"Deleted Notification",
"Deleted Notifications",
count
)
def allowed(self, request, datum=None):
return True
def delete(self, request, obj_id):
try:
api.monitor.notification_delete(request, obj_id)
except Exception:
exceptions.handle(
request, _('Unable to delete notification.'))
class CreateNotification(tables.LinkAction):
name = "create_notification"
verbose_name = _("Create Notification")
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("notification", "notification:create"),)
ajax = True
def get_link_url(self):
url = constants.URL_PREFIX + 'notification_create'
return reverse(url)
def allowed(self, request, datum=None):
return True
class EditNotification(tables.LinkAction):
name = "edit_notification"
verbose_name = _("Edit Notification")
classes = ("ajax-modal", "btn-create")
def get_link_url(self, datum):
return reverse(constants.URL_PREFIX + 'notification_edit',
args=(datum['id'], ))
def allowed(self, request, datum=None):
return True
class NotificationsFilterAction(tables.FilterAction):
def filter(self, table, notifications, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [notif for notif in notifications
if q in notif['name'].lower()]
class NotificationsTable(tables.DataTable):
target = tables.Column('name', verbose_name=_('Name'))
type = tables.Column('type', verbose_name=_('Type'))
address = tables.Column('address', verbose_name=_('Address'))
period = tables.Column('period', verbose_name=_('Period'))
def get_object_id(self, obj):
return obj['id']
def get_object_display(self, obj):
return obj['name']
class Meta(object):
name = "notifications"
verbose_name = _("Notifications")
row_actions = (EditNotification, DeleteNotification, )
table_actions = (CreateNotification, NotificationsFilterAction,
DeleteNotification)

View File

@@ -1,38 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}create_notif_method_form{% endblock %}
{% block form_action %}{{ action_url }}{% endblock %}
{% block modal-header %}{% trans "Create Notification Method" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right quota-dynamic">
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
The Name field is used to identify the notification method.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Type field indicates how the notification is sent when an alarm is triggered.
{% endblocktrans %}</p>
<p>{% blocktrans %}
The Address field indicates the email address, URL, or PagerDuty service
key to be notified.
{% endblocktrans %}</p>
<p>{% blocktrans %}
A non-zero value in the period field indicates how frequently a notification
should be resent (only valid for webhook urls).
{% endblocktrans %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<a href="{{ cancel_url }}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
<input class="btn btn-primary" type="submit" value="{% trans "Create Notification Method" %}" />
{% endblock %}

View File

@@ -1,17 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}detail_notif_method_form{% endblock %}
{% block form_action %}{% url 'horizon:project:monitoring_notifications:methods:detail' notification_method.id %}{% endblock %}
{% block modal-header %}{% trans "Notification Method Details" %}{% endblock %}
{% block modal-body %}
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% endblock %}
{% block modal-footer %}
<a href="{% url 'horizon:project:monitoring_notifications:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
{% endblock %}

View File

@@ -1,23 +0,0 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block form_id %}edit_method_form{% endblock %}
{% block form_action %}{{ action_url }}{% endblock %}
{% block modal-header %}{% trans "Edit Notification" %}{% endblock %}
{% block modal-body %}
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
The Name field is used to identify the notification method.
{% endblocktrans %}</p>
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% endblock %}
{% block modal-footer %}
<a href="{{ cancel_url }}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
<input class="btn btn-primary" type="submit" value="{% trans "Save Notification" %}" />
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Create Notification Method' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Create Notification Method" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/notifications/_create.html' %}
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Notification Method Details' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Notification Method Details" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/notifications/_detail.html' %}
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Create Notification' %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Create Notification" %}
{% endblock page_header %}
{% block main %}
{% include 'monitoring/notifications/_edit.html' %}
{% endblock %}

View File

@@ -1,28 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Monitoring' %}{% endblock %}
{% block page_header %}
{% include 'horizon/common/_page_header.html' with title=_("Notifications") %}
{% endblock page_header %}
{% block breadcrumb_nav %}
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate">{% trans "Monitoring" %}</li>
<li class="breadcrumb-item-truncate active">{% trans "Notifications" %}</li>
</ol>
{% endblock %}
{% block main %}
{{ table.render }}
<div class="pagination">
<span class="step-links">
{% if prev_page_offset != None %}
<a href="?page_offset={{ prev_page_offset }}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Previous Page' %}</a>
{% endif %}
{% if page_offset %}
<a href="?page_offset={{ page_offset }}" class="btn btn-default btn-sm" style="position:relative;top:-35px;">{% trans 'Next Page' %}</a>
{% endif %}
</span>
</div>
{% endblock %}

View File

@@ -1,65 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import reverse
from unittest.mock import patch
from monitoring.notifications import constants
from monitoring.test import helpers
INDEX_URL = reverse(
constants.URL_PREFIX + 'index')
CREATE_URL = reverse(
constants.URL_PREFIX + 'notification_create')
EDIT_URL = reverse(
constants.URL_PREFIX + 'notification_edit', args=('12345',))
class AlarmsTest(helpers.TestCase):
def test_index(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['notification_list'],
'notification_list.return_value': [],
}) as mock:
res = self.client.get(INDEX_URL)
self.assertEqual(mock.notification_list.call_count, 2)
self.assertTemplateUsed(
res, 'monitoring/notifications/index.html')
def test_notifications_create(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['notification_type_list'],
'notification_type_list.return_value': [],
}) as mock:
res = self.client.get(CREATE_URL)
self.assertEqual(mock. notification_type_list.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/notifications/_create.html')
def test_notifications_edit(self):
with patch('monitoring.api.monitor', **{
'spec_set': ['notification_get', 'notification_type_list'],
'notification_get.return_value': {
'alarm_actions': []
},
'notification_type_list.return_value': [],
}) as mock:
res = self.client.get(EDIT_URL)
self.assertEqual(mock.notification_get.call_count, 1)
self.assertEqual(mock.notification_type_list.call_count, 1)
self.assertTemplateUsed(
res, 'monitoring/notifications/_edit.html')

View File

@@ -1,26 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import re_path
from monitoring.notifications import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^notification_create$',
views.NotificationCreateView.as_view(),
name='notification_create'),
re_path(r'^notification_edit/(?P<id>[^/]+)$',
views.NotificationEditView.as_view(),
name='notification_edit')
]

View File

@@ -1,169 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.contrib import messages
from django.core.paginator import EmptyPage
from django.core.paginator import Paginator
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon.utils import functions as utils
from monitoring import api
from monitoring.notifications import constants
from monitoring.notifications import forms as notification_forms
from monitoring.notifications import tables as notification_tables
from openstack_dashboard import policy
PREV_PAGE_LIMIT = 100
class IndexView(tables.DataTableView):
table_class = notification_tables.NotificationsTable
template_name = constants.TEMPLATE_PREFIX + 'index.html'
def dispatch(self, *args, **kwargs):
return super(IndexView, self).dispatch(*args, **kwargs)
def get_data(self):
page_offset = self.request.GET.get('page_offset')
results = []
if page_offset is None:
page_offset = 0
limit = utils.get_page_size(self.request)
try:
results = api.monitor.notification_list(self.request, page_offset, limit)
paginator = Paginator(results, limit)
results = paginator.page(1)
except EmptyPage:
results = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve notifications"))
return results
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(IndexView, self).get_context_data(**kwargs)
contacts = []
results = []
num_results = 0
prev_page_stack = []
page_offset = self.request.GET.get('page_offset')
if 'prev_page_stack' in self.request.session:
prev_page_stack = self.request.session['prev_page_stack']
if page_offset is None:
page_offset = 0
prev_page_stack = []
else:
page_offset = int(page_offset)
limit = utils.get_page_size(self.request)
try:
# To judge whether there is next page, get LIMIT + 1
results = api.monitor.notification_list(self.request, page_offset,
limit + 1)
num_results = len(results)
paginator = Paginator(results, limit)
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
except Exception:
messages.error(self.request, _("Could not retrieve notifications"))
return context
context["contacts"] = contacts
if num_results < limit + 1:
context["page_offset"] = None
else:
context["page_offset"] = page_offset + limit
if page_offset in prev_page_stack:
index = prev_page_stack.index(page_offset)
prev_page_stack = prev_page_stack[0:index]
prev_page_offset = prev_page_stack[-1] if prev_page_stack else None
if prev_page_offset is not None:
context["prev_page_offset"] = prev_page_offset
if len(prev_page_stack) > PREV_PAGE_LIMIT:
del prev_page_stack[0]
prev_page_stack.append(page_offset)
self.request.session['prev_page_stack'] = prev_page_stack
return context
class NotificationCreateView(forms.ModalFormView):
form_class = notification_forms.CreateMethodForm
template_name = constants.TEMPLATE_PREFIX + 'create.html'
success_url = reverse_lazy(constants.URL_PREFIX + 'index')
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(NotificationCreateView, self). \
get_context_data(**kwargs)
context["cancel_url"] = self.get_success_url()
action = constants.URL_PREFIX + 'notification_create'
context["action_url"] = reverse(action)
return context
class NotificationEditView(forms.ModalFormView):
form_class = notification_forms.EditMethodForm
template_name = constants.TEMPLATE_PREFIX + 'edit.html'
def dispatch(self, *args, **kwargs):
return super(NotificationEditView, self).dispatch(*args, **kwargs)
def get_object(self):
id = self.kwargs['id']
try:
if hasattr(self, "_object"):
return self._object
self._object = None
self._object = api.monitor.notification_get(self.request, id)
return self._object
except Exception:
redirect = reverse(constants.URL_PREFIX + 'index')
exceptions.handle(self.request,
_('Unable to retrieve notification details.'),
redirect=redirect)
return None
def get_initial(self):
self.notification = self.get_object()
return self.notification
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(NotificationEditView, self).get_context_data(**kwargs)
id = self.kwargs['id']
context["cancel_url"] = self.get_success_url()
context["action_url"] = reverse(constants.URL_PREFIX +
'notification_edit', args=(id,))
return context
def get_success_url(self):
return reverse_lazy(constants.URL_PREFIX + 'index',)

View File

@@ -1,25 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
URL_PREFIX = 'horizon:monitoring:overview:'
TEMPLATE_PREFIX = 'monitoring/overview/'
prefix = settings.STATIC_URL or ''
CRITICAL_ICON = prefix + 'monitoring/img/critical-icon.png'
WARNING_ICON = prefix + 'monitoring/img/warning-icon.png'
OK_ICON = prefix + 'monitoring/img/ok-icon.png'
UNKNOWN_ICON = prefix + 'monitoring/img/unknown-icon.png'
NOTFOUND_ICON = prefix + 'monitoring/img/notfound-icon.png'

View File

@@ -1,27 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import gettext_lazy as _
import horizon
from monitoring import dashboard
class Overview(horizon.Panel):
name = _("Overview")
slug = 'overview'
dashboard.Monitoring.register(Overview)

View File

@@ -1,64 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans 'Monitoring' %}{% endblock %}
{% block breadcrumb_nav %}
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate">{% trans "Monitoring" %}</li>
<li class="breadcrumb-item-truncate active">{% trans "Overview" %}</li>
</ol>
{% endblock %}
{% block page_header %}
{% include 'horizon/common/_page_header.html' with title=_("Monitoring") %}
{% endblock page_header %}
{% block main %}
<div style="padding: 3px;">
{% if grafana_url %}
{% if show_grafana_home %}
<a target="Grafana Home" href="{{grafana_url}}" class="btn btn-primary">
<span class="fa fa-bar-chart"></span>
Grafana Home
</a>
{% endif %}
{% for dashboard in dashboards %}
{% if dashboard.raw %}
<a target={{ dashboard.title }} href="{{ dashboard.path }}" class="btn btn-default">
{% else %}
<a target={{ dashboard.title }} href="{{grafana_url}}/dashboard/db/{{ dashboard.path }}" class="btn btn-default">
{% endif %}
<span class="fa fa-bar-chart"></span>
{% trans dashboard.title %}
</a>
{% endfor %}
{% else %}
{% for dashboard in dashboards %}
{% if dashboard.raw %}
<a target={{ dashboard.title }} href="{{ dashboard.fileName }}" class="btn btn-default">
{% else %}
<a target={{ dashboard.title }} href="/grafana/index.html#/dashboard/file/{{ dashboard.fileName }}?api={{api}}" class="btn btn-default">
{% endif %}
<span class="fa fa-bar-chart"></span>
{% trans dashboard.title %}
</a>
{% endfor %}
{% endif %}
{% if can_access_kibana %}
{% if enable_log_management_button or enable_event_management_button %}
<a target="dashboard" href="{% url 'horizon:monitoring:overview:kibana_proxy' url='/app/kibana' %}" class="btn btn-default">
<span class="fa fa-bar-chart"></span>
{% if enable_log_management_button %}
{% if enable_event_management_button %}
Log and Event Management
{% else %}
Log Management
{% endif %}
{% elif enable_event_management_button %}
Event Management
{% endif %}
</a>
{% endif %}
{% endif %}
</div>
{% include 'monitoring/overview/monitor.html' %}
{% endblock %}

View File

@@ -1,71 +0,0 @@
{% load static %}
<style>
.chicklet-container {
border: 1px solid #F1F1F1;
color:black;
font-weight: bold;
background-color: white;
padding: 14px 35px 14px 14px;
margin-bottom: 18px;
border-radius: 4px;
}
.base-chicklet {
border: 3px solid;
color: #7C7C7C;
background-color: white;
display:inline-block;
padding:0px;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
border-radius: 4px;
}
.chicklet-success {
border-color: #36A32C;
}
.chicklet-notfound {
border-color: #e5e5e5;
}
.chicklet-unknown {
border-color: #626262;
}
.chicklet-error {
border-color: #9E1213;
}
.chicklet-warning {
border-color: #D6C037;
}
.status-icon-holder {
background-color: #EDEEF0;
float: left;
padding-top:10px;
padding-left: 6px;
padding-right: 2px;
padding-bottom: 10px;
border-right:2px solid;
border-color: #D8D7DD;
}
.service-text {
float: right;
margin: 10px;
min-width: 60px;
}
</style>
<div style="border: 1px solid #dddddd;">
<div ng-controller = "monitoringController"
ng-init="fetchStatus('{% get_static_prefix %}')">
<div class="chicklet-container" ng-repeat="group in _serviceModel">
<div>
{$ group.name $}
</div>
<div ng-repeat="service in group.services" style="display:inline-block; margin-bottom:4px; margin-right:4px;">
<a href="{% url 'horizon:monitoring:alarms:index' %}alarm/{$ service.name $}">
<div class="btn {$ service.class $}">
<div class="fa {$ service.icon $}"> </div>
{$ service.display $}
</div>
</a>
</div>
</div>
</div>
</div>

View File

@@ -1,70 +0,0 @@
# coding=utf-8
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.test import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from monitoring.overview import constants
from monitoring.overview import views
from monitoring.test import helpers
INDEX_URL = reverse(
constants.URL_PREFIX + 'index')
class OverviewTest(helpers.TestCase):
@override_settings(DASHBOARDS=[],
KIBANA_POLICY_SCOPE='monitoring',
KIBANA_POLICY_RULE='monitoring:kibana_access',
ENABLE_LOG_MANAGEMENT_BUTTON=False,
ENABLE_EVENT_MANAGEMENT_BUTTON=False,
SHOW_GRAFANA_HOME=True)
def test_index_get(self):
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(
res, 'monitoring/overview/index.html')
self.assertTemplateUsed(res, 'monitoring/overview/monitor.html')
class KibanaProxyViewTest(helpers.TestCase):
def setUp(self):
super(KibanaProxyViewTest, self).setUp()
self.view = views.KibanaProxyView()
self.request_factory = RequestFactory()
def test_get_relative_url_with_unicode(self):
"""Tests if it properly converts multibyte characters."""
from urllib import parse as urlparse
self.view.request = self.request_factory.get(
'/', data={'a': 1, 'b': 2}
)
expected_path = ('/elasticsearch/.kibana/search'
'/New-Saved-Search%E3%81%82')
expected_qs = {'a': ['1'], 'b': ['2']}
url = self.view.get_relative_url(
u'/elasticsearch/.kibana/search/New-Saved-Searchあ'
)
# order of query params may change
parsed_url = urlparse.urlparse(url)
actual_path = parsed_url.path
actual_qs = urlparse.parse_qs(parsed_url.query)
self.assertEqual(actual_path, expected_path)
self.assertEqual(actual_qs, expected_qs)

View File

@@ -1,29 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.urls import re_path
from monitoring.config import local_settings as settings
from monitoring.overview import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
re_path(r'^status', views.StatusView.as_view(), name='status'),
re_path(r'^proxy\/(?P<restpath>.*)$', views.MonascaProxyView.as_view()),
re_path(r'^proxy', views.MonascaProxyView.as_view(), name='proxy'),
re_path(r'^logs_proxy(?P<url>.*)$',
views.KibanaProxyView.as_view(base_url=settings.KIBANA_HOST),
name='kibana_proxy'
)
]

View File

@@ -1,440 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import copy
import json
import logging
from django.conf import settings
from django.contrib import messages
from django import http
from django.http import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views import generic
from django.views.generic import TemplateView
from horizon import exceptions
from openstack_auth import utils as auth_utils
from openstack_dashboard import policy
import urllib
from monitoring.alarms import tables as alarm_tables
from monitoring import api
from monitoring.overview import constants
LOG = logging.getLogger(__name__)
STATUS_FA_ICON_MAP = {'btn-success': "fa-check",
'btn-danger': "fa-exclamation-triangle",
'btn-warning': "fa-exclamation",
'btn-default': "fa-question-circle"}
def get_icon(status):
return STATUS_FA_ICON_MAP.get(status, "fa-question-circle")
priorities = [
{'status': 'btn-success', 'severity': 'OK'},
{'status': 'btn-default', 'severity': 'UNDETERMINED'},
{'status': 'btn-warning', 'severity': 'LOW'},
{'status': 'btn-warning', 'severity': 'MEDIUM'},
{'status': 'btn-warning', 'severity': 'HIGH'},
{'status': 'btn-danger', 'severity': 'CRITICAL'},
]
index_by_severity = {d['severity']: i for i, d in enumerate(priorities)}
def get_dashboard_links(request):
#
# GRAFANA_LINKS is a list of dictionaries, but can either
# be a nested list of dictionaries indexed by project name
# (or '*'), or simply the list of links to display. This
# code is a bit more complicated as a result but will allow
# for backward compatibility and ensure existing installations
# that don't take advantage of project specific dashboard
# links are unaffected. The 'non_project_keys' are the
# expected dictionary keys for the list of dashboard links,
# so if we encounter one of those, we know we're supporting
# legacy/non-project specific behavior.
#
# See examples of both in local_settings.py
#
non_project_keys = {'fileName', 'title'}
try:
for project_link in settings.DASHBOARDS:
key = list(project_link)[0]
value = list(project_link.values())[0]
if key in non_project_keys:
#
# we're not indexed by project, just return
# the whole list.
#
return settings.DASHBOARDS
elif key == request.user.project_name:
#
# we match this project, return the project
# specific links.
#
return value
elif key == '*':
#
# this is a global setting, squirrel it away
# in case we exhaust the list without a project
# match
#
return value
return settings.DEFAULT_LINKS
except Exception:
LOG.warning("Failed to parse dashboard links by project, returning defaults.")
pass
#
# Extra safety here -- should have got a match somewhere above,
# but fall back to defaults.
#
return settings.DASHBOARDS
def get_monitoring_services(request):
#
# GRAFANA_LINKS is a list of dictionaries, but can either
# be a nested list of dictionaries indexed by project name
# (or '*'), or simply the list of links to display. This
# code is a bit more complicated as a result but will allow
# for backward compatibility and ensure existing installations
# that don't take advantage of project specific dashboard
# links are unaffected. The 'non_project_keys' are the
# expected dictionary keys for the list of dashboard links,
# so if we encounter one of those, we know we're supporting
# legacy/non-project specific behavior.
#
# See examples of both in local_settings.py
#
non_project_keys = {'name', 'groupBy'}
try:
for group in settings.MONITORING_SERVICES:
key = list(group.keys())[0]
value = list(group.values())[0]
if key in non_project_keys:
#
# we're not indexed by project, just return
# the whole list.
#
return settings.MONITORING_SERVICES
elif key == request.user.project_name:
#
# we match this project, return the project
# specific links.
#
return value
elif key == '*':
#
# this is a global setting, squirrel it away
# in case we exhaust the list without a project
# match
#
return value
return settings.MONITORING_SERVICES
except Exception:
LOG.warning("Failed to parse monitoring services by project, returning defaults.")
pass
#
# Extra safety here -- should have got a match somewhere above,
# but fall back to defaults.
#
return settings.MONITORING_SERVICES
def show_by_dimension(data, dim_name):
if 'metrics' in data:
dimensions = []
for metric in data['metrics']:
if 'dimensions' in metric:
if dim_name in metric['dimensions']:
dimension = metric['dimensions'][dim_name]
dimensions.append(dimension)
return dimensions
return []
def get_status(alarms):
if not alarms:
return 'chicklet-notfound'
status_index = 0
for a in alarms:
severity = alarm_tables.show_severity(a)
severity_index = index_by_severity.get(severity, None)
status_index = max(status_index, severity_index)
return priorities[status_index]['status']
def generate_status(request):
try:
alarms = api.monitor.alarm_list(request)
except Exception as e:
messages.error(request,
_('Unable to list alarms: %s') % str(e))
alarms = []
alarms_by_service = {}
for a in alarms:
service = alarm_tables.get_service(a)
service_alarms = alarms_by_service.setdefault(service, [])
service_alarms.append(a)
monitoring_services = copy.deepcopy(get_monitoring_services(request))
for row in monitoring_services:
row['name'] = str(row['name'])
if 'groupBy' in row:
alarms_by_group = {}
for a in alarms:
groups = show_by_dimension(a, row['groupBy'])
if groups:
for group in groups:
group_alarms = alarms_by_group.setdefault(group, [])
group_alarms.append(a)
services = []
for group, group_alarms in alarms_by_group.items():
name = '%s=%s' % (row['groupBy'], group)
# Encode as base64url to be able to include '/'
# encoding and decoding is required because of python3 compatibility
# urlsafe_b64encode requires byte-type text
name = 'b64:' + base64.urlsafe_b64encode(name.encode('utf-8')).decode('utf-8')
service = {
'display': group,
'name': name,
'class': get_status(group_alarms)
}
service['icon'] = get_icon(service['class'])
services.append(service)
row['services'] = services
else:
for service in row['services']:
service_alarms = alarms_by_service.get(service['name'], [])
service['class'] = get_status(service_alarms)
service['icon'] = get_icon(service['class'])
service['display'] = str(service['display'])
return monitoring_services
class IndexView(TemplateView):
template_name = constants.TEMPLATE_PREFIX + 'index.html'
def get_context_data(self, **kwargs):
if not policy.check((('monitoring', 'monitoring:monitoring'), ), self.request):
raise exceptions.NotAuthorized()
context = super(IndexView, self).get_context_data(**kwargs)
try:
region = self.request.user.services_region
context["grafana_url"] = getattr(settings, 'GRAFANA_URL').get(region, '')
except AttributeError:
# Catches case where Grafana 2 is not enabled.
proxy_url_path = str(reverse_lazy(constants.URL_PREFIX + 'proxy'))
api_root = self.request.build_absolute_uri(proxy_url_path)
context["api"] = api_root
context["dashboards"] = get_dashboard_links(self.request)
# Ensure all links have a 'raw' attribute
for link in context["dashboards"]:
link['raw'] = link.get('raw', False)
context['can_access_kibana'] = policy.check(
((getattr(settings, 'KIBANA_POLICY_SCOPE'), getattr(settings, 'KIBANA_POLICY_RULE')), ),
self.request
)
context['enable_log_management_button'] = settings.ENABLE_LOG_MANAGEMENT_BUTTON
context['enable_event_management_button'] = settings.ENABLE_EVENT_MANAGEMENT_BUTTON
context['show_grafana_home'] = settings.SHOW_GRAFANA_HOME
return context
class MonascaProxyView(TemplateView):
template_name = ""
def _convert_dimensions(self, req_kwargs):
"""Converts the dimension string service:monitoring into a dict
This method converts the dimension string
service:monitoring (requested by a query string arg)
into a python dict that looks like
{"service": "monitoring"} (used by monasca api calls)
"""
dim_dict = {}
if 'dimensions' in req_kwargs:
dimensions_str = req_kwargs['dimensions'][0]
dimensions_str_array = dimensions_str.split(',')
for dimension in dimensions_str_array:
# limit splitting since value may contain a ':' such as in
# the `url` dimension of the service_status check.
dimension_name_value = dimension.split(':', 1)
if len(dimension_name_value) == 2:
name = dimension_name_value[0]
value = dimension_name_value[1]
if isinstance(value, bytes):
value = value.decode("utf-8")
dim_dict[name] = urllib.parse.unquote(value)
else:
raise Exception('Dimensions are malformed')
#
# If the request specifies 'INJECT_REGION' as the region, we'll
# replace with the horizon scoped region. We can't do this by
# default, since some implementations don't publish region as a
# dimension for all metrics (mini-mon for one).
#
if 'region' in dim_dict and dim_dict['region'] == 'INJECT_REGION':
dim_dict['region'] = self.request.user.services_region
req_kwargs['dimensions'] = dim_dict
return req_kwargs
def get(self, request, *args, **kwargs):
# monasca_endpoint = api.monitor.monasca_endpoint(self.request)
restpath = self.kwargs['restpath']
results = None
parts = restpath.split('/')
if "metrics" == parts[0]:
req_kwargs = dict(self.request.GET)
self._convert_dimensions(req_kwargs)
if len(parts) == 1:
results = {'elements': api.monitor.
metrics_list(request,
**req_kwargs)}
elif "statistics" == parts[1]:
results = {'elements': api.monitor.
metrics_stat_list(request,
**req_kwargs)}
elif "measurements" == parts[1]:
results = {'elements': api.monitor.
metrics_measurement_list(request,
**req_kwargs)}
elif "dimensions" == parts[1]:
results = {'elements': api.monitor.
metrics_dimension_value_list(request,
**req_kwargs)}
if not results:
LOG.warning("There was a request made for the path %s that"
" is not supported." % restpath)
results = {}
return HttpResponse(json.dumps(results),
content_type='application/json')
class StatusView(TemplateView):
template_name = ""
def get(self, request, *args, **kwargs):
ret = {
'series': generate_status(self.request),
'settings': {}
}
return HttpResponse(json.dumps(ret),
content_type='application/json')
class _HttpMethodRequest(urllib.request.Request):
def __init__(self, method, url, **kwargs):
urllib.request.Request.__init__(self, url, **kwargs)
self.method = method
def get_method(self):
return self.method
def proxy_stream_generator(response):
while True:
chunk = response.read(1000 * 1024)
if not chunk:
break
yield chunk
class KibanaProxyView(generic.View):
base_url = None
http_method_names = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD']
def read(self, method, url, data, headers):
proxy_request_url = self.get_absolute_url(url)
proxy_request = _HttpMethodRequest(
method, proxy_request_url, data=data, headers=headers
)
try:
response = urllib.request.urlopen(proxy_request)
except urllib.error.HTTPError as e:
return http.HttpResponse(
e.read(),
status=e.code,
content_type=e.hdrs['content-type']
)
except urllib.error.URLError as e:
return http.HttpResponse(e.reason, 404)
else:
status = response.getcode()
proxy_response = http.StreamingHttpResponse(
proxy_stream_generator(response),
status=status,
content_type=response.headers['content-type']
)
if 'set-cookie' in response.headers:
proxy_response['set-cookie'] = response.headers['set-cookie']
return proxy_response
@csrf_exempt
def dispatch(self, request, url):
if not url:
url = '/'
if request.method not in self.http_method_names:
return http.HttpResponseNotAllowed(request.method)
if not self._can_access_kibana():
error_msg = (_('User %s does not have sufficient '
'privileges to access Kibana')
% auth_utils.get_user(request))
LOG.error(error_msg)
return http.HttpResponseForbidden(content=error_msg)
# passing kbn version explicitly for kibana >= 4.3.x
headers = {
"X-Auth-Token": request.user.token.id,
"kbn-version": request.META.get("HTTP_KBN_VERSION", ""),
"Cookie": request.META.get("HTTP_COOKIE", ""),
"Content-Type": "application/json",
}
return self.read(request.method, url, request.body, headers)
def get_relative_url(self, url):
url = urllib.parse.quote(url.encode('utf-8'))
params_str = self.request.GET.urlencode()
if params_str:
return '{0}?{1}'.format(url, params_str)
return url
def get_absolute_url(self, url):
return self.base_url + self.get_relative_url(url).lstrip('/')
def _can_access_kibana(self):
return policy.check(
((getattr(settings, 'KIBANA_POLICY_SCOPE'), getattr(settings, 'KIBANA_POLICY_RULE')), ),
self.request
)

View File

@@ -1,60 +0,0 @@
.alarm-expression {
.row {
margin-left: -5px;
margin-right: -5px;
}
pre.expression-preview {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
}
pre.expression-valid {
border-color: #00B700;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
pre.expression-invalid {
border-color: #A94442;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.alarm-sub-expression {
margin-top: 5px;
margin-bottom: 5px;
padding: 10px;
.sub-expression-preview {
overflow: hidden;
text-overflow: ellipsis;
}
.expression-details {
margin-bottom: 5px;
div {
padding-left: 2px;
padding-right: 2px;
}
}
form {
.has-error {
border-color: #a94442;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
}
}
}

View File

@@ -1,129 +0,0 @@
tags-input *, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
tags-input .host {
position: relative;
/*margin-top: 5px;*/
margin-bottom: 8px;
}
tags-input .host:active {
outline: none;
}
tags-input .tags {
-moz-appearance: textfield;
-webkit-appearance: textfield;
padding: 1px;
overflow: hidden;
word-wrap: break-word;
cursor: text;
background-color: white;
border: 1px solid darkgray;
box-shadow: 1px 1px 1px 0 lightgray inset;
}
tags-input .tags.focused {
outline: none;
-webkit-box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
-moz-box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
box-shadow: 0 0 3px 1px rgba(5, 139, 242, 0.6);
}
tags-input .tags .tag-list {
margin: 0;
padding: 0;
list-style-type: none;
}
tags-input .tags .tag-item {
margin: 2px;
padding: 0 5px;
display: inline-block;
float: left;
/*font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;*/
height: 26px;
line-height: 25px;
border: 1px solid #acacac;
border-radius: 3px;
background: -webkit-linear-gradient(top, #f0f9ff 0%, #cbebff 47%, #a1dbff 100%);
background: linear-gradient(to bottom, #f0f9ff 0%, #cbebff 47%, #a1dbff 100%);
}
tags-input .tags .tag-item.selected {
background: -webkit-linear-gradient(top, #febbbb 0%, #fe9090 45%, #ff5c5c 100%);
background: linear-gradient(to bottom, #febbbb 0%, #fe9090 45%, #ff5c5c 100%);
}
tags-input .tags .tag-item .remove-button {
margin: 0 0 0 5px;
padding: 0;
border: none;
background: none;
cursor: pointer;
vertical-align: middle;
font: bold 16px Arial, sans-serif;
color: #585858;
}
tags-input .tags .tag-item .remove-button:active {
color: red;
}
tags-input .tags .input {
border: 0;
outline: none;
margin: 2px;
padding: 0;
padding-left: 5px;
float: left;
height: 26px;
/*font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;*/
}
tags-input .tags .input.invalid-tag {
color: red;
}
tags-input .tags .input::-ms-clear {
display: none;
}
tags-input.ng-invalid .tags {
-webkit-box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
-moz-box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
box-shadow: 0 0 3px 1px rgba(255, 0, 0, 0.6);
}
tags-input .autocomplete {
margin-top: 5px;
position: absolute;
padding: 5px 0;
z-index: 999;
width: 100%;
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
}
tags-input .autocomplete .suggestion-list {
margin: 0;
padding: 0;
list-style-type: none;
}
tags-input .autocomplete .suggestion-item {
padding: 5px 10px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font: 16px "Helvetica Neue", Helvetica, Arial, sans-serif;
color: black;
background-color: white;
}
tags-input .autocomplete .suggestion-item.selected {
color: white;
background-color: #0097cf;
}
tags-input .autocomplete .suggestion-item.selected em {
color: white;
background-color: #0097cf;
}
tags-input .autocomplete .suggestion-item em {
font: normal bold 16px "Helvetica Neue", Helvetica, Arial, sans-serif;
color: black;
background-color: white;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,19 +0,0 @@
'use strict';
// Declare app level module which depends on filters, and services
angular
.module('monitoringApp', [
'monitoring.controllers',
'monitoring.directives',
'monitoring.filters',
'monitoring.services',
'ngTagsInput'
])
.config(config);
config.$inject = ['$provide', '$windowProvider'];
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'monitoring/widgets/';
$provide.constant('monitoringApp.staticPath', path);
}

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