Implement service graph; add service graph to service grid; add add-service to service grid
diff --git a/xos/core/static/primarycons_blue/plus.png b/xos/core/static/primarycons_blue/plus.png
new file mode 100644
index 0000000..a00ab89
--- /dev/null
+++ b/xos/core/static/primarycons_blue/plus.png
Binary files differ
diff --git a/xos/core/views/services.py b/xos/core/views/services.py
index 76180e7..6f24609 100644
--- a/xos/core/views/services.py
+++ b/xos/core/views/services.py
@@ -6,6 +6,7 @@
 import json
 import os
 import time
+import tempfile
 
 class ServiceGridView(TemplateView):
     head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
@@ -21,11 +22,8 @@
 
         html = '<table><tr>'
 
-        i=0
+        icons=[]
         for service in Service.objects.all():
-            if (i%4) == 0:
-                html = html + '</tr><tr>'
-
             view_url = service.view_url
             if (not view_url):
                 view_url = "/admin/core/service/$id$/"
@@ -35,8 +33,26 @@
             if (not image_url):
                 image_url = "/static/primarycons_blue/gear_2.png"
 
+            icons.append( {"name": service.name, "view_url": view_url, "image_url": image_url} )
+
+        icons.append( {"name": "Tenancy Graph", "view_url": "/serviceGraph.png", "image_url": "/static/primarycons_blue/service_graph.png", "horiz_rule": True} )
+        icons.append( {"name": "Add Service", "view_url": "/admin/core/service/add/", "image_url": "/static/primarycons_blue/plus.png"} )
+
+        i=0
+        for icon in icons:
+            if icon.get("horiz_rule", False):
+                html = html + "</tr><tr><td colspan=4><hr></td></tr><tr>"
+                i=0
+
+            service_name = icon["name"]
+            view_url = icon["view_url"]
+            image_url = icon["image_url"]
+
+            if (i%4) == 0:
+                html = html + '</tr><tr>'
+
             html = html + '<td width=96 height=128 valign=top align=center><a href="%s"><img src="%s" height=64 width=64></img></a>' % (view_url, image_url)
-            html = html + '<p><a href="%s">%s</a></p></td>' % (view_url, service.name)
+            html = html + '<p><a href="%s">%s</a></p></td>' % (view_url, service_name)
             i=i+1
 
         html = html + '</tr></table>'
@@ -50,4 +66,76 @@
             template = t,
             **response_kwargs)
 
+class ServiceGraphViewOld(TemplateView):
+    #  this attempt used networkx
+    # yum -y install python-matplotlib python-networkx
+    # pip-python install -upgrade networkx
+    # pip-python install graphviz pygraphviz
 
+    def get(self, request, name="root", *args, **kwargs):
+        import networkx as nx
+        import matplotlib as mpl
+        mpl.use("Agg")
+        import matplotlib.pyplot as plt
+        import nxedges
+
+        plt.figure(figsize=(10,8))
+
+        g = nx.DiGraph()
+
+        labels = {}
+        for service in Service.objects.all():
+            g.add_node(service.id)
+            if len(service.name)>8:
+                labels[service.id] = service.name[:8] + "\n" + service.name[8:]
+            else:
+                labels[service.id] = service.name
+
+        for tenant in CoarseTenant.objects.all():
+            if (not tenant.provider_service) or (not tenant.subscriber_service):
+                continue
+            g.add_edge(tenant.subscriber_service.id, tenant.provider_service.id)
+
+        pos = nx.graphviz_layout(g)
+        nxedges.xos_draw_networkx_edges(g,pos,arrow_len=30)
+        nx.draw_networkx_nodes(g,pos,node_size=5000)
+        nx.draw_networkx_labels(g,pos,labels,font_size=12)
+        #plt.axis('off')
+        plt.savefig("/tmp/foo.png")
+
+        return HttpResponse(open("/tmp/foo.png","r").read(), content_type="image/png")
+
+class ServiceGraphView(TemplateView):
+    # this attempt just uses graphviz directly
+    # yum -y install graphviz
+    # pip-python install pygraphviz
+
+    def get(self, request, name="root", *args, **kwargs):
+        import pygraphviz as pgv
+
+        g = pgv.AGraph(directed=True)
+        g.graph_attr.update(size="8,4!")
+        g.graph_attr.update(dpi="100")
+        #g.graph_attr.update(nodesep="2.5")
+        g.graph_attr.update(overlap="false")
+        g.graph_attr.update(graphdir="TB")
+
+        for service in Service.objects.all():
+            provided_tenants = CoarseTenant.get_tenant_objects().filter(provider_service=service)
+            subscribed_tenants = CoarseTenant.get_tenant_objects().filter(subscriber_service=service)
+            if not (provided_tenants or subscribed_tenants):
+               # nodes with no edges aren't interesting
+               continue
+            g.add_node(service.id, label=service.name)
+
+        for tenant in CoarseTenant.get_tenant_objects().all():
+            if (not tenant.provider_service) or (not tenant.subscriber_service):
+                continue
+            g.add_edge(tenant.subscriber_service.id, tenant.provider_service.id)
+
+        tf = tempfile.TemporaryFile()
+        g.layout(prog="dot")
+        g.draw(path=tf, format="png")
+        tf.seek(0)
+
+        return HttpResponse(tf.read(), content_type="image/png")
diff --git a/xos/xos/urls.py b/xos/xos/urls.py
index 7f7f5bc..0adf32d 100644
--- a/xos/xos/urls.py
+++ b/xos/xos/urls.py
@@ -7,7 +7,7 @@
 from xosapi import *
 
 from core.views.legacyapi import LegacyXMLRPC
-from core.views.services import ServiceGridView
+from core.views.services import ServiceGridView, ServiceGraphView
 #from core.views.analytics import AnalyticsAjaxView
 from core.models import *
 from rest_framework import generics
@@ -28,6 +28,7 @@
     url(r'^stats', 'core.views.stats.Stats', name='stats'),
     url(r'^observer', 'core.views.observer.Observer', name='observer'),
     url(r'^serviceGrid', ServiceGridView.as_view(), name='serviceGrid'),
+    url(r'^serviceGraph.png', ServiceGraphView.as_view(), name='serviceGraph'),
     url(r'^hpcConfig', 'core.views.hpc_config.HpcConfig', name='hpcConfig'),
 
     url(r'^docs/', include('rest_framework_swagger.urls')),