Browse Source

[ADD] dead_mans_switch_client, dead_mans_switch_server

pull/297/head
Holger Brunn 9 years ago
parent
commit
9b6c6b9048
  1. 73
      dead_mans_switch_client/README.rst
  2. 4
      dead_mans_switch_client/__init__.py
  3. 18
      dead_mans_switch_client/__openerp__.py
  4. 13
      dead_mans_switch_client/data/ir_actions.xml
  5. 13
      dead_mans_switch_client/data/ir_cron.xml
  6. 4
      dead_mans_switch_client/models/__init__.py
  7. 68
      dead_mans_switch_client/models/dead_mans_switch_client.py
  8. BIN
      dead_mans_switch_client/static/description/icon.png
  9. 4
      dead_mans_switch_client/tests/__init__.py
  10. 17
      dead_mans_switch_client/tests/test_dead_mans_switch_client.py
  11. 93
      dead_mans_switch_server/README.rst
  12. 5
      dead_mans_switch_server/__init__.py
  13. 23
      dead_mans_switch_server/__openerp__.py
  14. 4
      dead_mans_switch_server/controllers/__init__.py
  15. 28
      dead_mans_switch_server/controllers/main.py
  16. 13
      dead_mans_switch_server/data/ir_cron.xml
  17. 5
      dead_mans_switch_server/models/__init__.py
  18. 141
      dead_mans_switch_server/models/dead_mans_switch_instance.py
  19. 17
      dead_mans_switch_server/models/dead_mans_switch_log.py
  20. 4
      dead_mans_switch_server/security/ir.model.access.csv
  21. 14
      dead_mans_switch_server/security/res_groups.xml
  22. BIN
      dead_mans_switch_server/static/description/icon.png
  23. 4
      dead_mans_switch_server/tests/__init__.py
  24. 36
      dead_mans_switch_server/tests/test_dead_mans_switch_server.py
  25. 117
      dead_mans_switch_server/views/dead_mans_switch_instance.xml
  26. 38
      dead_mans_switch_server/views/dead_mans_switch_log.xml
  27. 16
      dead_mans_switch_server/views/menu.xml

73
dead_mans_switch_client/README.rst

@ -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.

4
dead_mans_switch_client/__init__.py

@ -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

18
dead_mans_switch_client/__openerp__.py

@ -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",
],
}

13
dead_mans_switch_client/data/ir_actions.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>

13
dead_mans_switch_client/data/ir_cron.xml

@ -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&apos;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>

4
dead_mans_switch_client/models/__init__.py

@ -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

68
dead_mans_switch_client/models/dead_mans_switch_client.py

@ -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',
}))

BIN
dead_mans_switch_client/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

4
dead_mans_switch_client/tests/__init__.py

@ -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

17
dead_mans_switch_client/tests/test_dead_mans_switch_client.py

@ -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()

93
dead_mans_switch_server/README.rst

@ -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.

5
dead_mans_switch_server/__init__.py

@ -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

23
dead_mans_switch_server/__openerp__.py

@ -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',
],
}

4
dead_mans_switch_server/controllers/__init__.py

@ -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

28
dead_mans_switch_server/controllers/main.py

@ -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)],
})

13
dead_mans_switch_server/data/ir_cron.xml

@ -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&apos;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>

5
dead_mans_switch_server/models/__init__.py

@ -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

141
dead_mans_switch_server/models/dead_mans_switch_instance.py

@ -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)

17
dead_mans_switch_server/models/dead_mans_switch_log.py

@ -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')

4
dead_mans_switch_server/security/ir.model.access.csv

@ -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

14
dead_mans_switch_server/security/res_groups.xml

@ -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>

BIN
dead_mans_switch_server/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

4
dead_mans_switch_server/tests/__init__.py

@ -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

36
dead_mans_switch_server/tests/test_dead_mans_switch_server.py

@ -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)

117
dead_mans_switch_server/views/dead_mans_switch_instance.xml

@ -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>

38
dead_mans_switch_server/views/dead_mans_switch_log.xml

@ -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>

16
dead_mans_switch_server/views/menu.xml

@ -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>
Loading…
Cancel
Save