Implement event log
This commit is contained in:
parent
8886b23434
commit
1a46cf7ef3
@ -4,13 +4,10 @@
|
|||||||
|
|
||||||
import { user } from "./stores";
|
import { user } from "./stores";
|
||||||
|
|
||||||
import Navbar from "./Navbar.svelte";
|
|
||||||
import P404 from "./pages/P404.svelte";
|
import P404 from "./pages/P404.svelte";
|
||||||
import Mining from "./pages/mining/Mining.svelte";
|
import Mining from "./pages/mining/Mining.svelte";
|
||||||
import Footer from "./Footer.svelte";
|
|
||||||
import BaseLayout from "./BaseLayout.svelte";
|
import BaseLayout from "./BaseLayout.svelte";
|
||||||
import Monitoring from "./pages/monitoring/Monitoring.svelte";
|
import Monitoring from "./pages/monitoring/Monitoring.svelte";
|
||||||
import Bar from "./pages/playground/Bar.svelte";
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const res = await fetch("/user/me");
|
const res = await fetch("/user/me");
|
||||||
@ -27,23 +24,6 @@
|
|||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
</Route>
|
</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/*">
|
<Route path="monitoring/*">
|
||||||
<BaseLayout><Monitoring /></BaseLayout>
|
<BaseLayout><Monitoring /></BaseLayout>
|
||||||
</Route>
|
</Route>
|
||||||
|
@ -30,8 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="navbar-menu {menu_open ? 'is-active' : ''}">
|
<div class="navbar-menu {menu_open ? 'is-active' : ''}">
|
||||||
<div class="navbar-start">
|
<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="/monitoring" text="Monitoring" icon="fas fa-binoculars" />
|
||||||
<Navlink target="/mining" text="Mining" icon="fas fa-person-digging" />
|
<Navlink target="/mining" text="Mining" icon="fas fa-person-digging" />
|
||||||
<Navlink target="/stats" text="Statistics" icon="fas fa-chart-line" />
|
<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">
|
<script lang="ts">
|
||||||
import { Link, Route, useParams } from "svelte-navigator";
|
import { Link, Route, useParams } from "svelte-navigator";
|
||||||
|
import Events from "./Events.svelte";
|
||||||
import Viewer from "./Viewer.svelte";
|
import Viewer from "./Viewer.svelte";
|
||||||
|
|
||||||
const uuids = ["8b9faf9f-9470-4a50-b405-0af5f0152550"];
|
const uuids = ["8b9faf9f-9470-4a50-b405-0af5f0152550"];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<Route path="/">
|
<div class="container">
|
||||||
<div class="content">
|
<Route path="/">
|
||||||
<p>Yo wassup</p>
|
<p>Yo wassup</p>
|
||||||
<ul>
|
<ul>
|
||||||
{#each uuids as id}
|
{#each uuids as id}
|
||||||
<li><Link to="./{id}">{id}</Link></li>
|
<li><Link to="./{id}">{id}</Link></li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<p>eventeroni</p>
|
||||||
</Route>
|
<Events />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path=":id" let:params>
|
<Route path=":id" let:params>
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">ASSUMING DIRECT CONTROL</h1>
|
<h1 class="title">ASSUMING DIRECT CONTROL</h1>
|
||||||
<Viewer uuid={params.id} />
|
<Viewer uuid={params.id} />
|
||||||
<Link to="..">fuck go back</Link>
|
<Link to="..">fuck go back</Link>
|
||||||
</div>
|
</Route>
|
||||||
</Route>
|
</div>
|
||||||
</section>
|
</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 .map_tiles import map_tiles, map_meta
|
||||||
from .templates import j2env
|
from .templates import j2env
|
||||||
from .monitoring import monitoring, ws_manager
|
from .monitoring import monitoring, ws_manager
|
||||||
|
from .api import api
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ app.mount("/user/", user_auth)
|
|||||||
app.mount("/map/", map_meta)
|
app.mount("/map/", map_meta)
|
||||||
app.mount("/tiles/", map_tiles)
|
app.mount("/tiles/", map_tiles)
|
||||||
app.mount("/ipmi/", monitoring)
|
app.mount("/ipmi/", monitoring)
|
||||||
|
app.mount("/api/", api)
|
||||||
|
|
||||||
installer = j2env.get_template("install.lua").render(deploy_path=settings.deploy_path)
|
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
|
from bson import ObjectId
|
||||||
import motor.motor_asyncio as motor
|
import motor.motor_asyncio as motor
|
||||||
from .settings import settings
|
from .settings import settings
|
||||||
@ -8,6 +10,16 @@ db = client["controlpanel"]
|
|||||||
events: motor.AsyncIOMotorCollection = db["events"]
|
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):
|
class PyObjectId(ObjectId):
|
||||||
@classmethod
|
@classmethod
|
||||||
def __get_validators__(cls):
|
def __get_validators__(cls):
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from datetime import datetime
|
|
||||||
from bson import ObjectId
|
|
||||||
|
|
||||||
from fastapi import (
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||||
Body,
|
from pydantic import BaseModel, ValidationError
|
||||||
FastAPI,
|
|
||||||
HTTPException,
|
|
||||||
WebSocket,
|
|
||||||
WebSocketDisconnect,
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from pydantic import BaseModel, Field, ValidationError
|
|
||||||
|
|
||||||
from .db import PyObjectId, events
|
from .db import create_event
|
||||||
|
|
||||||
monitoring = FastAPI()
|
monitoring = FastAPI()
|
||||||
|
|
||||||
@ -75,7 +65,7 @@ class WSManager:
|
|||||||
await socket.close()
|
await socket.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"[WS] Computer {uuid} connected")
|
await create_event({"type": "computer_connect", "computer_id": str(uuid)})
|
||||||
self.computers[uuid] = socket
|
self.computers[uuid] = socket
|
||||||
|
|
||||||
if len(self.viewers.get(uuid, [])) > 0:
|
if len(self.viewers.get(uuid, [])) > 0:
|
||||||
@ -96,7 +86,7 @@ class WSManager:
|
|||||||
break
|
break
|
||||||
|
|
||||||
del self.computers[uuid]
|
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):
|
async def on_browser_connect(self, socket: WebSocket, uuid: UUID):
|
||||||
print(f"[WS] Browser connected for {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):
|
async def browser_ws(socket: WebSocket, uuid: UUID):
|
||||||
await socket.accept()
|
await socket.accept()
|
||||||
await ws_manager.on_browser_connect(socket, uuid)
|
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