changeset 1412:302063bfb8ff

reasoning web page uses rdf/browse/graphView for inputs and outputs now Ignore-this: 64b7275ee149f631b606320444a3478b darcs-hash:1ec4d67abed3d43997fae5f10d4bc23ee1a6b2d5
author drewp <drewp@bigasterisk.com>
date Wed, 24 Jul 2019 00:36:16 -0700
parents 21d0cd98ef7a
children 14802ffc9e44
files service/reasoning/actions.py service/reasoning/index.html service/reasoning/reasoning.py
diffstat 3 files changed, 244 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/service/reasoning/actions.py	Wed Jul 24 00:34:41 2019 -0700
+++ b/service/reasoning/actions.py	Wed Jul 24 00:36:16 2019 -0700
@@ -19,13 +19,29 @@
         self.payload = None
         self.foafAgent = None
         self.nextCall = None
+        self.lastErr = None
         self.numRequests = 0
 
+    def report(self):
+        return {
+            'url': self.url,
+            'urlAbbrev': self.url
+            .replace('http%3A%2F%2Fprojects.bigasterisk.com%2Froom%2F', ':')
+            .replace('http://projects.bigasterisk.com/room/', ':')
+            .replace('.vpn-home.bigasterisk.com', '.vpn-home'),
+            'payload': self.payload,
+            'numRequests': self.numRequests,
+            'lastChangeTime': round(self.lastChangeTime, 2),
+            'lastErr': str(self.lastErr) if self.lastErr is not None else None,
+            }
+
     def setPayload(self, payload, foafAgent):
-        if self.numRequests > 0 and (self.payload == payload or self.foafAgent == foafAgent):
+        if self.numRequests > 0 and (self.payload == payload and
+                                     self.foafAgent == foafAgent):
             return
         self.payload = payload
         self.foafAgent = foafAgent
+        self.lastChangeTime = time.time()
         self.makeRequest()
 
     def makeRequest(self):
@@ -52,13 +68,13 @@
         log.debug("  PUT %s ok", self.url)
         self.lastErr = None
         self.currentRequest = None
-        self.nextCall = reactor.callLater(3, self.makeRequest)
+        self.nextCall = reactor.callLater(30, self.makeRequest)
 
     def onError(self, err):
         self.lastErr = err
         log.debug('  PUT %s failed: %s', self.url, err)
         self.currentRequest = None
-        self.nextCall = reactor.callLater(5, self.makeRequest)
+        self.nextCall = reactor.callLater(50, self.makeRequest)
 
 class HttpPutOutputs(object):
     """these grow forever"""
@@ -155,6 +171,7 @@
                 obj = deviceGraph.value(defaultDesc, ROOM['defaultObject'])
 
                 defaultStmts.add((s, p, obj))
+                log.debug('defaultStmts %s %s %s', s, p, obj)
         self._putDevices(deviceGraph, defaultStmts)
 
     def _oneShotPostActions(self, deviceGraph, inferred):
@@ -242,3 +259,27 @@
     def _put(self, url, payload, agent=None):
         assert isinstance(payload, bytes)
         self.putOutputs.put(url, payload, agent)
+
+import cyclone.sse
+
+class PutOutputsTable(cyclone.sse.SSEHandler):
+    def __init__(self, application, request):
+        cyclone.sse.SSEHandler.__init__(self, application, request)
+        self.actions = self.settings.reasoning.actions
+
+    def bind(self, *args, **kwargs):
+        self.bound = True
+        self.loop()
+
+    def unbind(self):
+        self.bound = False
+
+    def loop(self):
+        if not self.bound:
+            return
+
+        self.sendEvent(message=json.dumps({
+            'puts': [row.report() for _, row in
+                     sorted(self.actions.putOutputs.state.items())],
+        }), event='update')
+        reactor.callLater(1, self.loop)
--- a/service/reasoning/index.html	Wed Jul 24 00:34:41 2019 -0700
+++ b/service/reasoning/index.html	Wed Jul 24 00:36:16 2019 -0700
@@ -2,143 +2,218 @@
 <html>
   <head>
     <title>reasoning</title>
-    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.js"></script>
+    <meta charset="utf-8" />
+    <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents.min.js"></script>
+    <script src="/lib/require/require-2.3.3.js"></script>
+    <script src="/rdf/common_paths_and_ns.js"></script>
+    <script>
+     window.NS = {
+       room: "http://projects.bigasterisk.com/room/",
+       dev: "http://projects.bigasterisk.com/device/",
+       dcterms: "http://purl.org/dc/terms/",
+       rdfs: "http://www.w3.org/2000/01/rdf-schema#",
+       map: "http://bigasterisk.com/map#",
+       rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+     };
+    </script>
+    <link rel="import" href="/rdf/streamed-graph.html">
+    <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html">
+
+    <meta name="mobile-web-app-capable" content="yes">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
     <style type="text/css" media="all">
      /* <![CDATA[ */
+     @import url('https://fonts.googleapis.com/css?family=Allerta|Dosis|Jura&display=swap');
      body {
-       font-family: sans-serif;
-       font-size: 12px;
+         background: black;
+         color: white;
+         font-family: 'Allerta', sans-serif;
+         font-size: 12px;
+     }
+     a {
+         color: #b1b1fd;
+         text-shadow: 1px 1px 0px #0400ff94;
+         text-decoration-color: #00007714;
+     }
+     #subjectRequest {
+         width: 50em;
+     }
+     .pane > h2 {
+         background: #3f738a61;
+         border-top-left-radius: 23px;
+         border-top-right-radius: 23px;
+         border-top: 3px solid #2a4b58;
+         padding: 14px 0 5px 11px;
+
+         margin-top:  10px;
      }
      pre {
-       font-family: sans-serif;
-     } 
-     pre div {
-       border-bottom: 1px solid #ccc;
+         font-family: sans-serif;
      }
-     .pred {
-       background: #e7e6f8;
-     }
-     .obj {
-       background: #ccf
+     pre div {
+         border-bottom: 1px solid #ccc;
      }
-     .pane {
-       position: relative;
-       display: flex;
-       flex-direction: column;
-     }
-     .pane pre {
-       overflow: auto;
-       flex-grow: 1;
-     }
-     /* ]]> */
+
+
+
+     #out > section { background: #1d23314a; }
+     #out2 > section { background: #4222134a; }
+         /* ]]> */
     </style>
     <link rel="import" href="/supdebug/bang/service-rows/main.html">
 
   </head>
-  <body layout vertical fit>
+  <body>
+
+    <link rel="import" href="/rdf/rdf-uri.html">
+
+
     <h1>reasoning service</h1>
-    <div style="flex: 0 0 auto">
+    <div class="pane">
       <h2>Service</h2>
       <service-rows name-substrs="reasoning"></service-rows>
     </div>
-    
-    <div class="pane">
-      <h2>Input</h2>
-      <div><input id="inputQ"></div>
-      <div style="max-height: 600px; margin-right: 30px; overflow: auto">
-        <pre id="input"></pre>
-      </div>
-    </div>
 
 
     <div  class="pane">
+      <h2>Input</h2>
+
+      <streamed-graph id="inGraph" url="/sse_collector/graph/home"></streamed-graph>
+      <div id="out"></div>
+      <script type="module" src="/rdf/streamed_graph_view.js"></script>
+
+    </div>
+
+    <div  class="pane">
       <h2>Rules</h2>
-      <div style="max-height: 300px; margin-right: 30px; overflow: auto">
+      <div style="">
         <pre id="rules"></pre>
       </div>
+      <label><input id="auto" type="checkbox"> auto refresh</label>
+
+      <script src="//bigasterisk.com/lib/jquery-2.0.3.min.js"></script>
+      <script type="text/javascript">
+       // <![CDATA[
+       $(function () {
+
+         function update() {
+	   $.get("rules", function (txt) {
+	     $("#rules").empty().text(txt);
+	   });
+         }
+         function loop() {
+	   update();
+	   if ($("input#auto").is(":checked")) {
+	     setTimeout(loop, 2000);
+	   }
+         }
+         loop();
+         $("input#auto").click(loop);
+       });
+       // ]]>
+      </script>
     </div>
 
 
     <div  class="pane">
       <h2>Output</h2>
-      <div><input id="outputQ"></div>
-      <div style="max-height: 300px; margin-right: 30px; overflow: auto">
-        <pre id="output"></pre>
-      </div>
-    </div>
+
+      <streamed-graph id="outGraph" url="graph/output/events"></streamed-graph>
+      <div id="out2"></div>
+
+
+      <script type="module">
+       import { render } from '/lib/lit-html/1.0.0/lit-html.js';
+       import { graphView } from '/rdf/browse/graphView.js';
+
+       const sg = document.querySelector('#outGraph');
+
+       const out = document.querySelector('#out2');
+       const startPainting = () => {
+         if (!sg.graph || !sg.graph.graph) {
+           setTimeout(startPainting, 100);
+           return;
+         }
+
+         let dirty = true;
 
-    <div>
-      <label><input id="auto" type="checkbox"> auto refresh</label>
+         const repaint = () => {
+           if (!dirty) {
+             return;
+           }
+           render(graphView(sg.graph.graph), out);
+           dirty = false;
+         };
+
+         sg.addEventListener('graph-changed', (ev) => {
+           dirty = true;
+           requestAnimationFrame(repaint);
+         });
+         repaint();
+       };
+       setTimeout(startPainting, 10);
+
+      </script>
+
+
     </div>
 
-    <script src="//bigasterisk.com/lib/jquery-2.0.3.min.js"></script>
-    <script>
-     window.NS = {
-       room: "http://projects.bigasterisk.com/room/", 
-       dev: "http://projects.bigasterisk.com/device/", 
-       dcterms: "http://purl.org/dc/terms/", 
-       rdfs: "http://www.w3.org/2000/01/rdf-schema#", 
-       map: "http://bigasterisk.com/map#", 
-       rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 
-     };
-    </script>
-    <link rel="import" href="/rdf/rdf-uri.html">
-    <script type="text/javascript">
-     // <![CDATA[
-     $(function () { 
+    <div  class="pane">
 
-       function makeAddStmts(elem, q) {
-	 return function (stmts) {
-	   elem.empty();
-	   $.each(stmts, function (i, stmt) {
-             var s = BigastUri.compactUri(stmt[0]);
-             var p = BigastUri.compactUri(stmt[1]);
-             var o = BigastUri.compactUri(stmt[2]);
-
-             q = q.toLowerCase()
-             if (q &&
-                 s.toLowerCase().indexOf(q) == -1 &&
-                 p.toLowerCase().indexOf(q) == -1 &&
-                 o.toLowerCase().indexOf(q) == -1) {
-               return;
+      <h2>put outputs</h2>
+      <style>
+       .recent2  { background: #ffff55; }
+       .recent10 { background: #ffff88; }
+       .recent60 { background: #ffffdd; }
+       #putOutputs th, #putOutputs td { text-align: left; padding-left: 5px; }
+      </style>
+      <table id="putOutputs">
+        <thead>
+          <tr>
+            <th>url</th>
+            <th>numReq</th>
+            <th>changed</th>
+            <th>payload</th>
+            <th>lastErr</th>
+          </tr>
+        </thead>
+        <tbody id="putRows">
+        </tbody>
+      </table>
+      <script>
+       window.addEventListener('load', () => {
+         const es = new EventSource('putOutputs');
+         es.addEventListener('update', (ev) => {
+           const rows = document.querySelector('#putRows');
+           rows.innerHTML = '';
+           JSON.parse(ev.data).puts.forEach((row) => {
+             const tr = document.createElement('tr');
+             for (let attr of [
+               'urlAbbrev',
+               'numRequests',
+               'lastChangeTime',
+               'payload',
+               'lastErr',
+             ]) {
+               const td = document.createElement('td');
+               let value = row[attr];
+               if (attr == 'lastChangeTime') {
+                 const secAgo = Math.round(Date.now() / 1000 - row.lastChangeTime);
+                 value = `-${secAgo} sec`;
+                 if (secAgo < 2) { tr.classList.add('recent2'); }
+                 else if (secAgo < 10) { tr.classList.add('recent10'); }
+                 else if (secAgo < 60) { tr.classList.add('recent60'); }
+               }
+               td.innerText = value;
+               tr.appendChild(td);
              }
-             
-	     elem.append(
-               $("<div>")
-                  .append($("<span>").text(s))
-                  .append(" ")
-                  .append($("<span>").addClass("pred").text(p))
-                  .append(" ")
-                  .append($("<span>").addClass("obj").text(o)));
-	   })
-	 }
-       }
+             rows.appendChild(tr);
+           });
+         });
+       });
+      </script>
+        </div>
 
-       function updateIn() {
-         $.get("lastInputGraph", makeAddStmts($("#input"), $("#inputQ").val()));
-       }
-       function updateOut() {
-         $.get("lastOutputGraph", makeAddStmts($("#output"), $("#outputQ").val()));
-       }
-       
-       function update() {
-	 updateIn();
-         updateOut();
-	 $.get("rules", function (txt) { 
-	   $("#rules").empty().text(txt);
-	 });
-       }
-       $("#inputQ").on('keyup', updateIn);
-       function loop() {
-	 update();
-	 if ($("input#auto").is(":checked")) {
-	   setTimeout(loop, 2000);
-	 }
-       }
-       loop();
-       $("input#auto").click(loop);
-     });
-     // ]]>
-    </script>
   </body>
 </html>
--- a/service/reasoning/reasoning.py	Wed Jul 24 00:34:41 2019 -0700
+++ b/service/reasoning/reasoning.py	Wed Jul 24 00:36:16 2019 -0700
@@ -31,11 +31,12 @@
 from greplin.scales.cyclonehandler import StatsHandler
 
 from inference import infer, readRules
-from actions import Actions
+from actions import Actions, PutOutputsTable
 from inputgraph import InputGraph
 from escapeoutputstatements import unquoteOutputStatements
- 
+
 from standardservice.logsetup import log, verboseLogging
+from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
 
 
 ROOM = Namespace("http://projects.bigasterisk.com/room/")
@@ -56,8 +57,9 @@
 
         self.rulesN3 = "(not read yet)"
         self.inferred = Graph() # gets replaced in each graphChanged call
+        self.outputGraph = PatchableGraph() # copy of inferred, for now
 
-        self.inputGraph = InputGraph([], self.graphChanged)      
+        self.inputGraph = InputGraph([], self.graphChanged)
         self.inputGraph.updateFileData()
 
     @STATS.updateRules.time()
@@ -81,6 +83,7 @@
             self.inferred = Graph()
             self.inferred.add((ROOM['reasoner'], ROOM['ruleParseError'],
                                Literal(traceback.format_exc())))
+            self.copyOutput()
             raise
         return [(ROOM['reasoner'], ROOM['ruleParseTime'],
                  Literal(ruleParseTime))], ruleParseTime
@@ -127,6 +130,11 @@
                   ruleParseSec * 1000,
                   inferSec * 1000,
                   putResultsTime * 1000))
+        if not oneShot:
+            self.copyOutput()
+
+    def copyOutput(self):
+        self.outputGraph.setToGraph((s,p,o,ROOM['inferred']) for s,p,o in self.inferred)
 
     def _makeInferred(self, inputGraph):
         t1 = time.time()
@@ -141,7 +149,7 @@
         return out, inferenceTime
 
 
-        
+
 class Index(cyclone.web.RequestHandler):
     def get(self):
         self.set_header("Content-Type", "text/html")
@@ -185,7 +193,7 @@
             traceback.print_exc()
             log.error(e)
             raise
-            
+
 # for reuse
 class GraphResource(cyclone.web.RequestHandler):
     def get(self, which):
@@ -257,8 +265,13 @@
             (r"/", Index),
             (r"/immediateUpdate", ImmediateUpdate),
             (r"/oneShot", OneShot),
+            (r'/putOutputs', PutOutputsTable),
             (r'/(jquery.min.js)', Static),
             (r'/(lastInput|lastOutput)Graph', GraphResource),
+
+            (r"/graph/output", CycloneGraphHandler, {'masterGraph': reasoning.outputGraph}),
+            (r"/graph/output/events", CycloneGraphEventsHandler, {'masterGraph': reasoning.outputGraph}),
+
             (r'/ntGraphs', NtGraphs),
             (r'/rules', Rules),
             (r'/status', Status),
@@ -269,7 +282,7 @@
 
 def configLogging(arg):
     log.setLevel(WARN)
-    
+
     if arg['-i'] or arg['-r'] or arg['-o'] or arg['-v']:
         log.handlers[0].setFormatter(ColoredFormatter("%(log_color)s%(levelname)-8s %(name)-6s %(filename)-12s:%(lineno)-3s %(funcName)-20s%(reset)s %(white)s%(message)s",
         datefmt=None,