Implement event log
This commit is contained in:
parent
8886b23434
commit
1a46cf7ef3
@ -4,13 +4,10 @@
|
||||
|
||||
import { user } from "./stores";
|
||||
|
||||
import Navbar from "./Navbar.svelte";
|
||||
import P404 from "./pages/P404.svelte";
|
||||
import Mining from "./pages/mining/Mining.svelte";
|
||||
import Footer from "./Footer.svelte";
|
||||
import BaseLayout from "./BaseLayout.svelte";
|
||||
import Monitoring from "./pages/monitoring/Monitoring.svelte";
|
||||
import Bar from "./pages/playground/Bar.svelte";
|
||||
|
||||
onMount(async () => {
|
||||
const res = await fetch("/user/me");
|
||||
@ -27,23 +24,6 @@
|
||||
</BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="foo">
|
||||
<BaseLayout>
|
||||
<section class="hero is-danger is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="">
|
||||
<p class="title">Fullheight hero</p>
|
||||
<p class="subtitle">Fullheight subtitle</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="bar">
|
||||
<BaseLayout><Bar /></BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="monitoring/*">
|
||||
<BaseLayout><Monitoring /></BaseLayout>
|
||||
</Route>
|
||||
|
@ -30,8 +30,6 @@
|
||||
</div>
|
||||
<div class="navbar-menu {menu_open ? 'is-active' : ''}">
|
||||
<div class="navbar-start">
|
||||
<Navlink target="/foo" text="Foo" icon="fas fa-hippo" />
|
||||
<Navlink target="/bar" text="Bar" icon="fas fa-otter" />
|
||||
<Navlink target="/monitoring" text="Monitoring" icon="fas fa-binoculars" />
|
||||
<Navlink target="/mining" text="Mining" icon="fas fa-person-digging" />
|
||||
<Navlink target="/stats" text="Statistics" icon="fas fa-chart-line" />
|
||||
|
95
frontend/src/pages/monitoring/Event.svelte
Normal file
95
frontend/src/pages/monitoring/Event.svelte
Normal file
@ -0,0 +1,95 @@
|
||||
<script lang="ts" context="module">
|
||||
type Event = {
|
||||
icon: string;
|
||||
text: string;
|
||||
};
|
||||
const mappings: Map<string, (any) => Event> = new Map([
|
||||
[
|
||||
"computer_connect",
|
||||
(value) => ({
|
||||
icon: "fas fa-circle-plus",
|
||||
text: `Computer ${value.computer_id} connected`,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"computer_disconnect",
|
||||
(value) => ({
|
||||
icon: "fas fa-circle-minus",
|
||||
text: `Computer ${value.computer_id} disconnected`,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let timestamp: string;
|
||||
export let value: any;
|
||||
|
||||
let icon = "far fa-circle-question";
|
||||
let text = "Unknown event";
|
||||
|
||||
if ("type" in value) {
|
||||
let m = mappings.get(value.type);
|
||||
if (m) {
|
||||
let mapped = m(value);
|
||||
icon = mapped.icon;
|
||||
text = mapped.text;
|
||||
}
|
||||
}
|
||||
|
||||
let datetime = new Date(timestamp);
|
||||
|
||||
let expanded = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="entry"
|
||||
on:click={() => {
|
||||
expanded = !expanded;
|
||||
}}
|
||||
>
|
||||
<div class="headers">
|
||||
<div><span class="icon"><i class={icon} /></span></div>
|
||||
<div class="header-title">{text}</div>
|
||||
<div>{datetime}</div>
|
||||
<div>
|
||||
<span class="icon"
|
||||
><i
|
||||
class="fas {expanded ? 'fa-angle-up' : 'fa-angle-down'}"
|
||||
/></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#if expanded}
|
||||
<div class="expandable">
|
||||
<pre>{JSON.stringify(value, null, 1)}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.entry {
|
||||
background-color: #fafafa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
border-top: 1px solid #dbdbdb;
|
||||
}
|
||||
|
||||
.headers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
</style>
|
34
frontend/src/pages/monitoring/Events.svelte
Normal file
34
frontend/src/pages/monitoring/Events.svelte
Normal file
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Event from "./Event.svelte";
|
||||
|
||||
const ENDPOINT = "/api/events";
|
||||
|
||||
let events: { _id: string; timestamp: string; value: any }[] = [];
|
||||
|
||||
onMount(() => {
|
||||
let int = setInterval(async () => {
|
||||
let r = await fetch(ENDPOINT);
|
||||
events = await r.json();
|
||||
}, 200);
|
||||
|
||||
return () => {
|
||||
clearInterval(int);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="events">
|
||||
{#each events as event (event._id)}
|
||||
<Event {...event} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.events {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
@ -1,27 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Link, Route, useParams } from "svelte-navigator";
|
||||
import Events from "./Events.svelte";
|
||||
import Viewer from "./Viewer.svelte";
|
||||
|
||||
const uuids = ["8b9faf9f-9470-4a50-b405-0af5f0152550"];
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<Route path="/">
|
||||
<div class="content">
|
||||
<div class="container">
|
||||
<Route path="/">
|
||||
<p>Yo wassup</p>
|
||||
<ul>
|
||||
{#each uuids as id}
|
||||
<li><Link to="./{id}">{id}</Link></li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</Route>
|
||||
<p>eventeroni</p>
|
||||
<Events />
|
||||
</Route>
|
||||
|
||||
<Route path=":id" let:params>
|
||||
<div class="container">
|
||||
<Route path=":id" let:params>
|
||||
<h1 class="title">ASSUMING DIRECT CONTROL</h1>
|
||||
<Viewer uuid={params.id} />
|
||||
<Link to="..">fuck go back</Link>
|
||||
</div>
|
||||
</Route>
|
||||
</Route>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from "svelte";
|
||||
import Canvas from "./Canvas.svelte";
|
||||
|
||||
let colors = ["#ff6600", "#00ff66", "#6600ff"];
|
||||
|
||||
let i = 0;
|
||||
$: color = colors[i];
|
||||
|
||||
let int = setInterval(() => {
|
||||
i = (i + 1) % colors.length;
|
||||
}, 1000);
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(int);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<Canvas {color} />
|
||||
</section>
|
@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let color: string;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
let redraws = 0;
|
||||
|
||||
function redraw(color) {
|
||||
if (!canvas) return;
|
||||
|
||||
redraws += 1;
|
||||
let cx = canvas.getContext("2d");
|
||||
cx.fillStyle = color;
|
||||
cx.fillRect(0, 0, cx.canvas.width, cx.canvas.height);
|
||||
}
|
||||
|
||||
$: redraw(color);
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas} />
|
||||
<pre>color: {color}
|
||||
redraws: {redraws}</pre>
|
@ -9,6 +9,7 @@ from .user import user_auth
|
||||
from .map_tiles import map_tiles, map_meta
|
||||
from .templates import j2env
|
||||
from .monitoring import monitoring, ws_manager
|
||||
from .api import api
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@ -16,6 +17,7 @@ app.mount("/user/", user_auth)
|
||||
app.mount("/map/", map_meta)
|
||||
app.mount("/tiles/", map_tiles)
|
||||
app.mount("/ipmi/", monitoring)
|
||||
app.mount("/api/", api)
|
||||
|
||||
installer = j2env.get_template("install.lua").render(deploy_path=settings.deploy_path)
|
||||
|
||||
|
40
server/server/api.py
Normal file
40
server/server/api.py
Normal file
@ -0,0 +1,40 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from bson import ObjectId
|
||||
|
||||
from fastapi import Body, FastAPI, status
|
||||
from pydantic import BaseModel, Field
|
||||
import pymongo
|
||||
|
||||
from .db import events, PyObjectId, create_event
|
||||
|
||||
api = FastAPI()
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
||||
timestamp: datetime
|
||||
value: Any
|
||||
|
||||
class Config:
|
||||
json_encoders = {ObjectId: str}
|
||||
|
||||
|
||||
@api.get("/events", response_model=list[Event])
|
||||
async def get_events():
|
||||
return await events.find().sort("timestamp", pymongo.DESCENDING).to_list(None)
|
||||
|
||||
|
||||
"""
|
||||
@api.get("/events/{id}", response_model=Event)
|
||||
async def get_single_event(id: PyObjectId):
|
||||
if (event := await events.find_one({"_id": id})) is not None:
|
||||
return event
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
"""
|
||||
|
||||
# TODO auth
|
||||
@api.post("/events", response_model=Event, status_code=status.HTTP_201_CREATED)
|
||||
async def push_event(value: Any = Body(...)):
|
||||
return await create_event(value)
|
@ -1,3 +1,5 @@
|
||||
from datetime import datetime
|
||||
|
||||
from bson import ObjectId
|
||||
import motor.motor_asyncio as motor
|
||||
from .settings import settings
|
||||
@ -8,6 +10,16 @@ db = client["controlpanel"]
|
||||
events: motor.AsyncIOMotorCollection = db["events"]
|
||||
|
||||
|
||||
async def create_event(value: any):
|
||||
event = {
|
||||
"timestamp": datetime.now(),
|
||||
"value": value,
|
||||
}
|
||||
new_event = await events.insert_one(event)
|
||||
created_event = await events.find_one({"_id": new_event.inserted_id})
|
||||
return created_event
|
||||
|
||||
|
||||
class PyObjectId(ObjectId):
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
|
@ -1,21 +1,11 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from bson import ObjectId
|
||||
|
||||
from fastapi import (
|
||||
Body,
|
||||
FastAPI,
|
||||
HTTPException,
|
||||
WebSocket,
|
||||
WebSocketDisconnect,
|
||||
status,
|
||||
)
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel, Field, ValidationError
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from .db import PyObjectId, events
|
||||
from .db import create_event
|
||||
|
||||
monitoring = FastAPI()
|
||||
|
||||
@ -75,7 +65,7 @@ class WSManager:
|
||||
await socket.close()
|
||||
return
|
||||
|
||||
print(f"[WS] Computer {uuid} connected")
|
||||
await create_event({"type": "computer_connect", "computer_id": str(uuid)})
|
||||
self.computers[uuid] = socket
|
||||
|
||||
if len(self.viewers.get(uuid, [])) > 0:
|
||||
@ -96,7 +86,7 @@ class WSManager:
|
||||
break
|
||||
|
||||
del self.computers[uuid]
|
||||
print(f"[WS] Computer {uuid} disconnected")
|
||||
await create_event({"type": "computer_disconnect", "computer_id": str(uuid)})
|
||||
|
||||
async def on_browser_connect(self, socket: WebSocket, uuid: UUID):
|
||||
print(f"[WS] Browser connected for {uuid}")
|
||||
@ -135,41 +125,3 @@ async def computer_ws(socket: WebSocket, uuid: UUID):
|
||||
async def browser_ws(socket: WebSocket, uuid: UUID):
|
||||
await socket.accept()
|
||||
await ws_manager.on_browser_connect(socket, uuid)
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
||||
timestamp: datetime
|
||||
value: Any
|
||||
|
||||
class Config:
|
||||
allow_population_by_field_name = True
|
||||
arbitrary_types_allowed = True
|
||||
json_encoders = {ObjectId: str}
|
||||
|
||||
|
||||
@monitoring.get("/events", response_model=list[Event])
|
||||
async def get_events():
|
||||
print("get /events")
|
||||
return await events.find().to_list(1000)
|
||||
|
||||
|
||||
@monitoring.get("/events/{id}", response_model=Event)
|
||||
async def get_single_event(id: PyObjectId):
|
||||
if (event := await events.find_one({"_id": id})) is not None:
|
||||
return event
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
@monitoring.post(
|
||||
"/push_event", response_model=Event, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def push_event(value: Any = Body(...)):
|
||||
event = {
|
||||
"timestamp": datetime.now(),
|
||||
"value": value,
|
||||
}
|
||||
new_event = await events.insert_one(event)
|
||||
created_event = await events.find_one({"_id": new_event.inserted_id})
|
||||
return created_event
|
||||
|
Loading…
Reference in New Issue
Block a user