Browse Source
Merge pull request #297 from hbrunn/8.0-dead_mans_switch
Merge pull request #297 from hbrunn/8.0-dead_mans_switch
[ADD] dead_mans_switch_client, dead_mans_switch_serverpull/301/head
Alexandre Fayolle
9 years ago
27 changed files with 772 additions and 0 deletions
-
73dead_mans_switch_client/README.rst
-
4dead_mans_switch_client/__init__.py
-
18dead_mans_switch_client/__openerp__.py
-
13dead_mans_switch_client/data/ir_actions.xml
-
13dead_mans_switch_client/data/ir_cron.xml
-
4dead_mans_switch_client/models/__init__.py
-
68dead_mans_switch_client/models/dead_mans_switch_client.py
-
BINdead_mans_switch_client/static/description/icon.png
-
4dead_mans_switch_client/tests/__init__.py
-
17dead_mans_switch_client/tests/test_dead_mans_switch_client.py
-
93dead_mans_switch_server/README.rst
-
5dead_mans_switch_server/__init__.py
-
23dead_mans_switch_server/__openerp__.py
-
4dead_mans_switch_server/controllers/__init__.py
-
28dead_mans_switch_server/controllers/main.py
-
13dead_mans_switch_server/data/ir_cron.xml
-
5dead_mans_switch_server/models/__init__.py
-
141dead_mans_switch_server/models/dead_mans_switch_instance.py
-
17dead_mans_switch_server/models/dead_mans_switch_log.py
-
4dead_mans_switch_server/security/ir.model.access.csv
-
14dead_mans_switch_server/security/res_groups.xml
-
BINdead_mans_switch_server/static/description/icon.png
-
4dead_mans_switch_server/tests/__init__.py
-
36dead_mans_switch_server/tests/test_dead_mans_switch_server.py
-
117dead_mans_switch_server/views/dead_mans_switch_instance.xml
-
38dead_mans_switch_server/views/dead_mans_switch_log.xml
-
16dead_mans_switch_server/views/menu.xml
@ -0,0 +1,73 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:alt: License: AGPL-3 |
|||
|
|||
========================== |
|||
Dead man's switch (client) |
|||
========================== |
|||
|
|||
This module is the client part of `dead_mans_switch_server`. It is responsible |
|||
of sending the server status updates, which in turn takes action if those |
|||
updates don't come in time. |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
After installing this module, you need to fill in the system parameter |
|||
`dead_mans_switch_client.url`. This must be the full URL to the server's |
|||
controller, usually of the form https://your.server/dead_mans_switch/alive |
|||
|
|||
This module attempts to send CPU and RAM statistics to the server. While this |
|||
is not mandatory, it's helpful for assessing a server's health. If you want |
|||
this, you need to install `psutil`. |
|||
|
|||
You can also have the currently online users logged, but this only works if |
|||
the `im_chat` module is installed. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
This module doesn't have any visible effect on the client. |
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
|||
|
|||
For further information, please visit: |
|||
|
|||
* https://www.odoo.com/forum/help-1 |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* certificate pinning would be nice |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback |
|||
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20dead_mans_switch_client%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Holger Brunn <hbrunn@therp.nl> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import models |
@ -0,0 +1,18 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Dead man's switch (client)", |
|||
"version": "8.0.1.0.0", |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"category": "Monitoring", |
|||
"summary": "Be notified when customers' odoo instances go down", |
|||
"depends": [ |
|||
'base', |
|||
], |
|||
"data": [ |
|||
"data/ir_actions.xml", |
|||
"data/ir_cron.xml", |
|||
], |
|||
} |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<act_window id="action_setup" res_model="ir.config_parameter" |
|||
name="Configure the dead man's switch server" view_mode="form" |
|||
context="{'default_key': 'dead_mans_switch_client.url'}"/> |
|||
<record id="todo_setup" model="ir.actions.todo"> |
|||
<field name="name">Configure the dead man's switch server</field> |
|||
<field name="type">automatic</field> |
|||
<field name="action_id" ref="action_setup" /> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data noupdate="1"> |
|||
<record id="cron_client" model="ir.cron"> |
|||
<field name="name">Dead man's switch client</field> |
|||
<field name="interval_number">5</field> |
|||
<field name="interval_type">minutes</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="model">dead.mans.switch.client</field> |
|||
<field name="function">alive</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import dead_mans_switch_client |
@ -0,0 +1,68 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
import json |
|||
import logging |
|||
import os |
|||
try: |
|||
import psutil |
|||
except ImportError: |
|||
psutil = None |
|||
import urllib2 |
|||
from openerp import api, models |
|||
|
|||
|
|||
class DeadMansSwitchClient(models.AbstractModel): |
|||
_name = 'dead.mans.switch.client' |
|||
_register = True |
|||
|
|||
@api.model |
|||
def _get_data(self): |
|||
ram = 0 |
|||
cpu = 0 |
|||
if psutil: |
|||
process = psutil.Process(os.getpid()) |
|||
# psutil changed its api through versions |
|||
if process.parent: |
|||
if hasattr(process.parent, '__call__'): |
|||
process = process.parent() |
|||
else: |
|||
process = process.parent |
|||
if hasattr(process, 'memory_percent'): |
|||
ram = process.memory_percent() |
|||
if hasattr(process, 'cpu_percent'): |
|||
cpu = process.cpu_percent() |
|||
user_count = 0 |
|||
if 'im_chat.presence' in self.env.registry: |
|||
user_count = len(self.env['im_chat.presence'].search([ |
|||
('status', '!=', 'offline'), |
|||
])) |
|||
return { |
|||
'database_uuid': self.env['ir.config_parameter'].get_param( |
|||
'database.uuid'), |
|||
'cpu': cpu, |
|||
'ram': ram, |
|||
'user_count': user_count, |
|||
} |
|||
|
|||
@api.model |
|||
def alive(self): |
|||
url = self.env['ir.config_parameter'].get_param( |
|||
'dead_mans_switch_client.url') |
|||
logger = logging.getLogger(__name__) |
|||
if not url: |
|||
logger.error('No server configured!') |
|||
return |
|||
data = self._get_data() |
|||
logger.debug('sending %s', data) |
|||
urllib2.urlopen( |
|||
urllib2.Request( |
|||
url, |
|||
json.dumps({ |
|||
'jsonrpc': '2.0', |
|||
'method': 'call', |
|||
'params': data, |
|||
}), |
|||
{ |
|||
'Content-Type': 'application/json', |
|||
})) |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import test_dead_mans_switch_client |
@ -0,0 +1,17 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp.tests.common import TransactionCase |
|||
|
|||
|
|||
class TestDeadMansSwitchClient(TransactionCase): |
|||
def test_dead_mans_switch_client(self): |
|||
# test unconfigured case |
|||
self.env['ir.config_parameter'].search([ |
|||
('key', '=', 'dead_mans_switch_client.url')]).unlink() |
|||
self.env['dead.mans.switch.client'].alive() |
|||
# test configured case |
|||
self.env['ir.config_parameter'].set_param( |
|||
'dead_mans_switch_client.url', 'fake_url') |
|||
with self.assertRaises(ValueError): |
|||
self.env['dead.mans.switch.client'].alive() |
@ -0,0 +1,93 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:alt: License: AGPL-3 |
|||
|
|||
========================== |
|||
Dead man's switch (server) |
|||
========================== |
|||
|
|||
This module receives status messages by `dead_mans_switch_client` and notifies |
|||
you if a client instance didn't check back in time. |
|||
|
|||
As a side effect, you'll also get some statistical data from your client |
|||
instances. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
Install `dead_mans_switch_client` on a customer instance and configure it as |
|||
described in that module's documentation. The clients will register themselves |
|||
with the server automatically. They will show up with their database uuid, |
|||
you'll have to assign a human readable description yourself. |
|||
|
|||
At this point, you can assign a customer to this client instance for reporting |
|||
purposes, and, more important, add followers to the instance. They will be |
|||
notified in case the instance doesn't check back in time. Notification are only |
|||
turned on for instances in state 'active', instances in states 'new' or |
|||
'suspended' will be ignored. |
|||
|
|||
You'll find the instances' current state at Reporting/Customer instances. |
|||
|
|||
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas |
|||
:alt: Try me on Runbot |
|||
:target: https://runbot.odoo-community.org/runbot/149/8.0 |
|||
|
|||
For further information, please visit: |
|||
|
|||
* https://www.odoo.com/forum/help-1 |
|||
|
|||
Security considerations |
|||
======================= |
|||
|
|||
As the controller receiving status updates is unauthenticated, any internet user |
|||
can have your server create monitoring instance records. While this is annoying, |
|||
it's quite harmless and basically the same as misuse of the fetchmail module. |
|||
|
|||
For a more substantial annoyance, the attacker would have to guess one of your |
|||
client's database uuids, so they functionally are your passwords. |
|||
|
|||
To be sure, consider blocking this controller from unknown origins in your SSL |
|||
proxy. In nginx, it would look like this:: |
|||
|
|||
location /dead_mans_switch/alive { |
|||
allow 192.168.1.0/24; |
|||
# add other client's IPs |
|||
deny all; |
|||
} |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* matching is done via the database's uuid, so take care to change this if you |
|||
clone a database |
|||
* logging some postgres stats and disk usage would be nice too |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_. |
|||
In case of trouble, please check there if your issue has already been reported. |
|||
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback |
|||
`here <https://github.com/OCA/server-tools/issues/new?body=module:%20dead_mans_switch_server%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Holger Brunn <hbrunn@therp.nl> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit http://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import models |
|||
from . import controllers |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Dead man's switch (server)", |
|||
"version": "8.0.1.0.0", |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"category": "Monitoring", |
|||
"summary": "Be notified when customers' odoo instances go down", |
|||
"depends": [ |
|||
'mail', |
|||
'web_kanban_sparkline', |
|||
], |
|||
"data": [ |
|||
"data/ir_cron.xml", |
|||
"security/res_groups.xml", |
|||
"views/dead_mans_switch_log.xml", |
|||
"views/dead_mans_switch_instance.xml", |
|||
"views/menu.xml", |
|||
'security/ir.model.access.csv', |
|||
], |
|||
} |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import main |
@ -0,0 +1,28 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
import werkzeug |
|||
from openerp import http |
|||
from openerp.http import request |
|||
|
|||
|
|||
class Main(http.Controller): |
|||
@http.route('/dead_mans_switch/alive', type='json', auth="none") |
|||
def alive(self, **kwargs): |
|||
if 'database_uuid' not in kwargs: |
|||
raise werkzeug.exceptions.NotFound() |
|||
instance = request.env['dead.mans.switch.instance'].sudo().search([ |
|||
('database_uuid', '=', kwargs['database_uuid']), |
|||
]) |
|||
if not instance: |
|||
instance = request.env['dead.mans.switch.instance'].sudo().create({ |
|||
'database_uuid': kwargs['database_uuid'], |
|||
}) |
|||
data = { |
|||
field: value |
|||
for field, value in kwargs.iteritems() |
|||
if field in request.env['dead.mans.switch.log']._fields |
|||
} |
|||
instance.write({ |
|||
'log_ids': [(0, 0, data)], |
|||
}) |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data noupdate="1"> |
|||
<record id="cron_server" model="ir.cron"> |
|||
<field name="name">Dead man's switch server</field> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">minutes</field> |
|||
<field name="numbercall">-1</field> |
|||
<field name="model">dead.mans.switch.instance</field> |
|||
<field name="function">check_alive</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import dead_mans_switch_instance |
|||
from . import dead_mans_switch_log |
@ -0,0 +1,141 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
import json |
|||
from datetime import datetime, timedelta |
|||
from openerp import _, api, fields, models |
|||
|
|||
|
|||
class DeadMansSwitchInstance(models.Model): |
|||
_inherit = ['mail.thread'] |
|||
_name = 'dead.mans.switch.instance' |
|||
_description = 'Instance to monitor' |
|||
_order = 'state, partner_id' |
|||
_rec_name = 'partner_id' |
|||
|
|||
state = fields.Selection( |
|||
[('new', 'New'), ('active', 'Active'), ('suspended', 'Suspended')], |
|||
'State', default='new') |
|||
partner_id = fields.Many2one( |
|||
'res.partner', 'Customer', |
|||
domain=[('is_company', '=', True), ('customer', '=', True)]) |
|||
database_uuid = fields.Char('Database id', required=True, readonly=True) |
|||
user_id = fields.Many2one('res.users', 'Responsible user', |
|||
track_visibility='onchange') |
|||
description = fields.Char('Description') |
|||
log_ids = fields.One2many( |
|||
'dead.mans.switch.log', 'instance_id', string='Log lines') |
|||
alive = fields.Boolean('Alive', compute='_compute_alive') |
|||
alive_max_delay = fields.Integer( |
|||
'Alive delay', help='The amount of seconds without notice after which ' |
|||
'the instance is considered dead', default=600) |
|||
last_seen = fields.Datetime('Last seen', compute='_compute_last_log') |
|||
last_cpu = fields.Float('CPU', compute='_compute_last_log') |
|||
last_cpu_sparkline = fields.Text('CPU', compute='_compute_last_log') |
|||
last_ram = fields.Float('RAM', compute='_compute_last_log') |
|||
last_ram_sparkline = fields.Text('RAM', compute='_compute_last_log') |
|||
last_user_count = fields.Integer( |
|||
'Active users', compute='_compute_last_log') |
|||
last_user_count_sparkline = fields.Text( |
|||
'Active users', compute='_compute_last_log') |
|||
|
|||
_sql_constraints = [ |
|||
('uuid_unique', 'unique(database_uuid)', 'Database ID must be unique'), |
|||
] |
|||
|
|||
@api.multi |
|||
def name_get(self): |
|||
return [ |
|||
( |
|||
this.id, |
|||
'%s%s' % ( |
|||
this.partner_id.name or this.database_uuid, |
|||
' (%s)' % (this.description) if this.description else '', |
|||
) |
|||
) |
|||
for this in self |
|||
] |
|||
|
|||
@api.onchange('partner_id') |
|||
def _onchange_partner_id(self): |
|||
if not self.user_id: |
|||
self.user_id = self.partner_id.user_id |
|||
|
|||
@api.multi |
|||
def button_active(self): |
|||
self.write({'state': 'active'}) |
|||
|
|||
@api.multi |
|||
def button_suspended(self): |
|||
self.write({'state': 'suspended'}) |
|||
|
|||
@api.multi |
|||
def button_logs(self): |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'res_model': 'dead.mans.switch.log', |
|||
'domain': [('instance_id', 'in', self.ids)], |
|||
'name': _('Logs'), |
|||
'view_mode': 'graph,tree,form', |
|||
'context': { |
|||
'search_default_this_month': 1, |
|||
}, |
|||
} |
|||
|
|||
@api.multi |
|||
def _compute_alive(self): |
|||
for this in self: |
|||
if this.state in ['new', 'suspended']: |
|||
this.alive = False |
|||
continue |
|||
this.alive = bool( |
|||
self.env['dead.mans.switch.log'].search( |
|||
[ |
|||
('instance_id', '=', this.id), |
|||
('create_date', '>=', fields.Datetime.to_string( |
|||
datetime.utcnow() - |
|||
timedelta(seconds=this.alive_max_delay))), |
|||
], |
|||
limit=1)) |
|||
|
|||
@api.multi |
|||
def _compute_last_log(self): |
|||
for this in self: |
|||
last_log = self.env['dead.mans.switch.log'].search( |
|||
[('instance_id', '=', this.id)], limit=12) |
|||
field_mapping = { |
|||
'last_seen': 'create_date', |
|||
'last_cpu': 'cpu', |
|||
'last_ram': 'ram', |
|||
'last_user_count': 'user_count', |
|||
} |
|||
for field, mapped_field in field_mapping.iteritems(): |
|||
this[field] = last_log[:1][mapped_field] |
|||
sparkline_fields = ['last_cpu', 'last_ram', 'last_user_count'] |
|||
for field in sparkline_fields: |
|||
this['%s_sparkline' % field] = json.dumps( |
|||
list(reversed(last_log.mapped(lambda log: { |
|||
'value': log[field_mapping[field]], |
|||
'tooltip': log.create_date, |
|||
})))) |
|||
|
|||
@api.model |
|||
def check_alive(self): |
|||
"""handle cronjob""" |
|||
for this in self.search([('state', '=', 'active')]): |
|||
if this.alive: |
|||
continue |
|||
last_post = fields.Datetime.from_string(this.message_last_post) |
|||
if not last_post or datetime.utcnow() - timedelta( |
|||
seconds=this.alive_max_delay * 2) > last_post: |
|||
this.panic() |
|||
|
|||
@api.multi |
|||
def panic(self): |
|||
"""override for custom handling""" |
|||
self.ensure_one() |
|||
self.message_post( |
|||
type='comment', subtype='mt_comment', |
|||
subject=_('Dead man\'s switch warning: %s') % |
|||
self.display_name, content_subtype='plaintext', |
|||
body=_('%s seems to be dead') % self.display_name) |
@ -0,0 +1,17 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp import fields, models |
|||
|
|||
|
|||
class DeadMansSwitchLog(models.Model): |
|||
_name = 'dead.mans.switch.log' |
|||
_description = 'Instance log line' |
|||
_order = 'create_date desc' |
|||
_rec_name = 'create_date' |
|||
|
|||
instance_id = fields.Many2one( |
|||
'dead.mans.switch.instance', 'Instance', index=True) |
|||
cpu = fields.Float('CPU', group_operator='avg') |
|||
ram = fields.Float('RAM', group_operator='avg') |
|||
user_count = fields.Integer('Users logged in', group_operator='avg') |
@ -0,0 +1,4 @@ |
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
|||
access_dead_mans_switch_log,access_dead_mans_switch_log,model_dead_mans_switch_log,group_user,1,0,0,0 |
|||
access_dead_mans_switch_instance,access_dead_mans_switch_instance,model_dead_mans_switch_instance,group_user,1,0,0,0 |
|||
access_dead_mans_switch_instance_manager,access_dead_mans_switch_instance,model_dead_mans_switch_instance,group_manager,1,1,0,1 |
@ -0,0 +1,14 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<record id="group_user" model="res.groups"> |
|||
<field name="name">User</field> |
|||
<field name="category_id" ref="base.module_category_monitoring" /> |
|||
</record> |
|||
<record id="group_manager" model="res.groups"> |
|||
<field name="name">Manager</field> |
|||
<field name="implied_ids" eval="[(4, ref('group_user'))]" /> |
|||
<field name="category_id" ref="base.module_category_monitoring" /> |
|||
</record> |
|||
</data> |
|||
</openerp> |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import test_dead_mans_switch_server |
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2015 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp.tests.common import TransactionCase |
|||
from openerp import http |
|||
from ..controllers.main import Main |
|||
|
|||
|
|||
class TestDeadMansSwitchServer(TransactionCase): |
|||
def test_dead_mans_switch_server(self): |
|||
if 'dead.mans.switch.client' not in self.env.registry: |
|||
return |
|||
data = self.env['dead.mans.switch.client']._get_data() |
|||
http._request_stack.push(self) |
|||
Main().alive(**data) |
|||
instance = self.env['dead.mans.switch.instance'].search([ |
|||
('database_uuid', '=', data['database_uuid']), |
|||
]) |
|||
self.assertTrue(instance) |
|||
self.assertTrue(instance.last_seen) |
|||
self.assertEqual(instance.display_name, data['database_uuid']) |
|||
main_partner = self.env.ref('base.main_partner') |
|||
instance.partner_id = main_partner |
|||
self.assertEqual(instance.display_name, main_partner.name) |
|||
instance._onchange_partner_id() |
|||
self.assertEqual(instance.user_id, main_partner.user_id) |
|||
instance.button_suspended() |
|||
self.assertEqual(instance.state, 'suspended') |
|||
instance.button_active() |
|||
self.assertTrue(instance.alive) |
|||
message_count = len(instance.message_ids) |
|||
instance.check_alive() |
|||
self.assertEqual(len(instance.message_ids), message_count) |
|||
instance.log_ids.unlink() |
|||
instance.check_alive() |
|||
self.assertEqual(len(instance.message_ids), message_count + 1) |
@ -0,0 +1,117 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<record id="search_dead_mans_switch_instance" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.instance</field> |
|||
<field name="arch" type="xml"> |
|||
<search> |
|||
<field name="partner_id" /> |
|||
<field name="database_uuid" /> |
|||
<field name="description" /> |
|||
<group string="State"> |
|||
<filter name="active" string="Active" domain="[('state', '=', 'active')]" /> |
|||
<filter name="new" string="New" domain="[('state', '=', 'new')]" /> |
|||
<filter name="suspended" string="Suspended" domain="[('state', '=', 'suspended')]" /> |
|||
</group> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
<record id="form_dead_mans_switch_instance" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.instance</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<header> |
|||
<button type="object" name="button_active" |
|||
string="Activate" states="new,suspended" |
|||
class="oe_highlight" /> |
|||
<button type="object" name="button_suspended" |
|||
string="Suspend" states="active" /> |
|||
<field name="state" widget="statusbar" /> |
|||
</header> |
|||
<sheet> |
|||
<div class="oe_title"> |
|||
<h1 class="oe_read_only"><field name="display_name" /></h1> |
|||
</div> |
|||
<div class="oe_button_box oe_right"> |
|||
<button type="object" name="button_logs" |
|||
string="Open logs" class="oe_stat_button" |
|||
icon="fa-tasks" /> |
|||
</div> |
|||
<group> |
|||
<group name="description"> |
|||
<field name="partner_id" /> |
|||
<field name="description" /> |
|||
</group> |
|||
<group name="data"> |
|||
<field name="database_uuid" /> |
|||
<field name="user_id" /> |
|||
</group> |
|||
</group> |
|||
<div class="oe_chatter"> |
|||
<field name="message_follower_ids" widget="mail_followers"/> |
|||
<field name="message_ids" widget="mail_thread"/> |
|||
</div> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
<record id="tree_dead_mans_switch_instance" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.instance</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="display_name" /> |
|||
<field name="alive" /> |
|||
<field name="user_id" /> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
<record id="kanban_dead_mans_switch_instance" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.instance</field> |
|||
<field name="arch" type="xml"> |
|||
<kanban> |
|||
<field name="partner_id" /> |
|||
<field name="alive" /> |
|||
<field name="state" /> |
|||
<templates> |
|||
<div t-name="kanban-box" t-attf-class="oe_kanban_card #{widget.kanban_color(record.partner_id.raw_value[0])}"> |
|||
<h4> |
|||
<a type="open"><field name="display_name" /></a> |
|||
<t t-if="record.alive.raw_value and record.state.raw_value == 'active'"> |
|||
<i class="fa fa-circle" style="color: green" /> |
|||
</t> |
|||
<t t-if="!record.alive.raw_value and record.state.raw_value == 'active'"> |
|||
<i class="fa fa-circle" style="color: red" /> |
|||
</t> |
|||
<t t-if="record.state.raw_value != 'active'"> |
|||
<i class="fa fa-circle" style="color: gray" /> |
|||
</t> |
|||
</h4> |
|||
<table class="table"> |
|||
<tr> |
|||
<th>CPU</th> |
|||
<td><field name="last_cpu" />%</td> |
|||
<td><field name="last_cpu_sparkline" widget="sparkline_bar" /></td> |
|||
</tr> |
|||
<tr> |
|||
<th>RAM</th> |
|||
<td><field name="last_ram" />%</td> |
|||
<td><field name="last_ram_sparkline" widget="sparkline_bar" /></td> |
|||
</tr> |
|||
<tr> |
|||
<th>Users</th> |
|||
<td><field name="last_user_count" /></td> |
|||
<td><field name="last_user_count_sparkline" widget="sparkline_bar" /></td> |
|||
</tr> |
|||
<tr> |
|||
<th>Last seen</th> |
|||
<td><field name="last_seen" /></td> |
|||
<td /> |
|||
</tr> |
|||
</table> |
|||
</div> |
|||
</templates> |
|||
</kanban> |
|||
</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,38 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<record id="tree_dead_mans_switch_log" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.log</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="create_date" /> |
|||
<field name="cpu" /> |
|||
<field name="ram" /> |
|||
<field name="user_count" /> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
<record id="graph_dead_mans_switch_log" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.log</field> |
|||
<field name="arch" type="xml"> |
|||
<graph type="pivot"> |
|||
<field name="create_date" type="row" interval="day" /> |
|||
<field name="cpu" type="measure" /> |
|||
<field name="ram" type="measure" /> |
|||
<field name="user_count" type="measure" /> |
|||
</graph> |
|||
</field> |
|||
</record> |
|||
<record id="search_dead_mans_switch_log" model="ir.ui.view"> |
|||
<field name="model">dead.mans.switch.log</field> |
|||
<field name="arch" type="xml"> |
|||
<search> |
|||
<filter name="this_month" string="This month" |
|||
domain="[('create_date', '>=', (datetime.date.today() - relativedelta(days=31)).strftime('%Y-%m-%d'))]" /> |
|||
<filter name="this_week" string="This week" |
|||
domain="[('create_date', '>=', (datetime.date.today() - relativedelta(days=6)).strftime('%Y-%m-%d'))]" /> |
|||
</search> |
|||
</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,16 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<act_window id="action_dead_mans_switch_instance" |
|||
name="Customer instances" |
|||
res_model="dead.mans.switch.instance" |
|||
context="{'search_default_active': 1}" |
|||
view_mode="kanban,tree,form" /> |
|||
<menuitem id="menu_dead_mans_switch" |
|||
name="Customer instances" |
|||
parent="base.menu_reporting" /> |
|||
<menuitem id="menu_dead_mans_switch_instance" |
|||
action="action_dead_mans_switch_instance" |
|||
parent="menu_dead_mans_switch" /> |
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue