WIP for network check
diff --git a/cfg_checker/cfg_check.py b/cfg_checker/cfg_check.py
index b2e3886..8a05771 100644
--- a/cfg_checker/cfg_check.py
+++ b/cfg_checker/cfg_check.py
@@ -9,6 +9,7 @@
 from cfg_checker.clients import salt
 
 from cfg_checker.pkg_check import CloudPackageChecker
+from cfg_checker.network_checks import NetworkChecker
 
 pkg_dir = os.path.dirname(__file__)
 pkg_dir = os.path.normpath(pkg_dir)
@@ -47,13 +48,17 @@
 
 
 def net_check(args):
-    print("This is net check routine")
+    netChecker = NetworkChecker()
+    netChecker.collect_network_info()
+    netChecker.print_network_report()
 
     return
 
 
 def net_report(args):
-    print("This is net check routine")
+    netChecker = NetworkChecker()
+    netChecker.collect_network_info()
+    netChecker.create_html_report()
 
     return
 
diff --git a/cfg_checker/network_checks.py b/cfg_checker/network_checks.py
new file mode 100644
index 0000000..56b25de
--- /dev/null
+++ b/cfg_checker/network_checks.py
@@ -0,0 +1,81 @@
+import json
+import os
+import sys
+
+from copy import deepcopy
+
+import reporter
+
+from cfg_checker.common import utils, const
+from cfg_checker.common import config, logger, logger_cli, pkg_dir
+from cfg_checker.common import salt_utils
+from cfg_checker.nodes import SaltNodes, node_tmpl
+
+
+class NetworkChecker(SaltNodes):
+    def collect_network_info(self):
+        """
+        Collects info on the network using ifs_data.py script
+
+        :return: none
+        """
+        logger_cli.info("### Collecting network data")
+        _result = self.execute_script("ifs_data.py", args=["json"])
+
+        for key in self.nodes.keys():
+            # due to much data to be passed from salt, it is happening in order
+            if key in _result:
+                _text = _result[key]
+                _dict = json.loads(_text[_text.find('{'):])
+                self.nodes[key]['networks'] = _dict
+            else:
+                self.nodes[key]['networks'] = {}
+            logger_cli.debug("# {} has {} networks".format(
+                key,
+                len(self.nodes[key]['networks'].keys())
+            ))
+        logger_cli.info("-> Done collecting networks data")
+
+        return
+
+    def print_network_report(self):
+        """
+        Create text report for CLI
+
+        :return: none
+        """
+        
+        return
+    
+    def create_html_report(self, filename):
+        """
+        Create static html showing network schema-like report
+
+        :return: none
+        """
+        logger_cli.info("### Generating report to '{}'".format(filename))
+        _report = reporter.ReportToFile(
+            reporter.HTMLNetworkReport(),
+            filename
+        )
+        _report({
+            "nodes": self.nodes,
+            "diffs": {}
+        })
+        logger_cli.info("-> Done")
+
+
+if __name__ == '__main__':
+    # init connection to salt and collect minion data
+    cl = NetworkChecker()
+
+    # collect data on installed packages
+    cl.collect_network_info()
+
+    # diff installed and candidates
+    # cl.collect_packages()
+
+    # report it
+    cl.create_html_report("./pkg_versions.html")
+
+    sys.exit(0)
diff --git a/cfg_checker/nodes.py b/cfg_checker/nodes.py
new file mode 100644
index 0000000..1f2b9f6
--- /dev/null
+++ b/cfg_checker/nodes.py
@@ -0,0 +1,125 @@
+import json
+import os
+import sys
+
+from  copy import deepcopy
+
+from cfg_checker.common import utils, const
+from cfg_checker.common import config, logger, logger_cli, pkg_dir
+from cfg_checker.common import salt_utils
+
+node_tmpl = {
+    'role': '',
+    'node_group': '',
+    'status': const.NODE_DOWN,
+    'pillars': {},
+    'grains': {}
+}
+
+
+class SaltNodes(object):
+    def __init__(self):
+        logger_cli.info("### Collecting nodes for package check")
+        # simple salt rest client
+        self.salt = salt_utils.SaltRemote()
+
+        # Keys for all nodes
+        # this is not working in scope of 2016.8.3, will overide with list
+        # cls.node_keys = cls.salt.list_keys()
+
+        logger_cli.info("### Collecting node names existing in the cloud")
+        self.node_keys = {
+            'minions': config.all_nodes
+        }
+
+        # all that answer ping
+        _active = self.salt.get_active_nodes()
+        logger_cli.debug("-> Nodes responded: {}".format(_active))
+        # just inventory for faster interaction
+        # iterate through all accepted nodes and create a dict for it
+        self.nodes = {}
+        for _name in self.node_keys['minions']:
+            _nc = utils.get_node_code(_name)
+            _rmap = const.all_roles_map
+            _role = _rmap[_nc] if _nc in _rmap else 'unknown'
+            _status = const.NODE_UP if _name in _active else const.NODE_DOWN
+
+            self.nodes[_name] = deepcopy(node_tmpl)
+            self.nodes[_name]['node_group'] = _nc
+            self.nodes[_name]['role'] = _role
+            self.nodes[_name]['status'] = _status
+
+        logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
+
+    def get_nodes(self):
+        return self.nodes
+    
+    def execute_script(self, script_filename, args=[]):
+        # form an all nodes compound string to use in salt
+        _active_nodes_string = self.salt.compound_string_from_list(
+            filter(
+                lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
+                self.nodes
+            )
+        )
+        # Prepare script
+        _p = os.path.join(pkg_dir, 'scripts', script_filename)
+        with open(_p, 'rt') as fd:
+            _script = fd.read().splitlines()
+        _storage_path = os.path.join(
+            config.salt_file_root, config.salt_scripts_folder
+        )
+        logger_cli.debug(
+            "# Uploading script {} to master's file cache folder: '{}'".format(
+                script_filename,
+                _storage_path
+            )
+        )
+        _result = self.salt.mkdir("cfg01*", _storage_path)
+        # Form cache, source and target path
+        _cache_path = os.path.join(_storage_path, script_filename)
+        _source_path = os.path.join(
+            'salt://',
+            config.salt_scripts_folder,
+            script_filename
+        )
+        _target_path = os.path.join(
+            '/root',
+            config.salt_scripts_folder,
+            script_filename
+        )
+
+        logger_cli.debug("# Creating file in cache '{}'".format(_cache_path))
+        _result = self.salt.f_touch_master(_cache_path)
+        _result = self.salt.f_append_master(_cache_path, _script)
+        # command salt to copy file to minions
+        logger_cli.debug("# Creating script target folder '{}'".format(_cache_path))
+        _result = self.salt.mkdir(
+            _active_nodes_string,
+            os.path.join(
+                '/root',
+                config.salt_scripts_folder
+            ),
+            tgt_type="compound"
+        )
+        logger_cli.info("-> Running script to all active nodes")
+        _result = self.salt.get_file(
+            _active_nodes_string,
+            _source_path,
+            _target_path,
+            tgt_type="compound"
+        )
+        # execute pkg collecting script
+        logger.debug("Running script to all nodes")
+        # handle results for each node
+        _script_arguments = " ".join(args) if args else ""
+        _result = self.salt.cmd(
+            _active_nodes_string,
+            'cmd.run',
+            param='python {} {}'.format(_target_path, _script_arguments),
+            expr_form="compound"
+        )
+
+        # TODO: Handle error result
+
+        return _result
diff --git a/cfg_checker/pkg_check.py b/cfg_checker/pkg_check.py
index fe34f60..e9c312e 100644
--- a/cfg_checker/pkg_check.py
+++ b/cfg_checker/pkg_check.py
@@ -9,50 +9,10 @@
 from cfg_checker.common import utils, const
 from cfg_checker.common import config, logger, logger_cli, pkg_dir
 from cfg_checker.common import salt_utils
-
-node_tmpl = {
-    'role': '',
-    'node_group': '',
-    'status': const.NODE_DOWN,
-    'pillars': {},
-    'grains': {}
-}
+from cfg_checker.nodes import SaltNodes, node_tmpl
 
 
-class CloudPackageChecker(object):
-    def __init__(self):
-        logger_cli.info("### Collecting nodes for package check")
-        # simple salt rest client
-        self.salt = salt_utils.SaltRemote()
-
-        # Keys for all nodes
-        # this is not working in scope of 2016.8.3, will overide with list
-        # cls.node_keys = cls.salt.list_keys()
-
-        logger_cli.info("### Collecting node names existing in the cloud")
-        self.node_keys = {
-            'minions': config.all_nodes
-        }
-
-        # all that answer ping
-        _active = self.salt.get_active_nodes()
-        logger_cli.debug("-> Nodes responded: {}".format(_active))
-        # just inventory for faster interaction
-        # iterate through all accepted nodes and create a dict for it
-        self.nodes = {}
-        for _name in self.node_keys['minions']:
-            _nc = utils.get_node_code(_name)
-            _rmap = const.all_roles_map
-            _role = _rmap[_nc] if _nc in _rmap else 'unknown'
-            _status = const.NODE_UP if _name in _active else const.NODE_DOWN
-
-            self.nodes[_name] = deepcopy(node_tmpl)
-            self.nodes[_name]['node_group'] = _nc
-            self.nodes[_name]['role'] = _role
-            self.nodes[_name]['status'] = _status
-
-        logger_cli.info("-> {} nodes collected".format(len(self.nodes)))
-
+class CloudPackageChecker(SaltNodes):
     def collect_installed_packages(self):
         """
         Collect installed packages on each node
@@ -61,70 +21,8 @@
         :return: none
         """
         logger_cli.info("### Collecting installed packages")
-        # form an all nodes compound string to use in salt
-        _active_nodes_string = self.salt.compound_string_from_list(
-            filter(
-                lambda nd: self.nodes[nd]['status'] == const.NODE_UP,
-                self.nodes
-            )
-        )
-        # Prepare script
-        _script_filename = "pkg_versions.py"
-        _p = os.path.join(pkg_dir, 'scripts', _script_filename)
-        with open(_p, 'rt') as fd:
-            _script = fd.read().splitlines()
-        _storage_path = os.path.join(
-            config.salt_file_root, config.salt_scripts_folder
-        )
-        logger_cli.debug(
-            "# Uploading script {} to master's file cache folder: '{}'".format(
-                _script_filename,
-                _storage_path
-            )
-        )
-        _result = self.salt.mkdir("cfg01*", _storage_path)
-        # Form cache, source and target path
-        _cache_path = os.path.join(_storage_path, _script_filename)
-        _source_path = os.path.join(
-            'salt://',
-            config.salt_scripts_folder,
-            _script_filename
-        )
-        _target_path = os.path.join(
-            '/root',
-            config.salt_scripts_folder,
-            _script_filename
-        )
+        _result = self.execute_script("pkg_versions.py")
 
-        logger_cli.debug("# Creating file in cache '{}'".format(_cache_path))
-        _result = self.salt.f_touch_master(_cache_path)
-        _result = self.salt.f_append_master(_cache_path, _script)
-        # command salt to copy file to minions
-        logger_cli.debug("# Creating script target folder '{}'".format(_cache_path))
-        _result = self.salt.mkdir(
-            _active_nodes_string,
-            os.path.join(
-                '/root',
-                config.salt_scripts_folder
-            ),
-            tgt_type="compound"
-        )
-        logger_cli.info("-> Running script to all active nodes")
-        _result = self.salt.get_file(
-            _active_nodes_string,
-            _source_path,
-            _target_path,
-            tgt_type="compound"
-        )
-        # execute pkg collecting script
-        logger.debug("Running script to all nodes")
-        # handle results for each node
-        _result = self.salt.cmd(
-            _active_nodes_string,
-            'cmd.run',
-            param='python {}'.format(_target_path),
-            expr_form="compound"
-        )
         for key in self.nodes.keys():
             # due to much data to be passed from salt, it is happening in order
             if key in _result:
diff --git a/cfg_checker/reporter.py b/cfg_checker/reporter.py
index 3b21894..a0eb1b3 100644
--- a/cfg_checker/reporter.py
+++ b/cfg_checker/reporter.py
@@ -156,6 +156,14 @@
         data['counters']['mdl_diff'] = len(data["diffs"].keys())
 
 
+class HTMLNetworkReport(_TMPLBase):
+    tmpl = "network_check_tmpl.j2"
+
+    def _extend_data(self, data):
+
+        return
+
+
 class ReportToFile(object):
     def __init__(self, report, target):
         self.report = report
diff --git a/templates/network_check_tmpl.j2 b/templates/network_check_tmpl.j2
new file mode 100644
index 0000000..553b554
--- /dev/null
+++ b/templates/network_check_tmpl.j2
@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Model Tree Changes</title>
+    <style>
+        body {
+            font-family: Verdana, Geneva, Tahoma, sans-serif;
+            font-size: 90% !important;
+        }
+        .dot_green {
+            float: left;
+            color: green;
+            margin-right: 0.5em;
+            margin-top: 0.2em;
+        }
+        .dot_red {
+            float: left;
+            color: red;
+            margin-right: 0.5em;
+            margin-top: 0.2em;
+        }
+        .dot_empty {
+            float: left;
+            color: darkgray;
+            margin-right: 0.5em;
+            margin-top: 0.2em;
+        }
+        /* Style the tab */
+        .tab {
+            float: left;
+            width: 130px;
+            border: 1px solid #fff;
+            background-color: #efe;
+        }
+
+        /* Style the buttons that are used to open the tab content */
+        .tab button {
+            display: block;
+            background-color: inherit;
+            color: Black;
+            border: none;
+            outline: none;
+            font-family: "Lucida Console", Monaco, monospace;
+            text-align: left;
+            cursor: pointer;
+            transition: 0.3s;
+            font-size: 1.3em;
+            width: 100%;
+            padding: 1px;
+            margin: 1px;
+        }
+
+        button > div.node_name {
+            float: left;
+            font-size: 1.3em;
+        }
+
+        .smallgreytext {
+            float: right;
+            font-size: 0.7em;
+            color: gray;
+        }
+
+        /* Change background color of buttons on hover */
+        .tab button:hover {
+            background-color: #7b7;
+        }
+
+        /* Create an active/current "tab button" class */
+        .tab button.active {
+            background-color: #8c8;
+            color: white;
+        }
+
+        /* Style the tab content */
+        .tabcontent {
+            display: none;
+            position: absolute;
+            font-size: 1em;
+            padding: 0.5em;
+            right: -10%;
+            top: 0%;
+            transform: translateX(-12%);
+            width: calc(100% - 170px);
+            overflow-x: scroll;
+            overflow-wrap: break-word;
+        }
+
+        table {
+            border: 0 hidden;
+            width: 100%;
+        }
+        tr:nth-child(even) {
+            background-color: #fff;
+        }
+        tr:nth-child(odd) {
+            background-color: #ddd;
+        }
+        .Header {
+            background-color: #bbb;
+            color: Black;
+            width: 30%;
+            text-align: center;
+        }
+        .param {
+            font-size: 0.8em;
+            color: #555;
+            padding-left: 50px;
+            padding-right: 10px;
+        }
+        .class_file {
+            font-size: 0.8em;
+            font-weight: bold;
+            min-width: 300px;
+            text-align: left;
+            color: black;
+        }
+
+        .pkgName {
+            font-size: 1em;
+            padding-left: 10px;
+            max-width: 800px;
+        }
+
+        .version {
+            font-size: 1em;
+            text-align: left;
+            max-width: 400px;
+            overflow-wrap: break-word;
+        }
+
+        .differ {
+            background-color: #eaa;
+        }
+        /* Tooltip container */
+        .tooltip {
+            position: relative;
+            display: inline-block;
+            border-bottom: 1px dotted black;
+        }
+
+        .tooltip .tooltiptext {
+            visibility: hidden;
+            background-color: black;
+            font-family: "Lucida Console", Monaco, monospace;
+            font-size: 0.5em;
+            width: auto;
+            color: #fff;
+            border-radius: 6px;
+            padding: 5px 5px;
+
+            /* Position the tooltip */
+            position: absolute;
+            z-index: 1;
+        }
+
+        .tooltip:hover .tooltiptext {
+            visibility: visible;
+        }
+
+    </style>
+    <script language="JavaScript">
+        function init() {
+            // Declare all variables
+            var i, tabcontent, tablinks;
+
+            // Get all elements with class="tabcontent" and hide them
+            tabcontent = document.getElementsByClassName("tabcontent");
+            for (i = 1; i < tabcontent.length; i++) {
+                tabcontent[i].style.display = "none";
+            }
+            tabcontent[0].style.display = "block";
+
+            // Get all elements with class="tablinks" and remove the class "active"
+            tablinks = document.getElementsByClassName("tablinks");
+            for (i = 1; i < tablinks.length; i++) {
+                tablinks[i].className = tablinks[i].className.replace(" active", "");
+            }
+            tablinks[0].className += " active";
+
+        }
+        function openTab(evt, tabName) {
+            // Declare all variables
+            var i, tabcontent, tablinks;
+
+            // Get all elements with class="tabcontent" and hide them
+            tabcontent = document.getElementsByClassName("tabcontent");
+            for (i = 0; i < tabcontent.length; i++) {
+                tabcontent[i].style.display = "none";
+            }
+
+            // Get all elements with class="tablinks" and remove the class "active"
+            tablinks = document.getElementsByClassName("tablinks");
+            for (i = 0; i < tablinks.length; i++) {
+                tablinks[i].className = tablinks[i].className.replace(" active", "");
+            }
+
+            // Show the current tab, and add an "active" class to the link that opened the tab
+            document.getElementById(tabName).style.display = "block";
+            evt.currentTarget.className += " active";
+        }
+    </script>
+</head>
+<body onload="init()">
+
+</body>
+</html>
\ No newline at end of file