class DBConnection:
    """A sql connection manager."""
    def __init__(self, host: str, user: str, passwd: str, dbname: str, loop: asyncio.AbstractEventLoop=None) -> None:
        self.loop = loop or asyncio.get_event_loop()
        self.host = host
        self.user = user
        self.passwd = passwd
        self.dbname = dbname
        self.pool = None  # type: Any
        stats.set('queries', 0, 'SQL')
        stats.set('transactions', 0, 'SQL')

    async def initialize(self, *, only_init_tables: bool=False):
        """Initialize the DBConnection.

        Creates a connection pool using aiomysql and initializes the database
        if necessary.
        """
        self.pool = await aiomysql.create_pool(
            host=self.host, user=self.user, password=self.passwd,
            loop=self.loop)
        db_exists = await self._check_db_exists()
        if not db_exists:
            await self._create_db()
            db_initialized = False
        else:
            db_initialized = await self._check_db_initialized()
        # We close the pool and create a new one because aiomysql doesn't
        # provide an easy way to change the active database for an entire
        # pool, just individual connections.
        self.pool.terminate()
        self.pool = await aiomysql.create_pool(
            host=self.host, user=self.user, password=self.passwd,
            db=self.dbname, loop=self.loop)
        if not db_initialized:
            await self._init_db(only_init_tables)
        await self._upgrade_db()
        log.msg('Database initialized')

    async def close(self) -> None:
        self.pool.terminate()
        await self.pool.wait_closed()

    async def _create_db(self) -> None:
        # Yes yes, this risks sql injection, but the dbname is from the
        # irisett config file, so if you want to sql inject yourself,
        # go ahead.
        log.msg('Creating missing database %s' % self.dbname)
        q = """CREATE DATABASE %s""" % self.dbname
        await self.operation(q)

    async def _init_db(self, only_init_tables: bool) -> None:
        log.msg('Initializing empty database')
        commands = sql_data.SQL_ALL
        if only_init_tables:
            commands = sql_data.SQL_BARE
        for command in commands:
            await self.operation(command)

    async def _check_db_exists(self) -> bool:
        """Check if the database exists."""
        q = """SELECT SCHEMA_NAME
            FROM INFORMATION_SCHEMA.SCHEMATA
            WHERE SCHEMA_NAME = %s"""
        res = await self.fetch_single(q, (self.dbname,))
        if not res:
            return False
        return True

    async def _check_db_initialized(self) -> bool:
        """Check if the database has been initialized."""
        q = """SELECT count(*)
        FROM information_schema.TABLES
        WHERE (TABLE_SCHEMA = %s) AND (TABLE_NAME = %s)"""
        res = await self.fetch_single(q, (self.dbname, 'version'))
        if res == 0:
            return False
        return True

    async def _upgrade_db(self) -> None:
        """Upgrade to a newer database version if required.

        Loops through the commands in sql_data.SQL_UPGRADES and runs them.
        """
        cur_version = await self._get_db_version()
        for n in range(cur_version + 1, sql_data.CUR_VERSION + 1):
            log.msg('Upgrading database to version %d' % n)
            if n in sql_data.SQL_UPGRADES:
                for command in sql_data.SQL_UPGRADES[n]:
                    await self.operation(command)
        if cur_version != sql_data.CUR_VERSION:
            await self._set_db_version(sql_data.CUR_VERSION)

    async def _get_db_version(self) -> int:
        q = """select version from version limit 1"""
        str_version = await self.fetch_single(q)
        version = int(str_version)
        return version

    async def _set_db_version(self, version: int):
        q = """update version set version=%s"""
        q_args = (version,)
        await self.operation(q, q_args)

    async def fetch_all(self, query: str, args: Optional[Iterable]=None) -> List:
        """Run a query and fetch all returned rows."""
        stats.inc('queries', 'SQL')
        async with self.pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(query, args)
                ret = await cur.fetchall()
        return ret

    async def fetch_row(self, query: str, args: Optional[Iterable]=None) -> List:
        """Run a query and fetch a single returned row."""
        stats.inc('queries', 'SQL')
        async with self.pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(query, args)
                ret = await cur.fetchone()
        return ret

    async def fetch_single(self, query: str, args: Optional[Iterable]=None) -> Any:
        """Run a query and fetch a single returned value from a single row."""
        res = await self.fetch_row(query, args)
        ret = None
        if res and len(res) == 1:
            ret = res[0]
        return ret

    async def count_rows(self, query: str, args: Optional[Iterable]=None) -> float:
        """Count the number of returned rows for a query.

        This is not equivalent to select count(*), this will actually fetch
        all the rows then count them and is thus much slower.
        """
        res = await self.fetch_all(query, args)
        return len(res)

    async def operation(self, query: str, args: Optional[Iterable]=None) -> Any:
        """Run a sql operation (query).

        Ie. insert, update etc. not select.
        Returns the row id of the created row if any.
        """
        stats.inc('queries', 'SQL')
        async with self.pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute(query, args)
                ret = cur.lastrowid
                await conn.commit()
        return ret

    async def transact(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
        """Create a db cursor and hand it to a callback.

        This can be used to simulate transactions.
        commit will be called when the callback returns. If an exception is
        raised in the callback a rollback is performed.
        """
        stats.inc('transactions', 'SQL')
        async with self.pool.acquire() as conn:
            async with conn.cursor() as cur:
                try:
                    ret = await func(cur, *args, **kwargs)
                except:
                    await conn.rollback()
                    raise
                else:
                    await conn.commit()
        return ret async def get_metadata(dbcon: DBConnection, object_type: str, object_id: int) -> Dict[str, str]:
async def add_metadata(dbcon: DBConnection, object_type: str, object_id: int, metadict: Dict[str, str]):
    async def _run(cur: Cursor) -> None:
async def update_metadata(dbcon: DBConnection, object_type: str, object_id: int, metadict: Dict[str, str]):
    async def _run(cur: Cursor) -> None:
async def delete_metadata(dbcon: DBConnection, object_type: str, object_id: int,
                          keys: Optional[Iterable[str]] = None):
    async def _run(cur: Cursor) -> None:
async def get_metadata_for_object(
        dbcon: DBConnection, object_type: str, object_id: int) -> Iterable[object_models.ObjectMetadata]:
async def get_metadata_for_object_type(
        dbcon: DBConnection, object_type: str) -> Iterable[object_models.ObjectMetadata]:
async def get_metadata_for_object_metadata(
        dbcon: DBConnection, metadata_key: str, metadata_value: str,
        object_type: str, object_table: str) -> Iterable[object_models.ObjectMetadata]: def get_section(section: Optional[str]) -> Dict[str, float]:
def set(var: str, value: float, section: Optional[str] = None) -> None:
def inc(var: str, section: Optional[str] = None) -> None:
def dec(var: str, section: Optional[str] = None) -> None:
def get_stats() -> Dict[str, float]: async def create_contact(dbcon: DBConnection, name: Optional[str], email: Optional[str],
                         phone: Optional[str], active: bool) -> str:
async def update_contact(dbcon: DBConnection, contact_id: int, data: Dict[str, str]) -> None:
    async def _run(cur: Cursor) -> None:
async def delete_contact(dbcon: DBConnection, contact_id: int) -> None:
async def create_contact_group(dbcon: DBConnection, name: str, active: bool) -> str:
async def update_contact_group(dbcon: DBConnection, contact_group_id: int, data: Dict[str, str]) -> None:
    async def _run(cur: Cursor) -> None:
async def delete_contact_group(dbcon: DBConnection, contact_group_id: int) -> None:
async def get_all_contacts_for_active_monitor(dbcon: DBConnection, monitor_id: int) -> Iterable[object_models.Contact]:
async def _active_monitor_contacts(dbcon: DBConnection, monitor_id: int) -> Set[object_models.Contact]:
async def _active_monitor_contact_groups(dbcon: DBConnection, monitor_id: int) -> Set[object_models.Contact]:
async def _active_monitor_monitor_group_contacts(dbcon: DBConnection, monitor_id: int) -> Set[object_models.Contact]:
async def _active_monitor_monitor_group_contact_groups(
        dbcon: DBConnection, monitor_id: int) -> Set[object_models.Contact]:
async def get_contact_dict_for_active_monitor(dbcon: DBConnection, monitor_id: int) -> Dict[str, set]:
async def add_contact_to_active_monitor(dbcon: DBConnection, contact_id: int, monitor_id: int) -> None:
async def delete_contact_from_active_monitor(dbcon: DBConnection, contact_id: int, monitor_id: int) -> None:
async def set_active_monitor_contacts(dbcon: DBConnection,
                                      contact_ids: Iterable[int], monitor_id: int):
    async def _run(cur: Cursor) -> None:
async def get_contacts_for_active_monitor(dbcon: DBConnection, monitor_id: int) -> Iterable[object_models.Contact]:
async def add_contact_group_to_active_monitor(dbcon: DBConnection, contact_group_id: int, monitor_id: int) -> None:
async def delete_contact_group_from_active_monitor(dbcon: DBConnection, contact_group_id: int, monitor_id: int) -> None:
async def set_active_monitor_contact_groups(dbcon: DBConnection,
                                            contact_group_ids: Iterable[int], monitor_id: int) -> None:
    async def _run(cur: Cursor) -> None:
async def get_contact_groups_for_active_monitor(
        dbcon: DBConnection, monitor_id: int) -> Iterable[object_models.ContactGroup]:
async def get_all_contacts(dbcon: DBConnection) -> Iterable[object_models.Contact]:
async def get_contact(dbcon: DBConnection, id: int) -> Any:  # Use any because optional returns suck.
async def get_contacts_for_metadata(
        dbcon: DBConnection, meta_key: str, meta_value: str) -> Iterable[object_models.Contact]:
async def add_contact_to_contact_group(dbcon: DBConnection, contact_group_id: int, contact_id: int) -> None:
async def delete_contact_from_contact_group(dbcon: DBConnection, contact_group_id: int, contact_id: int) -> None:
async def set_contact_group_contacts(dbcon: DBConnection,
                                     contact_group_id: int, contact_ids: Iterable[int]) -> None:
    async def _run(cur: Cursor) -> None:
async def get_contacts_for_contact_group(dbcon: DBConnection, contact_group_id: int) -> Iterable[object_models.Contact]:
async def get_all_contact_groups(dbcon: DBConnection) -> Iterable[object_models.ContactGroup]:
async def get_contact_group(dbcon: DBConnection, id: int) -> Any:  # Use any because optional returns suck.
async def get_contact_groups_for_metadata(
        dbcon: DBConnection, meta_key: str, meta_value: str) -> Iterable[object_models.ContactGroup]: def configure_logging(logtype: str, logfilename: Optional[str]=None, debug_logging: bool=False,
                      rotate_length: int=1000000, max_rotated_files: int=250) -> None:
def msg(logmsg: str, section: Optional[str]=None) -> None:
def debug(logmsg: str, section: Optional[str]=None) -> None:
    def log_msg(self, logmsg: str) -> None:
    def log_debug(self, logmsg: str) -> None:
class LoggingMixin: def insert_values(object: Any) -> Tuple:
def list_asdict(in_list: Iterable[Any]) -> List[Any]:
def insert_filter(attribute: Any, value: Any) -> bool:
class Contact:
class ContactGroup:
class ActiveMonitor:
class ActiveMonitorArg:
class ActiveMonitorAlert:
class ActiveMonitorDef:
class ActiveMonitorDefArg:
class ObjectMetadata:
class ObjectBindata:
class MonitorGroup: async def create_monitor_group(dbcon: DBConnection, parent_id: Optional[int], name: str):
async def update_monitor_group(dbcon: DBConnection, monitor_group_id: int, data: Dict[str, Any]):
    async def _run(cur: Cursor) -> None:
async def delete_monitor_group(dbcon: DBConnection, monitor_group_id: int) -> None:
    async def _run(cur: Cursor) -> None:
async def add_active_monitor_to_monitor_group(dbcon: DBConnection, monitor_group_id: int, monitor_id: int) -> None:
async def delete_active_monitor_from_monitor_group(dbcon: DBConnection, monitor_group_id: int, monitor_id: int) -> None:
async def add_contact_to_monitor_group(dbcon: DBConnection, monitor_group_id: int, contact_id: int) -> None:
async def delete_contact_from_monitor_group(dbcon: DBConnection, monitor_group_id: int, contact_id: int) -> None:
async def add_contact_group_to_monitor_group(dbcon: DBConnection, monitor_group_id: int, contact_group_id: int) -> None:
async def delete_contact_group_from_monitor_group(
        dbcon: DBConnection, monitor_group_id: int, contact_group_id: int) -> None:
async def get_all_monitor_groups(dbcon: DBConnection) -> Iterable[object_models.MonitorGroup]:
async def get_monitor_group(dbcon: DBConnection, id: int) -> Any:  # Use any because optional returns suck.
async def get_contacts_for_monitor_group(dbcon: DBConnection, id: int) -> Iterable[object_models.Contact]:
async def get_contact_groups_for_monitor_group(dbcon: DBConnection, id: int) -> Iterable[object_models.ContactGroup]:
async def get_active_monitors_for_monitor_group(dbcon: DBConnection, id: int) -> Iterable[object_models.ActiveMonitor]:
async def get_monitor_groups_for_metadata(
        dbcon: DBConnection, meta_key: str, meta_value: str) -> Iterable[object_models.MonitorGroup]:
async def get_active_monitor_metadata_for_monitor_group(
        dbcon: DBConnection, id: int) -> Iterable[object_models.ObjectMetadata]: async def get_all_active_monitor_defs(dbcon: DBConnection) -> Iterable[object_models.ActiveMonitorDef]:
async def get_active_monitor_def(dbcon: DBConnection, id: int) -> Optional[object_models.ActiveMonitorDef]:
async def get_all_active_monitor_def_args(dbcon: DBConnection) -> Iterable[object_models.ActiveMonitorDefArg]:
async def get_active_monitor_def_args_for_def(
        dbcon: DBConnection, def_id: int) -> Iterable[object_models.ActiveMonitorDefArg]:
async def get_all_active_monitors(dbcon: DBConnection) -> Iterable[object_models.ActiveMonitor]:
async def get_all_active_monitor_args(dbcon: DBConnection) -> Iterable[object_models.ActiveMonitorArg]:
async def get_active_monitors_for_metadata(dbcon: DBConnection, meta_key: str, meta_value: str): class WebMgmtError(Exception):
class InvalidData(WebMgmtError):
class PermissionDenied(WebMgmtError):
class NotFound(WebMgmtError):
class MissingLogin(WebMgmtError): class WSEventProxy:
    def __init__(self, request: web.Request) -> None:
    async def run(self) -> None:
    async def _ws_read(self) -> None:
    async def _handle_events(self, listener: event.EventListener, event_name: str, timestamp: int, data: Any) -> None:
from typing import Any, Dict, List
from aiohttp import web
from irisett.sql import DBConnection
from irisett import (
    metadata,
    stats,
    contact,
    log,
    object_models,
    monitor_group,
)
from irisett.monitor import active_sql
from irisett.webmgmt import (
    errors,
    ws_event_proxy,
)
import time
import aiohttp_jinja2
            'section': 'alerts',
            'subsection': 'active_alerts',
            'alerting_active_monitors': [m for m in active_monitors.values() if m.state == 'DOWN']
        }
        return context


class AlertHistoryView(web.View):
    @aiohttp_jinja2.template('alert_history.html')
    async def get(self) -> Dict[str, Any]:
        alerts = await self._get_active_monitor_alerts()
        context = {
            'section': 'alerts',
            'subsection': 'alert_history',
            'alerts': alerts,
        }
        return context

    async def _get_active_monitor_alerts(self) -> List[object_models.ActiveMonitorAlert]:
        am_manager = self.request.app['active_monitor_manager']
        q = """select id, monitor_id, start_ts, end_ts, alert_msg from active_monitor_alerts order by start_ts desc"""
        alerts = []  # type: List[object_models.ActiveMonitorAlert]
        for row in await self.request.app['dbcon'].fetch_all(q):
            alert = object_models.ActiveMonitorAlert(*row)
            alert.monitor = am_manager.monitors.get(alert.monitor_id)
            alerts.append(alert)
        return alerts


class EventsView(web.View):
    """Events websocket proxy.

    This just supplies the HTML and javascript to connect the the websocket
    handler.
    """

    @aiohttp_jinja2.template('events.html')
    async def get(self) -> Dict[str, Any]:
        context = {'section': 'events'}  # type: Dict[str, Any]
        return context


async def events_websocket_handler(request: web.Request) -> Any:
    """GET view for events websocket.

    All the work is done in the WSEventProxy class.
    """
    proxy = ws_event_proxy.WSEventProxy(request)
    log.debug('Starting event websocket session')
    await proxy.run()
    log.debug('Ending event websocket session')
    return proxy.ws


class ListActiveMonitorsView(web.View):
    @aiohttp_jinja2.template('list_active_monitors.html')
    async def get(self) -> Dict[str, Any]:
        am_manager = self.request.app['active_monitor_manager']
        active_monitors = am_manager.monitors
        context = {
            'section': 'active_monitors',
            'active_monitors': active_monitors.values(),
        }
        return context


class DisplayActiveMonitorView(web.View):
    @aiohttp_jinja2.template('display_active_monitor.html')
    async def get(self) -> Dict[str, Any]:
        monitor_id = int(self.request.match_info['id'])
        am_manager = self.request.app['active_monitor_manager']
        monitor = am_manager.monitors[monitor_id]
        context = {
            'section': 'active_monitors',
            'notification_msg': self.request.rel_url.query.get('notification_msg'),
            'monitor': monitor,
            'metadata': await metadata.get_metadata_for_object(self.request.app['dbcon'], 'active_monitor', monitor_id),
            'contacts': await contact.get_all_contacts_for_active_monitor(self.request.app['dbcon'], monitor_id),
        }
        return context


async def run_active_monitor_view(request: web.Request) -> web.Response:
    """GET view to run an active monitor immediately."""
    monitor_id = int(request.match_info['id'])
    am_manager = request.app['active_monitor_manager']
    monitor = am_manager.monitors[monitor_id]
    monitor.schedule_immediately()
    return web.HTTPFound('/active_monitor/%s/?notification_msg=Monitor job scheduled' % monitor_id)


async def send_active_monitor_test_notification(request: web.Request) -> web.Response:
    """GET view to send a test notification for an active monitor."""
    monitor_id = int(request.match_info['id'])
    am_manager = request.app['active_monitor_manager']
    monitor = am_manager.monitors[monitor_id]
    monitor.schedule_immediately()
    await monitor.notify_state_change('UNKNOWN', abs(monitor.state_ts - (time.time() - monitor.state_ts)))
    return web.HTTPFound('/active_monitor/%s/?notification_msg=Notification sent' % monitor_id)


class ListActiveMonitorDefsView(web.View):
    @aiohttp_jinja2.template('list_active_monitor_defs.html')
    async def get(self) -> Dict[str, Any]:
        context = {
            'section': 'active_monitor_defs',
            'monitor_defs': await active_sql.get_all_active_monitor_defs(self.request.app['dbcon']),
        }
        return context


class DisplayActiveMonitorDefView(web.View):
    @aiohttp_jinja2.template('display_active_monitor_def.html')
    async def get(self) -> Dict[str, Any]:
        dbcon = self.request.app['dbcon']
        monitor_def_id = int(self.request.match_info['id'])
        am_manager = self.request.app['active_monitor_manager']
        monitor_def = am_manager.monitor_defs[monitor_def_id]
        sql_monitor_def = await active_sql.get_active_monitor_def(dbcon, monitor_def_id)
        if not sql_monitor_def:

            raise errors.NotFound()