changeset 2037:b731eaad3adf

deprecate light9/web/ - use light9/{service}/ with web/ subdir if needed
author drewp@bigasterisk.com
date Sat, 09 Apr 2022 11:52:50 -0700
parents 00afa5ec081a
children 5154f5a23e85
files bin/homepage light9/homepage/ServiceButtonRow.ts light9/homepage/StatsLine.ts light9/homepage/StatsProcess.ts light9/homepage/index.html light9/homepage/vite.config.ts light9/web/homepage/ServiceButtonRow.ts light9/web/homepage/StatsLine.ts light9/web/homepage/StatsProcess.ts light9/web/homepage/index.html light9/web/homepage/vite.config.ts
diffstat 11 files changed, 467 insertions(+), 468 deletions(-) [+]
line wrap: on
line diff
--- a/bin/homepage	Sat Apr 09 11:44:57 2022 -0700
+++ b/bin/homepage	Sat Apr 09 11:52:50 2022 -0700
@@ -33,6 +33,6 @@
 }
 EOF
 head -1 /tmp/light9_nginx_routes.conf
-pnpx vite -c light9/web/homepage/vite.config.ts &
+pnpx vite -c light9/homepage/vite.config.ts &
 /usr/sbin/nginx -c $CONF
 wait
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/homepage/ServiceButtonRow.ts	Sat Apr 09 11:52:50 2022 -0700
@@ -0,0 +1,63 @@
+import { LitElement, html, css } from "lit";
+import { customElement, property } from "lit/decorators.js";
+export { StatsLine } from "./StatsLine";
+
+@customElement("service-button-row")
+export class ServiceButtonRow extends LitElement {
+  @property() name: string = "?";
+  static styles = [
+    css`
+      :host {
+        padding-bottom: 10px;
+      }
+      a {
+        color: #7d7dec;
+      }
+      div {
+        display: flex;
+        justify-content: space-between;
+        padding: 2px 3px;
+      }
+      .left {
+        display: inline-block;
+        margin-right: 3px;
+        flex-grow: 1;
+      }
+      .window {
+      }
+      .serviceGrid > td {
+        border: 5px solid red;
+        display: inline-block;
+      }
+      .big {
+        font-size: 120%;
+        display: inline-block;
+        padding: 10px 0;
+      }
+
+      :host > div {
+        display: inline-block;
+        vertical-align: top;
+      }
+      :host > div:nth-child(2) {
+        width: 9em;
+      }
+    `,
+  ];
+
+  render() {
+    return html`
+      <div>
+        <div class="left"><a class="big" href="${this.name}/">${this.name}</a></div>
+        <div class="window"><button @click="${this.click}">window</button></div>
+        <div><a href="${this.name}/metrics">metrics</a></div>
+      </div>
+
+      <div id="stats"><stats-line name="${this.name}"></div>
+      `;
+  }
+
+  click() {
+    window.open(this.name + "/", "_blank", "scrollbars=1,resizable=1,titlebar=0,location=0");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/homepage/StatsLine.ts	Sat Apr 09 11:52:50 2022 -0700
@@ -0,0 +1,263 @@
+import { css, html, LitElement, TemplateResult } from "lit";
+import { customElement, property } from "lit/decorators.js";
+export { StatsProcess } from "./StatsProcess";
+import parsePrometheusTextFormat from "parse-prometheus-text-format";
+
+interface Value {
+  labels: { string: string };
+  value: string;
+}
+interface Metric {
+  name: string;
+  help: string;
+  type: "GAUGE" | "SUMMARY" | "COUNTER";
+  metrics: Value[];
+}
+type Metrics = Metric[];
+
+@customElement("stats-line")
+export class StatsLine extends LitElement {
+  @property() name = "?";
+  @property() stats: Metrics = [];
+
+  prevCpuNow = 0;
+  prevCpuTotal = 0;
+  @property() cpu = 0;
+  @property() mem = 0;
+
+  updated(changedProperties: any) {
+    changedProperties.forEach((oldValue: any, propName: string) => {
+      if (propName == "name") {
+        const reload = () => {
+          fetch(this.name + "/metrics").then((resp) => {
+            if (resp.ok) {
+              resp
+                .text()
+                .then((msg) => {
+                  this.stats = parsePrometheusTextFormat(msg) as Metrics;
+                  this.extractProcessStats(this.stats);
+                  setTimeout(reload, 1000);
+                })
+                .catch((err) => {
+                  setTimeout(reload, 1000);
+                });
+            } else {
+              if (resp.status == 502) {
+                setTimeout(reload, 5000);
+              }
+              // 404: likely not mapped to a responding server
+            }
+          });
+        };
+        reload();
+      }
+    });
+  }
+  extractProcessStats(stats: Metrics) {
+    stats.forEach((row) => {
+      if (row.name == "process_resident_memory_bytes") {
+        this.mem = parseFloat(row.metrics[0].value) / 1024 / 1024;
+      }
+      if (row.name == "process_cpu_seconds_total") {
+        const now = Date.now() / 1000;
+        const cpuSecondsTotal = parseFloat(row.metrics[0].value);
+        this.cpu = (cpuSecondsTotal - this.prevCpuTotal) / (now - this.prevCpuNow);
+        this.prevCpuTotal = cpuSecondsTotal;
+        this.prevCpuNow = now;
+      }
+    });
+  }
+
+  static styles = [
+    css`
+      :host {
+        border: 2px solid #46a79f;
+        display: inline-block;
+      }
+      table {
+        border-collapse: collapse;
+        background: #000;
+        color: #ccc;
+        font-family: sans-serif;
+      }
+      th,
+      td {
+        outline: 1px solid #000;
+      }
+      th {
+        padding: 2px 4px;
+        background: #2f2f2f;
+      }
+      td {
+        padding: 0;
+        vertical-align: top;
+        text-align: center;
+      }
+      td.val {
+        padding: 2px 4px;
+        background: #3b5651;
+      }
+      .recents {
+        display: flex;
+        align-items: flex-end;
+        height: 30px;
+      }
+      .recents > div {
+        width: 3px;
+        background: red;
+        border-right: 1px solid black;
+      }
+      .bigInt {
+        min-width: 6em;
+      }
+    `,
+  ];
+
+  render() {
+    const now = Date.now() / 1000;
+
+    const table = (d: Metrics, path: string[]): TemplateResult => {
+      const byName = new Map<string, Metric>();
+      d.forEach((row) => {
+        byName.set(row.name, row);
+      });
+      let cols = d.map((row) => row.name);
+      cols.sort();
+
+      if (path.length == 0) {
+        ["webServer", "process"].forEach((earlyKey) => {
+          let i = cols.indexOf(earlyKey);
+          if (i != -1) {
+            cols = [earlyKey].concat(cols.slice(0, i), cols.slice(i + 1));
+          }
+        });
+      }
+
+      const th = (col: string): TemplateResult => {
+        return html`<th>${col}</th>`;
+      };
+      const td = (col: string): TemplateResult => {
+        const cell = byName.get(col)!;
+        return html`${drawLevel(cell, path.concat(col))}`;
+      };
+      return html` <table>
+        <tr>
+          ${cols.map(th)}
+        </tr>
+        <tr>
+          ${cols.map(td)}
+        </tr>
+      </table>`;
+    };
+
+    const tdWrap = (content: TemplateResult): TemplateResult => {
+      return html`<td>${content}</td>`;
+    };
+
+    const recents = (d: any, path: string[]) => {
+      const hi = Math.max.apply(null, d.recents);
+      const scl = 30 / hi;
+
+      const bar = (y: number) => {
+        let color;
+        if (y < d.average) {
+          color = "#6a6aff";
+        } else {
+          color = "#d09e4c";
+        }
+        return html`<div class="bar" style="height: ${y * scl}px; background: ${color};"></div>`;
+      };
+      return html`<td>
+        <div class="recents">${d.recents.map(bar)}</div>
+        <div>avg=${rounding(d.average, 3)}</div>
+      </td>`;
+    };
+
+    const pmf = (d: Metrics, path: string[]) => {
+      return tdWrap(
+        table(
+          {
+            count: d.count,
+            "values [ms]": html`
+              <div>mean=${rounding(d.mean * 1000, 3)}</div>
+              <div>sd=${rounding(d.stddev * 1000, 3)}</div>
+              <div>99=${rounding(d["99percentile"] * 1000, 3)}</div>
+            `,
+          },
+          path
+        )
+      );
+    };
+
+    const drawLevel = (d: Metric, path: string[]) => {
+      if (path.length == 1 && path[0] === "process") {
+        const elem = this.shadowRoot!.querySelector("#proc");
+        if (elem) {
+          (elem as StatsProcess).data = d;
+        }
+        return html`<stats-process id="proc"></stats-process>`;
+      }
+      if (typeof d === "object") {
+        if (d.strings) {
+          //} instanceof TemplateResult) {
+          return html`<td class="val">${d}</td>`;
+        } else if (d.count !== undefined && d.min !== undefined) {
+          return pmf(d, path);
+        } else if (d.average !== undefined && d.recents !== undefined) {
+          return recents(d, path);
+        } else {
+          return tdWrap(table(d, path));
+        }
+      } else {
+        return html`<td class="val bigInt">${d}</td>`;
+      }
+    };
+
+    const nonBoring = (m: Metric) => {
+      return (
+        !m.name.endsWith("_created") && //
+        !m.name.startsWith("python_gc_") &&
+        m.name != "python_info" &&
+        m.name != "process_max_fds" &&
+        m.name != "process_virtual_memory_bytes" &&
+        m.name != "process_resident_memory_bytes" &&
+        m.name != "process_start_time_seconds" &&
+        m.name != "process_cpu_seconds_total"
+      );
+    };
+
+    const displayedStats = this.stats.filter(nonBoring);
+    return html`
+      <div>
+        <table>
+          ${displayedStats.map(
+            (row, rowNum) => html`
+              <tr>
+                <th>${row.name}</th>
+                <td>
+                  <table>
+                    ${row.metrics.map(
+                      (v) => html`
+                        <tr>
+                          <td>${JSON.stringify(v.labels)}</td>
+                          <td>${v.value}</td>
+                        </tr>
+                      `
+                    )}
+                  </table>
+                </td>
+                ${rowNum == 0
+                  ? html`
+                      <td rowspan="${displayedStats.length}">
+                        <stats-process id="proc" cpu="${this.cpu}" mem="${this.mem}"></stats-process>
+                      </td>
+                    `
+                  : ""}
+              </tr>
+            `
+          )}
+        </table>
+      </div>
+    `;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/homepage/StatsProcess.ts	Sat Apr 09 11:52:50 2022 -0700
@@ -0,0 +1,90 @@
+import { LitElement, html, css } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import debug from "debug";
+
+const log = debug("process");
+
+const remap = (x: number, lo: number, hi: number, outLo: number, outHi: number) => {
+  return outLo + (outHi - outLo) * Math.max(0, Math.min(1, (x - lo) / (hi - lo)));
+};
+
+@customElement("stats-process")
+export class StatsProcess extends LitElement {
+  // inspired by https://codepen.io/qiruiyin/pen/qOopQx
+  @property() cpu = 0; // process_cpu_seconds_total
+  @property() mem = 0; // process_resident_memory_bytes
+
+  w = 64;
+  h = 64;
+  revs = 0;
+  prev = 0;
+  canvas?: HTMLCanvasElement;
+  ctx?: CanvasRenderingContext2D;
+  connectedCallback() {
+    super.connectedCallback();
+    this.initCanvas(this.shadowRoot!.firstElementChild as HTMLCanvasElement);
+    this.prev = Date.now() / 1000;
+
+    var animate = () => {
+      requestAnimationFrame(animate);
+      this.redraw();
+    };
+    animate();
+  }
+  initCanvas(canvas: HTMLCanvasElement) {
+    if (!canvas) {
+      return;
+    }
+    this.canvas = canvas;
+    this.ctx = this.canvas.getContext("2d")!;
+
+    this.canvas.width = this.w;
+    this.canvas.height = this.h;
+  }
+  redraw() {
+    if (!this.ctx) {
+      this.initCanvas(this.shadowRoot!.firstElementChild as HTMLCanvasElement);
+    }
+    if (!this.ctx) return;
+
+    this.canvas!.setAttribute("title", 
+    `cpu ${new Number(this.cpu).toPrecision(3)}% mem ${new Number(this.mem).toPrecision(3)}MB`);
+
+    const now = Date.now() / 1000;
+    const ctx = this.ctx;
+    ctx.beginPath();
+    // wrong type of fade- never goes to 0
+    ctx.fillStyle = "#00000003";
+    ctx.fillRect(0, 0, this.w, this.h);
+    const dt = now - this.prev;
+    this.prev = now;
+
+    const size = remap(this.mem.valueOf() / 1024 / 1024, /*in*/ 20, 600, /*out*/ 3, 30);
+    this.revs += dt * remap(this.cpu.valueOf(), /*in*/ 0, 100, /*out*/ 4, 120);
+    const rad = remap(size, /*in*/ 3, 30, /*out*/ 14, 5);
+
+    var x = this.w / 2 + rad * Math.cos(this.revs / 6.28),
+      y = this.h / 2 + rad * Math.sin(this.revs / 6.28);
+
+    ctx.save();
+    ctx.beginPath();
+    ctx.fillStyle = "hsl(194, 100%, 42%)";
+    ctx.arc(x, y, size, 0, 2 * Math.PI);
+    ctx.fill();
+    ctx.restore();
+  }
+
+  static styles = [
+    css`
+      :host {
+        display: inline-block;
+        width: 64px;
+        height: 64px;
+      }
+    `,
+  ];
+
+  render() {
+    return html`<canvas></canvas>`;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/homepage/index.html	Sat Apr 09 11:52:50 2022 -0700
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+  <head>
+    <title>light9 home</title>
+    <meta charset="utf-8" />
+    <link rel="stylesheet" href="@fs/my/proj/light9/light9/web/style.css">
+    <script type="module" src="./ServiceButtonRow.ts"></script>
+  </head>
+  <body>
+    <h1>light9 home page</h1>
+
+    <div style="display: grid">
+      <service-button-row name="rdfdb"></service-button-row>
+      <hr>
+      <service-button-row name="ascoltami"></service-button-row>
+      <hr>
+      <!--  <service-button-row name="picamserve"></service-button-row> -->  
+      <service-button-row name="vidref"></service-button-row>
+      <hr>
+      <service-button-row name="collector"></service-button-row>
+      <service-button-row name="effectSequencer"></service-button-row>
+      <service-button-row name="live"></service-button-row>
+      <service-button-row name="effects"></service-button-row>
+      <service-button-row name="timeline"></service-button-row>
+      <service-button-row name="paint"></service-button-row>
+      <service-button-row name="effectEval"></service-button-row> 
+<!--       <hr>
+      <service-button-row name="subServer"></service-button-row>
+      <service-button-row name="subComposer"></service-button-row>
+      -->
+    </div>
+    
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light9/homepage/vite.config.ts	Sat Apr 09 11:52:50 2022 -0700
@@ -0,0 +1,16 @@
+import { defineConfig } from "vite";
+
+export default defineConfig({
+  root: "./light9/homepage",
+  server: {
+    host: "0.0.0.0",
+    strictPort: true,
+    port: 8300,
+    hmr: {
+      port: 8400,
+    },
+  },
+  define: {
+    global: {},
+  },
+});
--- a/light9/web/homepage/ServiceButtonRow.ts	Sat Apr 09 11:44:57 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-import { LitElement, html, css } from "lit";
-import { customElement, property } from "lit/decorators.js";
-export { StatsLine } from "./StatsLine";
-
-@customElement("service-button-row")
-export class ServiceButtonRow extends LitElement {
-  @property() name: string = "?";
-  static styles = [
-    css`
-      :host {
-        padding-bottom: 10px;
-      }
-      a {
-        color: #7d7dec;
-      }
-      div {
-        display: flex;
-        justify-content: space-between;
-        padding: 2px 3px;
-      }
-      .left {
-        display: inline-block;
-        margin-right: 3px;
-        flex-grow: 1;
-      }
-      .window {
-      }
-      .serviceGrid > td {
-        border: 5px solid red;
-        display: inline-block;
-      }
-      .big {
-        font-size: 120%;
-        display: inline-block;
-        padding: 10px 0;
-      }
-
-      :host > div {
-        display: inline-block;
-        vertical-align: top;
-      }
-      :host > div:nth-child(2) {
-        width: 9em;
-      }
-    `,
-  ];
-
-  render() {
-    return html`
-      <div>
-        <div class="left"><a class="big" href="${this.name}/">${this.name}</a></div>
-        <div class="window"><button @click="${this.click}">window</button></div>
-        <div><a href="${this.name}/metrics">metrics</a></div>
-      </div>
-
-      <div id="stats"><stats-line name="${this.name}"></div>
-      `;
-  }
-
-  click() {
-    window.open(this.name + "/", "_blank", "scrollbars=1,resizable=1,titlebar=0,location=0");
-  }
-}
--- a/light9/web/homepage/StatsLine.ts	Sat Apr 09 11:44:57 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,264 +0,0 @@
-import { css, html, LitElement, TemplateResult } from "lit";
-import { customElement, property } from "lit/decorators.js";
-export { StatsProcess } from "./StatsProcess";
-import parsePrometheusTextFormat from "parse-prometheus-text-format";
-
-interface Value {
-  labels: { string: string };
-  value: string;
-}
-interface Metric {
-  name: string;
-  help: string;
-  type: "GAUGE" | "SUMMARY" | "COUNTER";
-  metrics: Value[];
-}
-type Metrics = Metric[];
-
-@customElement("stats-line")
-export class StatsLine extends LitElement {
-  @property() name = "?";
-  @property() stats: Metrics = [];
-
-  prevCpuNow = 0;
-  prevCpuTotal = 0;
-  @property() cpu = 0;
-  @property() mem = 0;
-
-  updated(changedProperties: any) {
-    changedProperties.forEach((oldValue: any, propName: string) => {
-      if (propName == "name") {
-        const reload = () => {
-          fetch(this.name + "/metrics").then((resp) => {
-            if (resp.ok) {
-              resp
-                .text()
-                .then((msg) => {
-                  this.stats = parsePrometheusTextFormat(msg) as Metrics;
-                  this.extractProcessStats(this.stats);
-                  setTimeout(reload, 1000);
-                })
-                .catch((err) => {
-                  setTimeout(reload, 1000);
-                });
-            } else {
-              if (resp.status == 502) {
-                setTimeout(reload, 5000);
-              }
-              // 404: likely not mapped to a responding server
-            }
-          });
-        };
-        reload();
-      }
-    });
-  }
-  extractProcessStats(stats: Metrics) {
-    stats.forEach((row) => {
-      if (row.name == "process_resident_memory_bytes") {
-        this.mem = parseFloat(row.metrics[0].value) / 1024 / 1024;
-      }
-      if (row.name == "process_cpu_seconds_total") {
-        const now = Date.now() / 1000;
-        const cpuSecondsTotal = parseFloat(row.metrics[0].value);
-        this.cpu = (cpuSecondsTotal - this.prevCpuTotal) / (now - this.prevCpuNow);
-        this.prevCpuTotal = cpuSecondsTotal;
-        this.prevCpuNow = now;
-      }
-    });
-  }
-
-  static styles = [
-    css`
-      :host {
-        border: 2px solid #46a79f;
-        display: inline-block;
-      }
-      table {
-        border-collapse: collapse;
-        background: #000;
-        color: #ccc;
-        font-family: sans-serif;
-      }
-      th,
-      td {
-        outline: 1px solid #000;
-      }
-      th {
-        padding: 2px 4px;
-        background: #2f2f2f;
-      }
-      td {
-        padding: 0;
-        vertical-align: top;
-        text-align: center;
-      }
-      td.val {
-        padding: 2px 4px;
-        background: #3b5651;
-      }
-      .recents {
-        display: flex;
-        align-items: flex-end;
-        height: 30px;
-      }
-      .recents > div {
-        width: 3px;
-        background: red;
-        border-right: 1px solid black;
-      }
-      .bigInt {
-        min-width: 6em;
-      }
-    `,
-  ];
-
-  render() {
-    const now = Date.now() / 1000;
-
-    const table = (d: Metrics, path: string[]): TemplateResult => {
-      const byName = new Map<string, Metric>();
-      d.forEach((row) => {
-        byName.set(row.name, row);
-      });
-      let cols = d.map((row) => row.name);
-      cols.sort();
-
-      if (path.length == 0) {
-        ["webServer", "process"].forEach((earlyKey) => {
-          let i = cols.indexOf(earlyKey);
-          if (i != -1) {
-            cols = [earlyKey].concat(cols.slice(0, i), cols.slice(i + 1));
-          }
-        });
-      }
-
-      const th = (col: string): TemplateResult => {
-        return html`<th>${col}</th>`;
-      };
-      const td = (col: string): TemplateResult => {
-        const cell = byName.get(col)!;
-        return html`${drawLevel(cell, path.concat(col))}`;
-      };
-      return html` <table>
-        <tr>
-          ${cols.map(th)}
-        </tr>
-        <tr>
-          ${cols.map(td)}
-        </tr>
-      </table>`;
-    };
-
-    const tdWrap = (content: TemplateResult): TemplateResult => {
-      return html`<td>${content}</td>`;
-    };
-
-    const recents = (d: any, path: string[]) => {
-      const hi = Math.max.apply(null, d.recents);
-      const scl = 30 / hi;
-
-      const bar = (y: number) => {
-        let color;
-        if (y < d.average) {
-          color = "#6a6aff";
-        } else {
-          color = "#d09e4c";
-        }
-        return html`<div class="bar" style="height: ${y * scl}px; background: ${color};"></div>`;
-      };
-      return html`<td>
-        <div class="recents">${d.recents.map(bar)}</div>
-        <div>avg=${rounding(d.average, 3)}</div>
-      </td>`;
-    };
-
-    const pmf = (d: Metrics, path: string[]) => {
-      return tdWrap(
-        table(
-          {
-            count: d.count,
-            "values [ms]": html`
-              <div>mean=${rounding(d.mean * 1000, 3)}</div>
-              <div>sd=${rounding(d.stddev * 1000, 3)}</div>
-              <div>99=${rounding(d["99percentile"] * 1000, 3)}</div>
-            `,
-          },
-          path
-        )
-      );
-    };
-
-    const drawLevel = (d: Metric, path: string[]) => {
-      if (path.length == 1 && path[0] === "process") {
-        const elem = this.shadowRoot!.querySelector("#proc");
-        if (elem) {
-          (elem as StatsProcess).data = d;
-        }
-        return html`<stats-process id="proc"></stats-process>`;
-      }
-      if (typeof d === "object") {
-        if (d.strings) {
-          //} instanceof TemplateResult) {
-          return html`<td class="val">${d}</td>`;
-        } else if (d.count !== undefined && d.min !== undefined) {
-          return pmf(d, path);
-        } else if (d.average !== undefined && d.recents !== undefined) {
-          return recents(d, path);
-        } else {
-          return tdWrap(table(d, path));
-        }
-      } else {
-        return html`<td class="val bigInt">${d}</td>`;
-      }
-    };
-
-    const nonBoring = (m: Metric) => {
-      return (
-        !m.name.endsWith("_created") && //
-        !m.name.startsWith("python_gc_") &&
-        m.name != "python_info" &&
-        m.name != "process_max_fds" &&
-        m.name != "process_virtual_memory_bytes" &&
-        m.name != "process_resident_memory_bytes" &&
-        m.name != "process_start_time_seconds" &&
-        m.name != "process_cpu_seconds_total"
-      );
-    };
-
-    // return table(this.stats, []);
-    const displayedStats = this.stats.filter(nonBoring);
-    return html`
-      <div>
-        <table>
-          ${displayedStats.map(
-            (row, rowNum) => html`
-              <tr>
-                <th>${row.name}</th>
-                <td>
-                  <table>
-                    ${row.metrics.map(
-                      (v) => html`
-                        <tr>
-                          <td>${JSON.stringify(v.labels)}</td>
-                          <td>${v.value}</td>
-                        </tr>
-                      `
-                    )}
-                  </table>
-                </td>
-                ${rowNum == 0
-                  ? html`
-                      <td rowspan="${displayedStats.length}">
-                        <stats-process id="proc" cpu="${this.cpu}" mem="${this.mem}"></stats-process>
-                      </td>
-                    `
-                  : ""}
-              </tr>
-            `
-          )}
-        </table>
-      </div>
-    `;
-  }
-}
--- a/light9/web/homepage/StatsProcess.ts	Sat Apr 09 11:44:57 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-import { LitElement, html, css } from "lit";
-import { customElement, property } from "lit/decorators.js";
-import debug from "debug";
-
-const log = debug("process");
-
-const remap = (x: number, lo: number, hi: number, outLo: number, outHi: number) => {
-  return outLo + (outHi - outLo) * Math.max(0, Math.min(1, (x - lo) / (hi - lo)));
-};
-
-@customElement("stats-process")
-export class StatsProcess extends LitElement {
-  // inspired by https://codepen.io/qiruiyin/pen/qOopQx
-  @property() cpu = 0; // process_cpu_seconds_total
-  @property() mem = 0; // process_resident_memory_bytes
-
-  w = 64;
-  h = 64;
-  revs = 0;
-  prev = 0;
-  canvas?: HTMLCanvasElement;
-  ctx?: CanvasRenderingContext2D;
-  connectedCallback() {
-    super.connectedCallback();
-    this.initCanvas(this.shadowRoot!.firstElementChild as HTMLCanvasElement);
-    this.prev = Date.now() / 1000;
-
-    var animate = () => {
-      requestAnimationFrame(animate);
-      this.redraw();
-    };
-    animate();
-  }
-  initCanvas(canvas: HTMLCanvasElement) {
-    if (!canvas) {
-      return;
-    }
-    this.canvas = canvas;
-    this.ctx = this.canvas.getContext("2d")!;
-
-    this.canvas.width = this.w;
-    this.canvas.height = this.h;
-  }
-  redraw() {
-    if (!this.ctx) {
-      this.initCanvas(this.shadowRoot!.firstElementChild as HTMLCanvasElement);
-    }
-    if (!this.ctx) return;
-
-    this.canvas!.setAttribute("title", 
-    `cpu ${new Number(this.cpu).toPrecision(3)}% mem ${new Number(this.mem).toPrecision(3)}MB`);
-
-    const now = Date.now() / 1000;
-    const ctx = this.ctx;
-    ctx.beginPath();
-    // wrong type of fade- never goes to 0
-    ctx.fillStyle = "#00000003";
-    ctx.fillRect(0, 0, this.w, this.h);
-    const dt = now - this.prev;
-    this.prev = now;
-
-    const size = remap(this.mem.valueOf() / 1024 / 1024, /*in*/ 20, 600, /*out*/ 3, 30);
-    this.revs += dt * remap(this.cpu.valueOf(), /*in*/ 0, 100, /*out*/ 4, 120);
-    const rad = remap(size, /*in*/ 3, 30, /*out*/ 14, 5);
-
-    var x = this.w / 2 + rad * Math.cos(this.revs / 6.28),
-      y = this.h / 2 + rad * Math.sin(this.revs / 6.28);
-
-    ctx.save();
-    ctx.beginPath();
-    ctx.fillStyle = "hsl(194, 100%, 42%)";
-    ctx.arc(x, y, size, 0, 2 * Math.PI);
-    ctx.fill();
-    ctx.restore();
-  }
-
-  static styles = [
-    css`
-      :host {
-        display: inline-block;
-        width: 64px;
-        height: 64px;
-      }
-    `,
-  ];
-
-  render() {
-    return html`<canvas></canvas>`;
-  }
-}
--- a/light9/web/homepage/index.html	Sat Apr 09 11:44:57 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <title>light9 home</title>
-    <meta charset="utf-8" />
-    <link rel="stylesheet" href="@fs/my/proj/light9/light9/web/style.css">
-    <script type="module" src="./ServiceButtonRow.ts"></script>
-  </head>
-  <body>
-    <h1>light9 home page</h1>
-
-    <div style="display: grid">
-      <service-button-row name="rdfdb"></service-button-row>
-      <hr>
-      <service-button-row name="ascoltami"></service-button-row>
-      <hr>
-      <!--  <service-button-row name="picamserve"></service-button-row> -->  
-      <service-button-row name="vidref"></service-button-row>
-      <hr>
-      <service-button-row name="collector"></service-button-row>
-      <service-button-row name="effectSequencer"></service-button-row>
-      <service-button-row name="live"></service-button-row>
-      <service-button-row name="effects"></service-button-row>
-      <service-button-row name="timeline"></service-button-row>
-      <service-button-row name="paint"></service-button-row>
-      <service-button-row name="effectEval"></service-button-row> 
-<!--       <hr>
-      <service-button-row name="subServer"></service-button-row>
-      <service-button-row name="subComposer"></service-button-row>
-      -->
-    </div>
-    
-  </body>
-</html>
--- a/light9/web/homepage/vite.config.ts	Sat Apr 09 11:44:57 2022 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-import { defineConfig } from "vite";
-
-export default defineConfig({
-  root: "./light9/web/homepage",
-  server: {
-    host: "0.0.0.0",
-    strictPort: true,
-    port: 8300,
-    hmr: {
-      port: 8400,
-    },
-  },
-  define: {
-    global: {},
-  },
-});