Improve visualisation system #12
@ -4,9 +4,7 @@ version = "0.1.0"
|
||||
description = "Use Wi-Fi Channel State Information to locate human presence"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"flask",
|
||||
"numpy>=2.0.0",
|
||||
"flask-sock",
|
||||
"pytest",
|
||||
"matplotlib",
|
||||
"scipy",
|
||||
@ -53,4 +51,5 @@ packages = ["where_fi"]
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"grpcio-tools>=1.70.0",
|
||||
"protoletariat>=3.3.9",
|
||||
]
|
||||
|
||||
114
uv.lock
114
uv.lock
@ -1,4 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
@ -14,15 +15,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
@ -113,35 +105,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "blinker" },
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-sock"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "simple-websocket" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/8f/c6ab717dc90f4e46d1430335cd4ab13e3629410bb760c0ead6de476760fb/flask-sock-0.7.0.tar.gz", hash = "sha256:e023b578284195a443b8d8bdb4469e6a6acf694b89aeb51315b1a34fcf427b7d", size = 4334 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/98/107728ce3f430b5481eb426ccc5e1f7c8ab0bd01eaf231c62a8d528ff721/flask_sock-0.7.0-py3-none-any.whl", hash = "sha256:caac4d679392aaf010d02fabcf73d52019f5bdaf1c9c131ec5a428cb3491204a", size = 3982 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.55.6"
|
||||
@ -259,15 +222,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/8a/92aba852bbe2ddf3e44c354b4162b3cf350b810523ffb2d0e5937bd3f249/grpcio_tools-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:04bf30c0eb2741defe3ab6e0a6102b022d69cfd39d68fab9b954993ceca8d346", size = 1118147 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h5py"
|
||||
version = "3.12.1"
|
||||
@ -303,15 +257,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.5"
|
||||
@ -771,6 +716,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/b2/ab07b09e0f6d143dfb839693aa05765257bceaa13d03bf1a696b78323e7a/protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f", size = 172550 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protoletariat"
|
||||
version = "3.3.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/cd/f8c0b9d113a1d551b7a014339c4da85c71c2ba9ac889855eb45744d89459/protoletariat-3.3.9.tar.gz", hash = "sha256:db209b7058a0b700c5855b79c4151216205a93298b052c227774cba59f213831", size = 20678 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/59/bf79f403c358d1dd4a9cfa2bbfea8734bfa1a1181aecf3a8945ab6f6e7fc/protoletariat-3.3.9-py3-none-any.whl", hash = "sha256:a876a600691005c7d229a149f9639c994a087c4a66bc054717f243f0f353ae66", size = 20718 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
@ -1003,18 +961,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-websocket"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wsproto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/d4/bfa032f961103eba93de583b161f0e6a5b63cebb8f2c7d0c6e6efe1e3d2e/simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4", size = 17300 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/59/0782e51887ac6b07ffd1570e0364cf901ebc36345fea669969d2084baebb/simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c", size = 13842 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@ -1110,25 +1056,11 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "where-fi"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-sock" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "h5py" },
|
||||
{ name = "matplotlib" },
|
||||
@ -1145,12 +1077,11 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "grpcio-tools" },
|
||||
{ name = "protoletariat" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-sock" },
|
||||
{ name = "grpcio", specifier = ">=1.70.0" },
|
||||
{ name = "h5py", specifier = ">=3.12.1" },
|
||||
{ name = "matplotlib" },
|
||||
@ -1165,16 +1096,7 @@ requires-dist = [
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "grpcio-tools", specifier = ">=1.70.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "wsproto"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226 },
|
||||
dev = [
|
||||
{ name = "grpcio-tools", specifier = ">=1.70.0" },
|
||||
{ name = "protoletariat", specifier = ">=3.3.9" },
|
||||
]
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import torch
|
||||
import typer
|
||||
|
||||
from .. import visualise
|
||||
from ..config import config
|
||||
from ..processing.aoa import AoA
|
||||
from ..processing.preprocess import Preprocessor
|
||||
from ..visualise import server as visualise
|
||||
from . import file, globals
|
||||
|
||||
app = typer.Typer(callback=globals.main)
|
||||
@ -40,23 +41,24 @@ def heatmap() -> None:
|
||||
aoa = AoA()
|
||||
|
||||
# Start webapp in background process
|
||||
manager = mp.Manager()
|
||||
webapp_queue: "mp.Queue[npt.NDArray[np.float32]]" = manager.Queue(
|
||||
config.sample_rate
|
||||
)
|
||||
webapp_queue: "mp.Queue[visualise.VisualiserData]" = mp.Queue(config.sample_rate)
|
||||
webapp = mp.Process(target=visualise.start, args=(webapp_queue,))
|
||||
webapp.start()
|
||||
|
||||
def visualise_data(data: npt.NDArray[Any], dtype: visualise.DataType) -> None:
|
||||
if not webapp_queue.full():
|
||||
webapp_queue.put(visualise.VisualiserData(data, dtype))
|
||||
|
||||
def callback(antenna_data: npt.NDArray[np.complex64]) -> None:
|
||||
logger.info(f"Got final CSI data with shape {antenna_data.shape}")
|
||||
visualise_data(antenna_data, visualise.DataType.RAW_CSI)
|
||||
processed = preprocessor.preprocess(antenna_data)
|
||||
visualise_data(processed, visualise.DataType.PROCESSED_CSI)
|
||||
logger.info(f"Processed CSI data with shape {processed.shape}")
|
||||
processed_tensor = torch.tensor(processed, device=device)
|
||||
aoa.update(processed_tensor)
|
||||
|
||||
if not webapp_queue.full():
|
||||
heatmap = aoa.heatmap()
|
||||
webapp_queue.put(heatmap)
|
||||
heatmap = aoa.heatmap()
|
||||
visualise_data(heatmap, visualise.DataType.HEATMAP)
|
||||
|
||||
globals.csi_producer(csi_callback=callback)
|
||||
logger.info("Finished processing CSI data")
|
||||
|
||||
2
where_fi/visualise/.gitignore
vendored
Normal file
2
where_fi/visualise/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
server/generated
|
||||
frontend/src/grpc
|
||||
9
where_fi/visualise/Makefile
Normal file
9
where_fi/visualise/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
all: server/generated frontend/src/grpc
|
||||
|
||||
server/generated: protos/*.proto
|
||||
mkdir -p server/generated
|
||||
find protos/ -type f -name "*.proto" | xargs uv run python -m grpc_tools.protoc -Iprotos --python_out=server/generated --pyi_out=server/generated --grpc_python_out=server/generated
|
||||
find protos/ -type f -name "*.proto" | xargs uv run protol --create-package --in-place --python-out server/generated/ protoc --proto-path=protos/
|
||||
|
||||
frontend/src/grpc: protos/*.proto
|
||||
find protos/ -name "*.proto" | xargs npm exec --prefix frontend/ protoc --ts_out frontend/src/grpc -I protos/
|
||||
@ -1,134 +0,0 @@
|
||||
import io
|
||||
import logging
|
||||
import multiprocessing as mp
|
||||
from typing import Generator
|
||||
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from flask import Flask, Response, render_template
|
||||
from flask_sock import Sock
|
||||
from simple_websocket import Server
|
||||
|
||||
from ..config import config
|
||||
from ..processing.aoa import AoA
|
||||
|
||||
matplotlib.use("agg")
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
aoa_queue = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
data: npt.NDArray[np.complex128] = np.array([], dtype=complex)
|
||||
aoa: AoA = AoA()
|
||||
|
||||
|
||||
@app.route("/preprocessed")
|
||||
def preprocessed() -> str:
|
||||
return render_template("preprocessed.html")
|
||||
|
||||
|
||||
Subscriber = Server
|
||||
subscriber_settings: dict[Subscriber, tuple[int, int, int]] = {}
|
||||
|
||||
|
||||
@sock.route("/data")
|
||||
def get_data(sock: Subscriber) -> None:
|
||||
while True:
|
||||
msg = sock.receive()
|
||||
if len(msg.split()) != 3:
|
||||
break
|
||||
subcarrier, rx, tx = map(int, msg.split())
|
||||
subscriber_settings[sock] = (subcarrier, rx, tx)
|
||||
|
||||
|
||||
def add_data(
|
||||
raw_data: npt.NDArray[np.complex128], new_data: npt.NDArray[np.complex128]
|
||||
) -> None:
|
||||
if config.VISUALISE_RAW:
|
||||
magn = np.abs(raw_data)
|
||||
phase = np.angle(raw_data)
|
||||
|
||||
new_mag = np.abs(new_data)
|
||||
new_phase = np.angle(new_data)
|
||||
|
||||
fig, axs = plt.subplots(2, 2)
|
||||
axs[0, 0].plot(magn[:, 0, 0], c="b")
|
||||
axs[0, 0].plot(magn[:, 1, 0], c="orange")
|
||||
axs[1, 0].plot(new_mag[:, 0, 0], c="b")
|
||||
axs[1, 0].plot(new_mag[:, 1, 0], c="orange")
|
||||
axs[0, 1].plot(phase[:, 0, 0], c="b")
|
||||
axs[0, 1].plot(phase[:, 1, 0], c="orange")
|
||||
axs[1, 1].plot(new_phase[:, 0, 0], c="b")
|
||||
axs[1, 1].plot(new_phase[:, 1, 0], c="orange")
|
||||
fig.savefig("/tmp/plot.png")
|
||||
plt.close(fig)
|
||||
|
||||
global data
|
||||
if data.size == 0:
|
||||
data = np.expand_dims(new_data, axis=0)
|
||||
else:
|
||||
data = np.concat([data, np.expand_dims(new_data, axis=0)], axis=0)
|
||||
|
||||
# Only keep latest 100 entries
|
||||
if data.shape[0] > 100:
|
||||
data = data[-100:]
|
||||
to_remove: list[Subscriber] = []
|
||||
for subscriber in subscriber_settings:
|
||||
try:
|
||||
subcarrier, rx, tx = subscriber_settings[subscriber]
|
||||
subscriber.send(new_data.real[subcarrier, rx, tx])
|
||||
except Exception as e:
|
||||
to_remove.append(subscriber)
|
||||
print(e)
|
||||
|
||||
for subscriber in to_remove:
|
||||
del subscriber_settings[subscriber]
|
||||
|
||||
|
||||
def plot_heatmap(heatmap: npt.NDArray[np.float32]) -> io.BytesIO:
|
||||
logger.info(f"Making heatmap with aoa of {aoa.timestamp}")
|
||||
fig = plt.figure()
|
||||
ax = fig.add_axes([0.1, 0.1, 0.9, 0.9]) # , polar=True)
|
||||
r = np.linspace(
|
||||
0, config.music.heatmap.tof_max, config.music.heatmap.tof_resolution
|
||||
)
|
||||
theta = np.linspace(0, np.pi, config.music.heatmap.theta_resolution) # Angle values
|
||||
|
||||
mesh = ax.pcolormesh(theta, r, heatmap, edgecolors="face", vmin=0, vmax=50)
|
||||
|
||||
fig.colorbar(mesh, ax=ax)
|
||||
buf = io.BytesIO()
|
||||
fig.savefig(buf, format="jpeg")
|
||||
plt.close(fig)
|
||||
|
||||
buf.seek(0)
|
||||
return buf
|
||||
|
||||
|
||||
def gather_aoa() -> Generator[bytes, None, None]:
|
||||
assert aoa_queue is not None
|
||||
|
||||
while True:
|
||||
logger.debug("Receiving from aoa pipe")
|
||||
heatmap = aoa_queue.get()
|
||||
buf = plot_heatmap(heatmap)
|
||||
yield (b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + buf.read() + b"\r\n")
|
||||
buf.close()
|
||||
|
||||
|
||||
@app.route("/aoa_tof")
|
||||
def aoa_tof() -> Response:
|
||||
return Response(gather_aoa(), mimetype="multipart/x-mixed-replace; boundary=frame")
|
||||
|
||||
|
||||
def start(conn: "mp.Queue[npt.NDArray[np.float32]]") -> None:
|
||||
global app, aoa_queue
|
||||
|
||||
aoa_queue = conn
|
||||
app.run(debug=True, use_reloader=False, host="0.0.0.0")
|
||||
logger.info("Visualisation server shut down")
|
||||
66
where_fi/visualise/envoy.yaml
Normal file
66
where_fi/visualise/envoy.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
admin:
|
||||
access_log_path: /tmp/admin_access.log
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 9901 }
|
||||
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 8080 }
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains: ["*"]
|
||||
routes:
|
||||
- match: { prefix: "/" }
|
||||
route:
|
||||
cluster: echo_service
|
||||
timeout: 0s
|
||||
max_stream_duration:
|
||||
grpc_timeout_header_max: 0s
|
||||
cors:
|
||||
allow_origin_string_match:
|
||||
- prefix: "*"
|
||||
allow_methods: GET, PUT, DELETE, POST, OPTIONS
|
||||
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||
max_age: "1728000"
|
||||
expose_headers: custom-header-1,grpc-status,grpc-message
|
||||
http_filters:
|
||||
- name: envoy.filters.http.grpc_web
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
|
||||
- name: envoy.filters.http.cors
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
clusters:
|
||||
- name: echo_service
|
||||
connect_timeout: 0.25s
|
||||
type: logical_dns
|
||||
# HTTP/2 support
|
||||
typed_extension_protocol_options:
|
||||
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
|
||||
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
|
||||
explicit_http_config:
|
||||
http2_protocol_options: {}
|
||||
lb_policy: round_robin
|
||||
load_assignment:
|
||||
cluster_name: cluster_0
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: 127.0.0.1
|
||||
port_value: 50051
|
||||
9
where_fi/visualise/frontend/.editorconfig
Normal file
9
where_fi/visualise/frontend/.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
1
where_fi/visualise/frontend/.gitattributes
vendored
Normal file
1
where_fi/visualise/frontend/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
34
where_fi/visualise/frontend/.gitignore
vendored
34
where_fi/visualise/frontend/.gitignore
vendored
@ -1,4 +1,30 @@
|
||||
node_modules/
|
||||
**/*_pb.d.ts
|
||||
**/*_pb.js
|
||||
**/*Pb.ts
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
7
where_fi/visualise/frontend/.prettierrc.json
Normal file
7
where_fi/visualise/frontend/.prettierrc.json
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { FigureRequest, FigureUpdate } from './figure_pb.js';
|
||||
import { FigureServiceClient } from './FigureServiceClientPb';
|
||||
|
||||
export function connect(host: string) {
|
||||
var figureService = new FigureServiceClient(host);
|
||||
var request = new FigureRequest();
|
||||
|
||||
var stream = figureService.getFigure(request);
|
||||
stream.on('data', function(response: FigureUpdate) {
|
||||
if (response.hasFigure()) {
|
||||
console.log("Got new figure");
|
||||
}
|
||||
else {
|
||||
console.log("Got figure update");
|
||||
}
|
||||
});
|
||||
stream.on('status', function(status: any) {
|
||||
console.log("Status:", status);
|
||||
});
|
||||
stream.on('end', function() {
|
||||
console.log("End");
|
||||
});
|
||||
}
|
||||
1
where_fi/visualise/frontend/env.d.ts
vendored
Normal file
1
where_fi/visualise/frontend/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
24
where_fi/visualise/frontend/eslint.config.ts
Normal file
24
where_fi/visualise/frontend/eslint.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||
|
||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'app/files-to-ignore',
|
||||
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||
},
|
||||
|
||||
pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
skipFormatting,
|
||||
)
|
||||
12
where_fi/visualise/frontend/index.html
Normal file
12
where_fi/visualise/frontend/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Where-Fi Visualizer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,3 +0,0 @@
|
||||
import { connect } from "./connect";
|
||||
|
||||
connect("http://localhost:8080");
|
||||
5199
where_fi/visualise/frontend/package-lock.json
generated
5199
where_fi/visualise/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,46 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "Frontend for where-fi",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
"type": "commonjs",
|
||||
"main": "index.ts",
|
||||
"name": "visualise",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@protobuf-ts/grpcweb-transport": "^2.9.4",
|
||||
"buffer": "^6.0.3",
|
||||
"google-protobuf": "^3.21.4",
|
||||
"grpc-web": "^1.5.0"
|
||||
"grpc-web": "^1.5.0",
|
||||
"plotly.js-dist": "^3.0.1",
|
||||
"plotly.js-dist-min": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/google-protobuf": "^3.15.12",
|
||||
"ts-loader": "^9.5.2",
|
||||
"typescript": "^5.7.3"
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/plotly.js": "^2.35.2",
|
||||
"@types/plotly.js-dist-min": "^2.3.4",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vue/eslint-config-prettier": "^10.1.0",
|
||||
"@vue/eslint-config-typescript": "^14.3.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"jiti": "^2.4.2",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-vue-devtools": "^7.7.1",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
const plotElement = document.getElementById("plot");
|
||||
|
||||
const layout = {
|
||||
title: "Real-Time Complex Data Visualization",
|
||||
xaxis: { title: "Time (ms)" },
|
||||
yaxis: { title: "Value" },
|
||||
};
|
||||
|
||||
Plotly.newPlot(
|
||||
plotElement,
|
||||
[
|
||||
{
|
||||
x: [],
|
||||
y: [],
|
||||
type: "scatter",
|
||||
mode: "lines+markers", // Line + markers
|
||||
},
|
||||
],
|
||||
layout,
|
||||
);
|
||||
|
||||
const socket = new WebSocket("ws://" + location.host + "/data");
|
||||
socket.onopen = function () {
|
||||
console.log("Connected to the server");
|
||||
socket.send("0 0 0");
|
||||
};
|
||||
socket.addEventListener("message", function (msg) {
|
||||
Plotly.extendTraces(
|
||||
plotElement,
|
||||
{
|
||||
x: [[msg.timeStamp]],
|
||||
y: [[msg.data]],
|
||||
},
|
||||
[0],
|
||||
300,
|
||||
);
|
||||
});
|
||||
|
||||
function changeSubcarrier() {
|
||||
const subcarrier = document.getElementById("subcarrier").value;
|
||||
socket.send(subcarrier + " 0 0");
|
||||
}
|
||||
42
where_fi/visualise/frontend/src/App.vue
Normal file
42
where_fi/visualise/frontend/src/App.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import PlotList from './components/PlotList.vue'
|
||||
import Figure from './components/Figure.vue'
|
||||
import { Figure as FigureType } from './grpc/figure'
|
||||
|
||||
var plots = ref<FigureType[]>([])
|
||||
var paused = ref(false)
|
||||
|
||||
function addPlot(fig: FigureType) {
|
||||
console.log('Plot added', fig)
|
||||
plots.value.push(fig)
|
||||
console.log(plots)
|
||||
}
|
||||
function removePlot(fig: FigureType) {
|
||||
console.log('Plot removed', fig)
|
||||
plots.value = plots.value.filter((x) => x.uuid !== fig.uuid)
|
||||
console.log(plots)
|
||||
}
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$props: {
|
||||
onClick?: (e: MouseEvent) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<PlotList @figure-selected="addPlot" @figure-deselected="removePlot" />
|
||||
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<Figure v-for="plot in plots" :key="plot.uuid" :figure="plot" :paused="paused" />
|
||||
</v-container>
|
||||
<v-fab location="right bottom" color="secondary" @click="paused = !paused" icon app>
|
||||
<v-icon :icon="paused ? 'mdi-play' : 'mdi-pause'"></v-icon>
|
||||
</v-fab>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
86
where_fi/visualise/frontend/src/assets/main.css
Normal file
86
where_fi/visualise/frontend/src/assets/main.css
Normal file
@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
92
where_fi/visualise/frontend/src/components/Figure.vue
Normal file
92
where_fi/visualise/frontend/src/components/Figure.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import * as Plotly from 'plotly.js-dist-min'
|
||||
import { Figure, FigureData } from '../grpc/figure'
|
||||
import { FigureServiceClient } from '../grpc/figure.client'
|
||||
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'
|
||||
import { host } from '../connect'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onBeforeUnmount } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { figure, paused = false } = defineProps<{ figure: Figure; paused?: boolean }>()
|
||||
const cancel = ref<boolean>(false)
|
||||
|
||||
const data = ref<FigureData | null>(null)
|
||||
const plotData = computed(() => {
|
||||
if (data.value) {
|
||||
switch (data.value.figure.oneofKind) {
|
||||
case 'line':
|
||||
return data.value.figure.line.lines.map((line) => ({
|
||||
x: line.x.length == 0 ? undefined : line.x,
|
||||
y: line.y,
|
||||
type: 'scatter' as const,
|
||||
name: line.label,
|
||||
color: line.color ? line.color : undefined,
|
||||
}))
|
||||
case 'heatmap':
|
||||
return []
|
||||
/* data.value.figure.heatmap.lines.map((line) => {
|
||||
return {
|
||||
x: line.x,
|
||||
y: line.y,
|
||||
type: 'scatter',
|
||||
name: line.label,
|
||||
color: line.color,
|
||||
}
|
||||
})*/
|
||||
}
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const layout = {
|
||||
title: { text: figure.title },
|
||||
xaxis: { title: { text: figure.xLabel } },
|
||||
yaxis: { title: { text: figure.yLabel } },
|
||||
}
|
||||
|
||||
function updateGraph() {
|
||||
console.log('Updating graph with', plotData.value)
|
||||
Plotly.newPlot(figure.uuid, plotData.value, layout, { responsive: true })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
console.log('Mounted')
|
||||
|
||||
updateGraph()
|
||||
|
||||
const transport = new GrpcWebFetchTransport({ baseUrl: host })
|
||||
const figureService = new FigureServiceClient(transport)
|
||||
const stream = figureService.getFigureUpdate({ uuid: figure.uuid })
|
||||
for await (const response of stream.responses) {
|
||||
if (cancel.value) {
|
||||
console.log('Stopping stream...')
|
||||
break
|
||||
}
|
||||
console.log(`Got new value for ${figure.uuid}:`, response)
|
||||
|
||||
if (!paused) {
|
||||
data.value = response
|
||||
updateGraph()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
console.log('Component is about to unmount, stopping stream...')
|
||||
cancel.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row class="w-100">
|
||||
<v-col cols="12">
|
||||
<v-card class="w-100">
|
||||
<v-card-title>{{ figure.title }}</v-card-title>
|
||||
<v-card-text>
|
||||
<div :id="figure.uuid" class="w-100" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
83
where_fi/visualise/frontend/src/components/PlotList.vue
Normal file
83
where_fi/visualise/frontend/src/components/PlotList.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { Figure } from '../grpc/figure'
|
||||
import { FigureServiceClient } from '../grpc/figure.client'
|
||||
import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'
|
||||
import { host } from '../connect'
|
||||
|
||||
const plots = ref<Figure[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
var emit = (_: any, ...args: any[]) => {
|
||||
console.log('No emit function set', args)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['figure-selected', 'figure-deselected'],
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
loading: loading,
|
||||
plots: plots,
|
||||
error,
|
||||
}
|
||||
},
|
||||
setup(_, ctx) {
|
||||
console.log('Setup')
|
||||
emit = ctx.emit
|
||||
},
|
||||
async mounted() {
|
||||
const transport = new GrpcWebFetchTransport({ baseUrl: host })
|
||||
const figureService = new FigureServiceClient(transport)
|
||||
const stream = figureService.getFigure({})
|
||||
for await (const response of stream.responses) {
|
||||
console.log('Got new figure', response)
|
||||
plots.value.push(response)
|
||||
}
|
||||
console.log('Done fetching figures')
|
||||
loading.value = false
|
||||
},
|
||||
watch: {
|
||||
selected(newVal: string[], oldVal: string[]) {
|
||||
const added = newVal.filter((x) => !oldVal.includes(x))
|
||||
const removed = oldVal.filter((x) => !newVal.includes(x))
|
||||
|
||||
for (const uuid of added) {
|
||||
console.log('Selected', uuid)
|
||||
const plot = plots.value.find((x) => x.uuid === uuid)
|
||||
emit('figure-selected', plot)
|
||||
}
|
||||
for (const uuid of removed) {
|
||||
console.log('Deselected', uuid)
|
||||
const plot = plots.value.find((x) => x.uuid === uuid)
|
||||
emit('figure-deselected', plot)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-navigation-drawer permanent width="250">
|
||||
<v-list>
|
||||
<v-list-item title="Available figures" subtitle="Choose some of the figures below" />
|
||||
</v-list>
|
||||
<v-divider />
|
||||
<v-list v-model:selected="selected" select-strategy="leaf" nav>
|
||||
<v-list-item v-for="plot in plots" :key="plot.uuid" :value="plot.uuid">
|
||||
<v-list-item-title>{{ plot.title }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-high-emphasis"
|
||||
>{{ plot.xLabel }} w/ {{ plot.yLabel }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item>
|
||||
<v-skeleton-loader
|
||||
type="list-item-two-line"
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
v-if="loading"
|
||||
></v-skeleton-loader>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
1
where_fi/visualise/frontend/src/connect.ts
Normal file
1
where_fi/visualise/frontend/src/connect.ts
Normal file
@ -0,0 +1 @@
|
||||
export const host = 'http://localhost:8080'
|
||||
24
where_fi/visualise/frontend/src/main.ts
Normal file
24
where_fi/visualise/frontend/src/main.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import App from './App.vue'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
directives,
|
||||
theme: { defaultTheme: 'dark' },
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: {
|
||||
mdi,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
createApp(App).use(vuetify).mount('#app')
|
||||
16
where_fi/visualise/frontend/tsconfig.app.json
Normal file
16
where_fi/visualise/frontend/tsconfig.app.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"vueCompilerOptions": {
|
||||
"fallthroughAttributes": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"noImplicitAny": true,
|
||||
"module": "es6",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
19
where_fi/visualise/frontend/tsconfig.node.json
Normal file
19
where_fi/visualise/frontend/tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
15
where_fi/visualise/frontend/vite.config.ts
Normal file
15
where_fi/visualise/frontend/vite.config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueDevTools()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -1,21 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.ts',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
},
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
};
|
||||
@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Preprocessing</h1>
|
||||
<div id="plot"></div>
|
||||
<script src="{{url_for('static', filename='plot.js')}}"></script>
|
||||
<input type="number" id="subcarrier" class="form-control" placeholder="Choose subcarrier" onchange="changeSubcarrier()" value="0">
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
Loading…
Reference in New Issue
Block a user