changeset 6:b1043d39e493

start web console
author drewp@bigasterisk.com
date Mon, 13 Mar 2023 18:53:45 -0700
parents 5a99bde7a506
children 0f6c6c70610c
files console/Dockerfile console/index.html console/ingress.yaml console/package.json console/pnpm-lock.yaml console/skaffold.yaml console/src/main.css console/src/main.ts console/tsconfig.json console/vite.config.ts
diffstat 10 files changed, 689 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/console/Dockerfile	Mon Mar 13 18:52:00 2023 -0700
+++ b/console/Dockerfile	Mon Mar 13 18:53:45 2023 -0700
@@ -1,7 +1,28 @@
 FROM bang5:5000/base_basic
 
+ENV KEYRING=/usr/share/keyrings/nodesource.gpg
+ENV VERSION=node_18.x
+ENV DISTRO=jammy
+RUN apt-get install -y gpg
+
+RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor | tee "$KEYRING" >/dev/null && \
+  echo "deb [signed-by=$KEYRING] https://deb.nodesource.com/$VERSION $DISTRO main" | tee /etc/apt/sources.list.d/nodesource.list && \
+  echo "deb-src [signed-by=$KEYRING] https://deb.nodesource.com/$VERSION $DISTRO main" | tee -a /etc/apt/sources.list.d/nodesource.list && \
+  apt-get update && \
+  apt-get remove -y nodejs libnode-dev libnode72 && \
+  apt-get install -y nodejs && \
+  apt autoremove -y && \
+  pnpm add -g pnpm
+
 WORKDIR /opt
 
 COPY .pdm.toml pdm.lock pyproject.toml ./
 RUN pdm sync
 
+RUN pnpm config set @bigasterisk:registry https://bigasterisk.com/js/
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install
+
+COPY console.py index.html tsconfig.json vite.config.ts ./
+COPY src ./src/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/index.html	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>racc admin</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <link rel="stylesheet" type="text/css" media="screen" href="src/main.css" />
+ 
+    <script type="module" src="src/main.ts"></script>
+  </head>
+  <body>
+    racc admin
+    <div id="chart1" style="width:100%; height: 800px"></div>
+
+    <p><a href="https://bigasterisk.com/m/targets?endpoint_search=5150">targets list</a></p>
+    <div id="debug1"></div>
+  
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/ingress.yaml	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,27 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: racc-console
+  annotations:
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+    ingress.pomerium.io/allow_public_unauthenticated_access: "false"
+    ingress.pomerium.io/pass_identity_headers: "true"
+    ingress.pomerium.io/preserve_host_header: "true"
+    ingress.pomerium.io/policy: |
+      allow:
+        or: 
+          - { email: { is: "drewpca@gmail.com" }}
+          - { email: { is: "kelsimp@gmail.com" }}
+    ingress.pomerium.io/allow_websockets: "true"
+spec:
+  ingressClassName: pomerium
+  rules:
+    - host: "bigasterisk.com"
+      http:
+        paths:
+          - {pathType: Prefix, path: /racc/console/api/,    backend: { service: { name: racc-console, port: { name: py } } }}
+          - {pathType: Prefix, path: /racc/console/metrics, backend: { service: { name: racc-console, port: { name: py } } }}
+          - {pathType: Prefix, path: /racc/console/,        backend: { service: { name: racc-console, port: { name: vite } } }}
+  tls:
+    - hosts: [bigasterisk.com]
+      secretName: bigasterisk.com-tls
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/package.json	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,21 @@
+{
+  "name": "racc",
+  "version": "1.0.0",
+  "description": "apt install libxss-dev",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@types/tabulator-tables": "^5.4.5",
+    "typescript": "^4.9.5"
+  },
+  "dependencies": {
+    "date-fns": "^2.29.3",
+    "echarts": "^5.4.1",
+    "tabulator-tables": "^5.4.4",
+    "vite": "^4.1.4"
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/pnpm-lock.yaml	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,393 @@
+lockfileVersion: 5.3
+
+specifiers:
+  '@types/tabulator-tables': ^5.4.5
+  date-fns: ^2.29.3
+  echarts: ^5.4.1
+  tabulator-tables: ^5.4.4
+  typescript: ^4.9.5
+  vite: ^4.1.4
+
+dependencies:
+  date-fns: 2.29.3
+  echarts: 5.4.1
+  tabulator-tables: 5.4.4
+  vite: 4.1.4
+
+devDependencies:
+  '@types/tabulator-tables': 5.4.5
+  typescript: 4.9.5
+
+packages:
+
+  /@esbuild/android-arm/0.16.17:
+    resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/android-arm64/0.16.17:
+    resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/android-x64/0.16.17:
+    resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/darwin-arm64/0.16.17:
+    resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/darwin-x64/0.16.17:
+    resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/freebsd-arm64/0.16.17:
+    resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/freebsd-x64/0.16.17:
+    resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-arm/0.16.17:
+    resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-arm64/0.16.17:
+    resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-ia32/0.16.17:
+    resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-loong64/0.16.17:
+    resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==}
+    engines: {node: '>=12'}
+    cpu: [loong64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-mips64el/0.16.17:
+    resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==}
+    engines: {node: '>=12'}
+    cpu: [mips64el]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-ppc64/0.16.17:
+    resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-riscv64/0.16.17:
+    resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==}
+    engines: {node: '>=12'}
+    cpu: [riscv64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-s390x/0.16.17:
+    resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==}
+    engines: {node: '>=12'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/linux-x64/0.16.17:
+    resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/netbsd-x64/0.16.17:
+    resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [netbsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/openbsd-x64/0.16.17:
+    resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [openbsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/sunos-x64/0.16.17:
+    resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [sunos]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/win32-arm64/0.16.17:
+    resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/win32-ia32/0.16.17:
+    resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@esbuild/win32-x64/0.16.17:
+    resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@types/tabulator-tables/5.4.5:
+    resolution: {integrity: sha512-JCmigqOupYh/21JG0GqlrICyDyakzj1XJId6WoqUP5XSz1Khi1C7/skqdlN3B3AkUBIqYZ07QVIpUnyxknxODQ==}
+    dev: true
+
+  /date-fns/2.29.3:
+    resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
+    engines: {node: '>=0.11'}
+    dev: false
+
+  /echarts/5.4.1:
+    resolution: {integrity: sha512-9ltS3M2JB0w2EhcYjCdmtrJ+6haZcW6acBolMGIuf01Hql1yrIV01L1aRj7jsaaIULJslEP9Z3vKlEmnJaWJVQ==}
+    dependencies:
+      tslib: 2.3.0
+      zrender: 5.4.1
+    dev: false
+
+  /esbuild/0.16.17:
+    resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    optionalDependencies:
+      '@esbuild/android-arm': 0.16.17
+      '@esbuild/android-arm64': 0.16.17
+      '@esbuild/android-x64': 0.16.17
+      '@esbuild/darwin-arm64': 0.16.17
+      '@esbuild/darwin-x64': 0.16.17
+      '@esbuild/freebsd-arm64': 0.16.17
+      '@esbuild/freebsd-x64': 0.16.17
+      '@esbuild/linux-arm': 0.16.17
+      '@esbuild/linux-arm64': 0.16.17
+      '@esbuild/linux-ia32': 0.16.17
+      '@esbuild/linux-loong64': 0.16.17
+      '@esbuild/linux-mips64el': 0.16.17
+      '@esbuild/linux-ppc64': 0.16.17
+      '@esbuild/linux-riscv64': 0.16.17
+      '@esbuild/linux-s390x': 0.16.17
+      '@esbuild/linux-x64': 0.16.17
+      '@esbuild/netbsd-x64': 0.16.17
+      '@esbuild/openbsd-x64': 0.16.17
+      '@esbuild/sunos-x64': 0.16.17
+      '@esbuild/win32-arm64': 0.16.17
+      '@esbuild/win32-ia32': 0.16.17
+      '@esbuild/win32-x64': 0.16.17
+    dev: false
+
+  /fsevents/2.3.2:
+    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /function-bind/1.1.1:
+    resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+    dev: false
+
+  /has/1.0.3:
+    resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+    engines: {node: '>= 0.4.0'}
+    dependencies:
+      function-bind: 1.1.1
+    dev: false
+
+  /is-core-module/2.11.0:
+    resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
+    dependencies:
+      has: 1.0.3
+    dev: false
+
+  /nanoid/3.3.4:
+    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+    dev: false
+
+  /path-parse/1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+    dev: false
+
+  /picocolors/1.0.0:
+    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+    dev: false
+
+  /postcss/8.4.21:
+    resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.4
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+    dev: false
+
+  /resolve/1.22.1:
+    resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
+    hasBin: true
+    dependencies:
+      is-core-module: 2.11.0
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+    dev: false
+
+  /rollup/3.19.1:
+    resolution: {integrity: sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==}
+    engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+    hasBin: true
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: false
+
+  /source-map-js/1.0.2:
+    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
+  /supports-preserve-symlinks-flag/1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+    dev: false
+
+  /tabulator-tables/5.4.4:
+    resolution: {integrity: sha512-WqPWLRwrD8UMISUjQqZyj6y9k3ajQBs7eJtRosbt9q8RRkZlYCJYGxLt3L44jEXaQCE5q5EJFbGeDUEDJa1a3w==}
+    dev: false
+
+  /tslib/2.3.0:
+    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+    dev: false
+
+  /typescript/4.9.5:
+    resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+    engines: {node: '>=4.2.0'}
+    hasBin: true
+    dev: true
+
+  /vite/4.1.4:
+    resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': '>= 14'
+      less: '*'
+      sass: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      less:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+    dependencies:
+      esbuild: 0.16.17
+      postcss: 8.4.21
+      resolve: 1.22.1
+      rollup: 3.19.1
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: false
+
+  /zrender/5.4.1:
+    resolution: {integrity: sha512-M4Z05BHWtajY2241EmMPHglDQAJ1UyHQcYsxDNzD9XLSkPDqMq4bB28v9Pb4mvHnVQ0GxyTklZ/69xCFP6RXBA==}
+    dependencies:
+      tslib: 2.3.0
+    dev: false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/skaffold.yaml	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,21 @@
+apiVersion: skaffold/v3
+kind: Config
+metadata:
+  name: racc
+build:
+  artifacts:
+  - image: bang5:5000/racc_console_image
+    sync:
+      infer:
+      - src/*
+      - index.html
+  tagPolicy:
+    dateTime:
+      format: 2006-01-02_15-04-05
+      timezone: Local
+manifests:
+  rawYaml:
+  - deploy.yaml
+  - ingress.yaml
+deploy:
+  kubectl: {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/src/main.css	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,1 @@
+@import  "tabulator-tables/dist/css/tabulator.min.css";
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/src/main.ts	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,143 @@
+import { startOfDay, subDays, differenceInMinutes, getUnixTime, addDays, format, min, formatISO } from "date-fns";
+import * as echarts from "echarts";
+import { TabulatorFull as Tabulator } from "tabulator-tables";
+
+const QUERY_RANGE = "https://bigasterisk.com/m/prometheus/api/v1/query_range";
+const MAC = new Map([
+]);
+
+function progQueryUrl(prog: string, s: Date, e: Date): URL {
+  const rangeMins = differenceInMinutes(e, s);
+  const out = new URL(QUERY_RANGE);
+
+  const host = "Kelsis-iMac";
+  const isRunningQl = `racc_running{prog="${prog}",host="${host}"}`;
+  const isNonIdleQl = `max(racc_idle{host="${host}"}) < 100`;
+  const countedTimeQl = `(${isRunningQl}) if (${isNonIdleQl})`;
+  const integralQl = `integrate((${countedTimeQl})[${rangeMins}m])`;
+  const hoursQl = `${integralQl}/1h`;
+
+  out.searchParams.append("query", hoursQl);
+  out.searchParams.append("start", "" + getUnixTime(s));
+  out.searchParams.append("end", "" + getUnixTime(e));
+  out.searchParams.append("step", "1m");
+  return out;
+}
+
+async function queryLastRow(queryUrl: URL): Promise<string> {
+  const response = await fetch(queryUrl);
+  const body = await response.json();
+  if (!body?.data?.result) {
+    return "0";
+  }
+  const m = body.data.result[0];
+  if (m?.values?.length) {
+    const lastRow = m.values[m.values.length - 1];
+    return parseFloat(lastRow[1]).toFixed(2);
+  }
+  return "0";
+}
+
+async function makeSeries(now: Date, meas: string, prog: string, numDays: number): Promise<echarts.BarSeriesOption> {
+  const rows = [];
+  const sod = startOfDay(now);
+
+  for (let i = numDays-1 ; i >= 0; i--) {
+    const s = subDays(sod, i);
+    const e = min([now, addDays(s, 1)]);
+    console.log(i, formatISO(s), formatISO(e));
+    rows.push(queryLastRow(progQueryUrl(prog, s, e)));
+  }
+  const mc = await Promise.all(rows);
+
+  return {
+    name: meas,
+    type: "bar",
+    animation: false,
+    data: mc,
+    emphasis: { focus: "series" },
+    label: { show: true },
+    stack: "total",
+    
+  };
+}
+
+function dateRowLabels(now: Date, days: number) {
+  const rowNames = [];
+  for (let d = subDays(now, days - 1); d < now; d = addDays(d, 1)) {
+    rowNames.push(format(d, "MM/dd EEE"));
+  }
+  rowNames.push(format(now, "MM/dd EEE") + " (today)");
+  return rowNames;
+}
+
+function fillDebugTable(el: HTMLElement) {
+  var rows = [];
+
+  const vmui = (expr: string, range: string): string =>
+    "https://bigasterisk.com/m/vmui/?" +
+    "g0.expr=" +
+    encodeURIComponent(expr) + //
+    "&g0.range_input=" +
+    encodeURIComponent(range);
+  const view = (url: string): string => `<a href="${url}">view</a>`;
+  for (let host of ["Kelsis-iMac", "dash", "dot", "plus"]) {
+    rows.push({
+      id: rows.length,
+      host: host,
+      metrics: view(`http://${host}:5150/metrics`),
+      uptime: view(vmui(`python_info{job="racc",instance="${host}:5150"}`, "14d")),
+      traffic: view(vmui(`rate(lan_bytes_sent_from_total{mac="${MAC.get(host)}"})`, "6h")),
+    });
+  }
+  var table = new Tabulator(el, {
+    data: rows,
+    columns: [
+      { title: "Host", field: "host" },
+      { title: "current /metrics", field: "metrics", formatter: "html" },
+      { title: "racc uptime history", field: "uptime", formatter: "html" },
+      { title: "outgoing net traffic", field: "traffic", formatter: "html" },
+    ],
+  });
+}
+
+async function main() {
+  fillDebugTable(document.getElementById("debug1")!);
+
+  const now = new Date();
+
+  const chartDom = document.getElementById("chart1")!;
+  const chart = echarts.init(chartDom);
+
+  const nDays = 8;
+  const rowNames = dateRowLabels(now, nDays);
+
+  const option: echarts.EChartsOption = {
+    tooltip: {
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow",
+      },
+    },
+    legend: {},
+    grid: {
+      left: "0%",
+      containLabel: true,
+    },
+    xAxis: {
+      type: "value",
+      max: 10,
+    },
+    yAxis: {
+      type: "category",
+      data: rowNames,
+    },
+    series: await Promise.all([
+      makeSeries(now, "Minecraft hours", "minecraft", nDays), //
+      makeSeries(now, "Roblox hours", "roblox", nDays),
+    ]),
+  };
+console.log(option);
+  chart.setOption(option);
+}
+main();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/tsconfig.json	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "target": "es2020",
+    "module": "es2020",
+    "strict": true,
+    "moduleResolution": "node",
+    "esModuleInterop": true,
+    "experimentalDecorators": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "useDefineForClassFields": false
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/vite.config.ts	Mon Mar 13 18:53:45 2023 -0700
@@ -0,0 +1,30 @@
+import { defineConfig } from "vite";
+
+export default defineConfig({
+  base: "https://bigasterisk.com/racc/console/",
+  server: {
+    host: "0.0.0.0",
+    strictPort: true,
+    port: 8002,
+    hmr: { path: "vite-ws" },
+    fs: { allow: ["src", "node_modules", "."] },
+  },
+  build: {
+    assetsDir: "src",
+    target: "esnext",
+    lib: {
+      entry: "src/index.ts",
+      formats: ["es"],
+    },
+    rollupOptions: {
+      // input: { app: "src/index.html" },
+      external: /^lit/,
+    },
+  },
+  resolve: {
+    alias: [{ find: "rdf-canonize-native", replacement: "" }],
+  },
+  define: {
+    global: {},
+  },
+});