diff service/wifi/src/index.ts @ 1476:817da2dc80fc

kind of running with lit-element and polymer together. lots of data missing from table still Ignore-this: db24e7b633929b01430b0794c1a065dc darcs-hash:cdb1d499a42b869d6affa3ee226f408358a59cf4
author drewp <drewp@bigasterisk.com>
date Sun, 05 Jan 2020 23:18:27 -0800
parents 2e598cbcabf4
children 4bd77a9548d6
line wrap: on
line diff
--- a/service/wifi/src/index.ts	Thu Jan 02 00:23:36 2020 -0800
+++ b/service/wifi/src/index.ts	Sun Jan 05 23:18:27 2020 -0800
@@ -1,37 +1,281 @@
-import { PolymerElement, html } from "@polymer/polymer";
-import {
-  customElement,
-  property,
-  computed,
-  observe,
-} from "@polymer/decorators";
-import { N3Store } from "n3";
-//import {* as wt} from './wifi-table';
-import { VersionedGraph, StreamedGraph } from "streamed-graph";
+// for the web page
 export { DomBind } from "@polymer/polymer/lib/elements/dom-bind.js";
 
-console.log("here is a real dependency on ", StreamedGraph.name);
+import { LitElement, property, html, customElement } from "lit-element";
+
+import { Literal, Term, N3Store, Util } from "n3";
+import { NamedNode, DataFactory } from "n3";
+const { literal, quad, namedNode } = DataFactory;
+
+import { VersionedGraph } from "streamed-graph";
+export { StreamedGraph } from "streamed-graph";
+import { style } from "./style";
+
+interface DevGroup {
+  connectedToAp: NamedNode;
+  wifiBand: NamedNode;
+  devs: Array<Dev>;
+}
+interface Dev {
+  agoMin: number | undefined;
+  ipAddress: Literal;
+  dhcpHostname: string;
+  macAddress: Literal;
+  packetsPerSec: string; //number; todo
+  bytesPerSec: string; //number;
+}
+
+// workaround for uris that don't have good labels in the graph
+function labelFromUri(
+  uri: NamedNode,
+  prefix: string,
+  tailsToLabels: any,
+  defaultLabel: string
+) {
+  let label = defaultLabel === undefined ? uri.value : defaultLabel;
+  Object.entries(tailsToLabels).forEach(([tail, useLabel]) => {
+    if (uri.equals(namedNode(prefix + tail))) {
+      label = useLabel as string;
+    }
+  });
+  return label;
+}
 
 @customElement("wifi-display")
-class WifiDisplay extends PolymerElement {
-  @property({ type: Object, observer: WifiDisplay.prototype.onGraphChanged})
+class WifiDisplay extends LitElement {
+  static get styles() {
+    return [style];
+  }
+
+  @property({
+    type: Object,
+    //    observer: WifiDisplay.prototype.onGraphChanged,
+  })
   graph!: VersionedGraph;
 
+  connectedCallback() {
+    super.connectedCallback();
+    console.log("wifidisplay connected");
+    const sg = this.ownerDocument!.querySelector("streamed-graph");
+    sg?.addEventListener("graph-changed", ((ev: CustomEvent) => {
+      this.graph = ev.detail!.value as VersionedGraph;
+      console.log("wifidisplay got new graph", this.graph);
+    }) as EventListener);
+  }
 
-  onGraphChanged() {
-    console.log("new graph", this.graph);
+  static get observers() {
+    return ["onGraphChanged(graph)"];
+  }
+
+  @property({ type: Object }) //no longer a prop?
+  grouped: Map<string, DevGroup> = new Map();
+
+  render() {
+    const grouped = this.graphView(this.graph.store!, false);
+
+    return html`
+      <div class="report">
+        report at graph version ${this.graph.version} grouped:
+        ${Array.from(grouped.entries()).length}
+        <table>
+          ${Array.from(grouped.entries()).map((row: [string, DevGroup]) => {
+            return this.renderGroup(row[0], row[1]);
+          })}
+        </table>
+      </div>
+    `;
   }
 
-  ready() {
-    super.ready();
+  onGraphChanged(val: VersionedGraph, old: VersionedGraph) {
+    console.log("new graph value", this.graph);
+    this.grouped = this.graphView((val as VersionedGraph).store!, false);
+  }
+
+  renderDevice(dev: Dev) {
+    let agoReport = "";
+    if (dev.agoMin === undefined) {
+      agoReport = "unknown";
+    } else {
+      const glow = Math.max(0, 1 - dev.agoMin! / 60);
+      agoReport =
+        dev.agoMin! < 360
+          ? ` (${Math.ceil(dev.agoMin! * 10) / 10} minutes ago)`
+          : "";
+    }
+    const glow = ""; //todo
+    return html`
+      <div class="dev" style="background: rgba(185, 5, 138, ${glow});">
+        <span class="mac">${dev.macAddress && dev.macAddress.value}</span>
+        <span class="ip"
+          ><a href="http://${dev.ipAddress && dev.ipAddress.value}/"
+            >${dev.ipAddress && dev.ipAddress.value}</a
+          ></span
+        >
+        <span class="packets">${dev.packetsPerSec}</span>
+        <span class="bytes">${dev.bytesPerSec}</span>
+        <span class="hostname">${dev.dhcpHostname}</span>
+        <span class="ago">${agoReport}</span>
+        <span class="links">
+          <a
+            href="https://bigasterisk.com/ntop/lua/host_details.lua?ifid=17&amp;host=${dev.ipAddress &&
+              dev.ipAddress.value}&amp;page=flows"
+            >[flows]</a
+          >
+        </span>
+      </div>
+    `;
   }
 
-  // redraw() {
-  //     wt.render(this.graph.graph);
-  // }
-  static get template() {
+  renderGroup(key: string, group: DevGroup) {
+    let label;
+    if (key != "all") {
+      label = labelFromUri(
+        group.connectedToAp,
+        "http://bigasterisk.com/mac/",
+        {
+          "a0:40:a0:6f:96:d5": "Main router (d5)",
+          "8c:3b:ad:c4:8d:ce": "Downstairs satellite (ce)",
+          "a0:40:a0:6f:aa:f8": "Upstairs satellite (f8)",
+        },
+        "unknown"
+      );
+
+      label += labelFromUri(
+        group.wifiBand,
+        "http://projects.bigasterisk.com/room/wifiBand/",
+        {
+          "5G": " 5G",
+          "2.4G": " 2.4G",
+        },
+        "unknown"
+      );
+    }
+
+    const devs = group.devs;
+    function padIp(ip: string) {
+      return ip.replace(/(\d+)/g, m => ("00" + m).slice(-3));
+    }
+    devs.sort((a, b) => {
+      return 0; //todo
+      return padIp(a.ipAddress.value) > padIp(b.ipAddress.value) ? 1 : -1;
+    });
     return html`
-      here
+      <tr>
+        <th>${label}</th>
+        <td>
+          <div>Devices:</div>
+          ${devs.map(d => {
+            return this.renderDevice(d);
+          })}
+        </td>
+      </tr>
     `;
   }
+
+  graphView(store: N3Store, showGroups: boolean): Map<string, DevGroup> {
+    const grouped: Map<string, DevGroup> = new Map();
+    const room = "http://projects.bigasterisk.com/room/";
+    store.forEach(
+      q => {
+        const devUri: NamedNode = q.subject as NamedNode;
+
+        const graphLiteral = (
+          store: N3Store,
+          devUri: NamedNode,
+          pred: string,
+          notFoundResult?: string
+        ): Literal => {
+          const keep: Array<Literal> = [];
+          store.forEach(
+            q => {
+              if (!Util.isLiteral(q.object)) {
+                throw new Error("non literal found");
+              }
+              keep.push(q.object as Literal);
+            },
+            devUri,
+            namedNode(pred),
+            null,
+            null
+          );
+          if (keep.length == 0) {
+            return literal(notFoundResult || "(missing)");
+          }
+          if (keep.length == 1) {
+            return keep[0];
+          }
+          throw new Error("found multiple matches for pred");
+        };
+
+        const getAgoMin = (
+          connected: Literal | undefined
+        ): number | undefined => {
+          if (connected) {
+            const t = new Date(connected.value);
+            const agoMs = Date.now() - t.valueOf();
+            return agoMs / 1000 / 60;
+          }
+          return undefined;
+        };
+
+        const asString = (x: Literal | undefined): string => {
+          if (x && x.value) {
+            return x.value;
+          }
+          return "(unknown)";
+        };
+        const row: Dev = {
+          agoMin: getAgoMin(
+            literal("2020-01-01") //  graphLiteral(store, devUri, room + "connectedToAp")
+          ),
+          ipAddress: graphLiteral(
+            store,
+            devUri,
+            room + "ipAddress",
+            "(unknown)"
+          ),
+          dhcpHostname: asString(
+            graphLiteral(store, devUri, room + "dhcpHostname")
+          ),
+          macAddress: graphLiteral(store, devUri, room + "macAddress"),
+          packetsPerSec:
+            graphLiteral(store, devUri, room + "packetsPerSec", "? ").value +
+            " P/s", //number; todo
+          bytesPerSec:
+            graphLiteral(store, devUri, room + "bytesPerSec", "? ").value +
+            "B/s", //number;
+        };
+        if (row.dhcpHostname && (row.dhcpHostname as any).value) {
+          row.dhcpHostname = (row.dhcpHostname as any).value;
+        }
+        if (!showGroups || (row as any).connectedToAp) {
+          const key = showGroups
+            ? `${(row as any).connectedToAp.toNT()}-${(row as any).wifiBand.toNT()}`
+            : "all";
+          if (!grouped.has(key)) {
+            grouped.set(key, {
+              connectedToAp: (row as any).connectedToAp,
+              wifiBand: (row as any).wifiBand,
+              devs: [],
+            });
+          }
+          grouped.get(key)!.devs.push(row);
+        } else {
+          console.log("lost row", row);
+        }
+
+        if (row.bytesPerSec) {
+          row.bytesPerSec = row.bytesPerSec.valueOf() + " B/s";
+        }
+        if (row.packetsPerSec) {
+          row.packetsPerSec = row.packetsPerSec.valueOf() + " p/s";
+        }
+      },
+      null,
+      namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
+      namedNode(room + "NetworkedDevice"),
+      null
+    );
+    return grouped;
+  }
 }