refined grafana and prometheus
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from . import slides_bp
|
||||
from flask import current_app, abort, redirect, url_for, request, send_from_directory
|
||||
from app.utils import get_cached_deck_info, token_required
|
||||
from app.models import Deck, db, Course
|
||||
from app.models import Deck, db, Course, PageView
|
||||
import os
|
||||
import flask_login
|
||||
import re
|
||||
@@ -115,6 +115,13 @@ def serve_deck(course_folder, deck, token, path="index.html"):
|
||||
current_app.logger.info(
|
||||
f"User {token.name} accessed deck {deck_info['deck_name']}"
|
||||
)
|
||||
try:
|
||||
pv = PageView(deck_id=deck_info["deck_id"], token_id=token.id)
|
||||
db.session.add(pv)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"Failed to log page view: {e}")
|
||||
return send_from_directory(deck_path, path)
|
||||
|
||||
log_404("File missing within deck", deck, course_folder, path, token)
|
||||
|
||||
@@ -6,20 +6,29 @@ from typing import List, Optional
|
||||
import pytz
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask import current_app
|
||||
from sqlalchemy import Boolean, Column, DateTime, String, TypeDecorator, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
String,
|
||||
TypeDecorator,
|
||||
ForeignKey,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
db: SQLAlchemy = SQLAlchemy()
|
||||
|
||||
deck_password = db.Table(
|
||||
"deck_password",
|
||||
Column("deck_id", db.Integer, db.ForeignKey("deck.id"), primary_key=True),
|
||||
Column("password_id", db.Integer, db.ForeignKey("password.id"), primary_key=True)
|
||||
Column("password_id", db.Integer, db.ForeignKey("password.id"), primary_key=True),
|
||||
)
|
||||
|
||||
deck_token = db.Table(
|
||||
"deck_token",
|
||||
Column("deck_id", db.Integer, db.ForeignKey("deck.id"), primary_key=True),
|
||||
Column("token_id", db.Integer, db.ForeignKey("token.id"), primary_key=True)
|
||||
Column("token_id", db.Integer, db.ForeignKey("token.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
@@ -38,14 +47,19 @@ class UTCDateTime(TypeDecorator):
|
||||
value = value.replace(tzinfo=pytz.utc)
|
||||
return value
|
||||
|
||||
|
||||
class Password(db.Model):
|
||||
__tablename__ = "password"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
value: Mapped[str] = mapped_column(String(60))
|
||||
expires_at: Mapped[Optional[datetime]] = mapped_column(UTCDateTime()) # Optional expiration
|
||||
expires_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
UTCDateTime()
|
||||
) # Optional expiration
|
||||
|
||||
decks : Mapped[List[Deck]] = relationship("Deck", secondary=deck_password, back_populates='passwords')
|
||||
decks: Mapped[List[Deck]] = relationship(
|
||||
"Deck", secondary=deck_password, back_populates="passwords"
|
||||
)
|
||||
|
||||
def __init__(self, value: str, expires_at: datetime | None) -> None:
|
||||
self.value = value
|
||||
@@ -53,7 +67,9 @@ class Password(db.Model):
|
||||
|
||||
def is_expired(self) -> bool:
|
||||
"""Check if the password is expired"""
|
||||
return self.expires_at is not None and datetime.now(timezone.utc) > self.expires_at
|
||||
return (
|
||||
self.expires_at is not None and datetime.now(timezone.utc) > self.expires_at
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Password(value={self.value}, expires_at={self.expires_at})>"
|
||||
@@ -62,9 +78,7 @@ class Password(db.Model):
|
||||
class Deck(db.Model):
|
||||
__tablename__ = "deck"
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint('course_id', 'deck', name='_course_deck_uc'),
|
||||
)
|
||||
__table_args__ = (UniqueConstraint("course_id", "deck", name="_course_deck_uc"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)
|
||||
@@ -73,13 +87,24 @@ class Deck(db.Model):
|
||||
index_file: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
export_file: Mapped[Optional[str]] = mapped_column(String(40), nullable=True)
|
||||
|
||||
passwords : Mapped[List[Password]] = relationship("Password", secondary=deck_password, back_populates='decks')
|
||||
tokens : Mapped[List[Token]] = relationship("Token", secondary=deck_token, back_populates='decks')
|
||||
passwords: Mapped[List[Password]] = relationship(
|
||||
"Password", secondary=deck_password, back_populates="decks"
|
||||
)
|
||||
tokens: Mapped[List[Token]] = relationship(
|
||||
"Token", secondary=deck_token, back_populates="decks"
|
||||
)
|
||||
course_id: Mapped[int] = mapped_column(ForeignKey("course.id"))
|
||||
course: Mapped['Course'] = relationship("Course", back_populates='decks')
|
||||
course: Mapped["Course"] = relationship("Course", back_populates="decks")
|
||||
|
||||
|
||||
def __init__(self, name: str, deck: str, course: Course, description: str = "", index_file: str = "index.html", export_file: str | None = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
deck: str,
|
||||
course: Course,
|
||||
description: str = "",
|
||||
index_file: str = "index.html",
|
||||
export_file: str | None = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.deck = deck
|
||||
self.description = description
|
||||
@@ -91,16 +116,25 @@ class Deck(db.Model):
|
||||
def __repr__(self) -> str:
|
||||
return f"<Deck(name={self.name}, deck={self.deck}, course={self.course}, index={self.index_file})>"
|
||||
|
||||
|
||||
class Token(db.Model):
|
||||
__tablename__ = "token"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)
|
||||
active_course_id: Mapped[int] = mapped_column(ForeignKey("course.id"), nullable=True)
|
||||
active_course: Mapped[Course] = relationship("Course", foreign_keys=[active_course_id])
|
||||
decks : Mapped[List[Deck]]= relationship("Deck", secondary=deck_token, back_populates='tokens')
|
||||
created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
|
||||
active_course_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("course.id"), nullable=True
|
||||
)
|
||||
active_course: Mapped[Course] = relationship(
|
||||
"Course", foreign_keys=[active_course_id]
|
||||
)
|
||||
decks: Mapped[List[Deck]] = relationship(
|
||||
"Deck", secondary=deck_token, back_populates="tokens"
|
||||
)
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
self.created_at = datetime.now(timezone.utc)
|
||||
self.active_course_id = current_app.config["DEFAULT_COURSE"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -109,12 +143,13 @@ class Token(db.Model):
|
||||
def is_unlocked(self, deck: str) -> bool:
|
||||
return deck in [deck.deck for deck in self.decks]
|
||||
|
||||
|
||||
class Course(db.Model):
|
||||
__tablename__ = "course"
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
folder: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(60), unique=True, nullable=False)
|
||||
decks: Mapped[List['Deck']] = relationship(back_populates='course')
|
||||
decks: Mapped[List["Deck"]] = relationship(back_populates="course")
|
||||
|
||||
def __init__(self, name: str, folder: str) -> None:
|
||||
self.name = name
|
||||
@@ -134,3 +169,19 @@ class InfoBanner(db.Model):
|
||||
self.content = content
|
||||
self.is_active = False
|
||||
|
||||
|
||||
class PageView(db.Model):
|
||||
__tablename__ = "page_view"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
deck_id: Mapped[int] = mapped_column(ForeignKey("deck.id"), nullable=False)
|
||||
token_id: Mapped[int] = mapped_column(ForeignKey("token.id"), nullable=False)
|
||||
viewed_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
|
||||
|
||||
deck: Mapped["Deck"] = relationship("Deck")
|
||||
token: Mapped["Token"] = relationship("Token")
|
||||
|
||||
def __init__(self, deck_id: int, token_id: int) -> None:
|
||||
self.deck_id = deck_id
|
||||
self.token_id = token_id
|
||||
self.viewed_at = datetime.now(timezone.utc)
|
||||
|
||||
@@ -15,6 +15,7 @@ from .models import Deck, InfoBanner, Token, db, Course
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
def init_password_generator():
|
||||
wordfile = xkcd_password.locate_wordfile()
|
||||
global words
|
||||
@@ -225,6 +226,7 @@ def get_cached_deck_info(token_id, course_folder, deck_slug):
|
||||
"auth": is_unlocked,
|
||||
"course_exists": True,
|
||||
"deck_exists": True,
|
||||
"deck_id": deck_obj.id,
|
||||
"deck_name": deck_obj.name,
|
||||
"index_file": deck_obj.index_file
|
||||
}
|
||||
|
||||
@@ -20,6 +20,22 @@
|
||||
"graphTooltip": 0,
|
||||
"id": 1,
|
||||
"links": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"name": "course_id",
|
||||
"type": "query",
|
||||
"datasource": "PostgreSQL",
|
||||
"query": "SELECT id AS __value, name AS __text FROM course ORDER BY name",
|
||||
"refresh": 2,
|
||||
"multi": false,
|
||||
"includeAll": true,
|
||||
"allValue": null,
|
||||
"hide": 0,
|
||||
"label": "Course"
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
@@ -68,7 +84,6 @@
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -76,183 +91,21 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"expr": "probe_success{job=\"blackbox_http\"}",
|
||||
"legendFormat": "{{ instance }}",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"expr": "probe_success{job=\"blackbox_tcp\"}",
|
||||
"legendFormat": "{{ instance }}",
|
||||
"refId": "B"
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Service Health",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "ms"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"expr": "probe_duration_seconds{job=\"blackbox_http\"} * 1000",
|
||||
"legendFormat": "{{ instance }}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "HTTP Response Time (ms)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "yellow",
|
||||
"value": 95
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 99
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "Prometheus",
|
||||
"expr": "avg_over_time(probe_success{job=\"blackbox_http\", instance=\"http://tutortool:5000/grafana/health\"}[24h]) * 100",
|
||||
"legendFormat": "Flask App",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Uptime % (24h)",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"fieldConfig": {
|
||||
@@ -264,10 +117,6 @@
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -286,7 +135,6 @@
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -294,7 +142,6 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
@@ -321,10 +168,6 @@
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -343,7 +186,6 @@
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -351,7 +193,6 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
@@ -378,10 +219,6 @@
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -400,7 +237,6 @@
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -408,7 +244,6 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
@@ -456,7 +291,6 @@
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -464,7 +298,6 @@
|
||||
"fields": "",
|
||||
"values": true
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
@@ -480,93 +313,6 @@
|
||||
"title": "Users per Active Course",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"barRadius": 0,
|
||||
"barWidth": 0.7,
|
||||
"fullHighlight": false,
|
||||
"groupWidth": 0.7,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "never",
|
||||
"stacking": "none",
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
},
|
||||
"xTickLabelRotation": 0,
|
||||
"xTickLabelSpacing": 0
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"format": "table",
|
||||
"rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'GRNVS Tutorium SS 25' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Users with Access per Deck — GRNVS Tutorium SS 25",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"fieldConfig": {
|
||||
@@ -602,10 +348,6 @@
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -616,9 +358,12 @@
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 26
|
||||
"y": 16
|
||||
},
|
||||
"id": 9,
|
||||
"id": 8,
|
||||
"repeat": "course_id",
|
||||
"repeatDirection": "v",
|
||||
"maxPerRow": 1,
|
||||
"options": {
|
||||
"barRadius": 0,
|
||||
"barWidth": 0.7,
|
||||
@@ -641,45 +386,147 @@
|
||||
"xTickLabelRotation": 0,
|
||||
"xTickLabelSpacing": 0
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "auto",
|
||||
"stacking": "none",
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
},
|
||||
"xField": "Deck",
|
||||
"xTickLabelRotation": 0,
|
||||
"xTickLabelSpacing": 0
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"format": "table",
|
||||
"rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.name = 'IT-Sec Tutorium WS 25/26' GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST",
|
||||
"rawSql": "SELECT d.name AS \"Deck\", COUNT(dt.token_id) AS \"Users\" FROM deck d JOIN course c ON d.course_id = c.id LEFT JOIN deck_token dt ON d.id = dt.deck_id WHERE c.id = ${course_id} GROUP BY d.name ORDER BY regexp_replace(d.name, '\\D', '', 'g')::integer ASC NULLS LAST",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Users with Access per Deck — IT-Sec Tutorium WS 25/26",
|
||||
"title": "Users with Access per Deck \u2014 $course_id",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "Views",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 2,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 100
|
||||
},
|
||||
"id": 10,
|
||||
"repeat": "course_id",
|
||||
"repeatDirection": "v",
|
||||
"maxPerRow": 1,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.3",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"format": "time_series",
|
||||
"rawSql": "SELECT date_trunc('hour', pv.viewed_at) AS time, d.name AS metric, COUNT(*) AS value FROM page_view pv JOIN deck d ON pv.deck_id = d.id JOIN course c ON d.course_id = c.id WHERE c.id = ${course_id} AND $__timeFilter(pv.viewed_at) GROUP BY 1, 2 ORDER BY 1",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Deck Views Over Time \u2014 $course_id",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "User Registrations Over Time",
|
||||
"type": "timeseries",
|
||||
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 300 },
|
||||
"datasource": "PostgreSQL",
|
||||
"options": {
|
||||
"legend": { "displayMode": "list", "placement": "bottom", "showLegend": false },
|
||||
"tooltip": { "mode": "single", "sort": "none" }
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 80,
|
||||
"lineWidth": 1,
|
||||
"showPoints": "never"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": "PostgreSQL",
|
||||
"format": "time_series",
|
||||
"rawSql": "SELECT date_trunc('day', created_at) AS time, COUNT(*) AS value FROM token WHERE $__timeFilter(created_at) GROUP BY 1 ORDER BY 1",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "1m",
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"from": "now-7d",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Tutor Tool",
|
||||
"uid": "tutor-tool",
|
||||
"version": 3
|
||||
"version": 5
|
||||
}
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
modules:
|
||||
http_2xx:
|
||||
prober: http
|
||||
timeout: 5s
|
||||
http:
|
||||
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||
valid_status_codes: [200]
|
||||
method: GET
|
||||
fail_if_ssl: false
|
||||
fail_if_not_ssl: false
|
||||
|
||||
tcp_connect:
|
||||
prober: tcp
|
||||
timeout: 5s
|
||||
|
||||
@@ -3,25 +3,6 @@ global:
|
||||
evaluation_interval: 30s
|
||||
|
||||
scrape_configs:
|
||||
|
||||
# --- Health checks via Blackbox Exporter ---
|
||||
- job_name: 'blackbox_http'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx]
|
||||
static_configs:
|
||||
- targets:
|
||||
- http://tutortool:5000/grafana/health
|
||||
- http://tutortool:5000/
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# --- TCP check for Postgres ---
|
||||
- job_name: 'blackbox_tcp'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
@@ -30,6 +11,7 @@ scrape_configs:
|
||||
- targets:
|
||||
- db:5432
|
||||
- redis:6379
|
||||
- tutortool:5000
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
@@ -37,13 +19,3 @@ scrape_configs:
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# --- Blackbox exporter itself ---
|
||||
- job_name: 'blackbox_exporter'
|
||||
static_configs:
|
||||
- targets: ['blackbox-exporter:9115']
|
||||
|
||||
# --- Prometheus itself ---
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
Reference in New Issue
Block a user