Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard can be served on a non-root path #79

Merged
merged 13 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions karton/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from typing import Any, Dict, List, Optional, Tuple

import mistune # type: ignore
from flask import ( # type: ignore
from flask import (
Blueprint,
Flask,
jsonify,
redirect,
Expand All @@ -29,11 +30,6 @@

logging.basicConfig(level=logging.INFO)

app_path = Path(__file__).parent
static_folder = app_path / "static"
graph_folder = app_path / "graph"
app = Flask(__name__, static_folder=None, template_folder=str(app_path / "templates"))


class KartonDashboard(KartonBase):
identity = "karton.dashboard"
Expand All @@ -43,6 +39,17 @@ class KartonDashboard(KartonBase):

karton = KartonDashboard()

base_path = karton.config.get("dashboard", "base_path", fallback="")

app_path = Path(__file__).parent
static_folder = app_path / "static"
graph_folder = app_path / "graph"
app = Flask(__name__, static_folder=None)
blueprint = Blueprint(
"dashboard", __name__, template_folder=str(app_path / "templates")
)


markdown = mistune.create_markdown(
escape=True,
renderer="html",
Expand Down Expand Up @@ -186,12 +193,15 @@ def get_xrefs(root_uid) -> List[Tuple[str, str]]:


def add_metrics(state: KartonState, metric: KartonMetrics, key: str) -> None:
metrics = {k: int(v) for k, v in state.backend.redis.hgetall(metric.value).items()}
metrics = {
k: int(v)
for k, v in state.backend.redis.hgetall(metric.value).items() # type: ignore
}
for name, value in metrics.items():
karton_metrics.labels(key, name).set(value)


@app.route("/varz", methods=["GET"])
@blueprint.route("/varz", methods=["GET"])
def varz():
"""Update and get prometheus metrics"""

Expand All @@ -208,7 +218,7 @@ def varz():
task_infos[(safe_name, task.priority, task.status)] += 1

# set the default of active queues to 0 to avoid gaps in graphs
for (priority, status) in product(TaskPriority, TaskState):
for priority, status in product(TaskPriority, TaskState):
karton_tasks.labels(safe_name, priority.value, status.value).set(0)

for (name, priority, status), count in task_infos.items():
Expand All @@ -226,18 +236,18 @@ def varz():
return generate_latest()


@app.route("/static/<path:path>", methods=["GET"])
@blueprint.route("/static/<path:path>", methods=["GET"])
def static(path: str):
return send_from_directory(static_folder, path)


@app.route("/", methods=["GET"])
@blueprint.route("/", methods=["GET"])
def get_queues():
state = KartonState(karton.backend)
return render_template("index.html", queues=state.queues)


@app.route("/services", methods=["GET"])
@blueprint.route("/services", methods=["GET"])
def get_services():
aggregated_services = defaultdict(list)
online_services = karton.backend.get_online_services()
Expand All @@ -246,7 +256,7 @@ def get_services():
return render_template("services.html", services=aggregated_services)


@app.route("/api/queues", methods=["GET"])
@blueprint.route("/api/queues", methods=["GET"])
def get_queues_api():
state = KartonState(karton.backend)
return jsonify(
Expand All @@ -257,7 +267,7 @@ def get_queues_api():
)


@app.route("/<queue_name>/restart_crashed", methods=["POST"])
@blueprint.route("/<queue_name>/restart_crashed", methods=["POST"])
def restart_crashed_queue_tasks(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -269,7 +279,7 @@ def restart_crashed_queue_tasks(queue_name):
return redirect(request.referrer)


@app.route("/<queue_name>/cancel_crashed", methods=["POST"])
@blueprint.route("/<queue_name>/cancel_crashed", methods=["POST"])
def cancel_crashed_queue_tasks(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -280,7 +290,7 @@ def cancel_crashed_queue_tasks(queue_name):
return redirect(request.referrer)


@app.route("/<queue_name>/cancel_pending", methods=["POST"])
@blueprint.route("/<queue_name>/cancel_pending", methods=["POST"])
def cancel_pending_queue_tasks(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -291,7 +301,7 @@ def cancel_pending_queue_tasks(queue_name):
return redirect(request.referrer)


@app.route("/restart_task/<task_id>/restart", methods=["POST"])
@blueprint.route("/restart_task/<task_id>/restart", methods=["POST"])
def restart_task(task_id):
task = karton.backend.get_task(task_id)
if not task:
Expand All @@ -301,7 +311,7 @@ def restart_task(task_id):
return redirect(request.referrer)


@app.route("/cancel_task/<task_id>/cancel", methods=["POST"])
@blueprint.route("/cancel_task/<task_id>/cancel", methods=["POST"])
def cancel_task(task_id):
task = karton.backend.get_task(task_id)
if not task:
Expand All @@ -311,7 +321,7 @@ def cancel_task(task_id):
return redirect(request.referrer)


@app.route("/queue/<queue_name>", methods=["GET"])
@blueprint.route("/queue/<queue_name>", methods=["GET"])
def get_queue(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -321,7 +331,7 @@ def get_queue(queue_name):
return render_template("queue.html", name=queue_name, queue=queue)


@app.route("/queue/<queue_name>/crashed", methods=["GET"])
@blueprint.route("/queue/<queue_name>/crashed", methods=["GET"])
def get_crashed_queue(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -331,7 +341,7 @@ def get_crashed_queue(queue_name):
return render_template("crashed.html", name=queue_name, queue=queue)


@app.route("/api/queue/<queue_name>", methods=["GET"])
@blueprint.route("/api/queue/<queue_name>", methods=["GET"])
def get_queue_api(queue_name):
state = KartonState(karton.backend)
queue = state.queues.get(queue_name)
Expand All @@ -340,7 +350,7 @@ def get_queue_api(queue_name):
return jsonify(QueueView(queue).to_dict())


@app.route("/task/<task_id>", methods=["GET"])
@blueprint.route("/task/<task_id>", methods=["GET"])
def get_task(task_id):
task = karton.backend.get_task(task_id)
if not task:
Expand All @@ -351,15 +361,15 @@ def get_task(task_id):
)


@app.route("/api/task/<task_id>", methods=["GET"])
@blueprint.route("/api/task/<task_id>", methods=["GET"])
def get_task_api(task_id):
task = karton.backend.get_task(task_id)
if not task:
return jsonify({"error": "Task doesn't exist"}), 404
return jsonify(TaskView(task).to_dict())


@app.route("/analysis/<root_id>", methods=["GET"])
@blueprint.route("/analysis/<root_id>", methods=["GET"])
def get_analysis(root_id):
state = KartonState(karton.backend)
analysis = state.analyses.get(root_id)
Expand All @@ -371,7 +381,7 @@ def get_analysis(root_id):
)


@app.route("/api/analysis/<root_id>", methods=["GET"])
@blueprint.route("/api/analysis/<root_id>", methods=["GET"])
def get_analysis_api(root_id):
state = KartonState(karton.backend)
analysis = state.analyses.get(root_id)
Expand All @@ -381,15 +391,18 @@ def get_analysis_api(root_id):
return jsonify(AnalysisView(analysis).to_dict())


@app.route("/graph", methods=["GET"])
@blueprint.route("/graph", methods=["GET"])
def get_graph():
return render_template("graph.html")


@app.route("/graph/generate", methods=["GET"])
@blueprint.route("/graph/generate", methods=["GET"])
def generate_graph():
state = KartonState(karton.backend)
graph = KartonGraph(state)
raw_graph = graph.generate_graph()

return raw_graph


app.register_blueprint(blueprint, url_prefix=base_path)
2 changes: 1 addition & 1 deletion karton/dashboard/static/graph/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ window.onresize = function() {

option = null;
myChart.showLoading('default', loadingOpts);
$.get("/graph/generate", function(raw_graph) {
$.get(dom.getAttribute('data-generate-url'), function(raw_graph) {
myChart.hideLoading();
myChart.resize();

Expand Down
4 changes: 2 additions & 2 deletions karton/dashboard/templates/analysis.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ <h4>Tasks</h4>
{% for task in queue.pending_tasks %}
<tr>
<td>
<a href="/queue/{{identity}}">{{identity}}</a>
<a href="{{ url_for('dashboard.get_queue', queue_name=identity) }}">{{identity}}</a>
</td>
<td>
<a href="/task/{{task.uid}}">{{ task.uid }}</a>
<a href="{{ url_for('dashboard.get_task', task_id=task.uid) }}">{{ task.uid }}</a>
</td>
<td>
{% if task.priority.value != 'normal' %}
Expand Down
10 changes: 5 additions & 5 deletions karton/dashboard/templates/crashed.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ <h4>
<span class="align-middle">Crashed tasks</span>
{% if queue.crashed_tasks %}
<div class="btn-group float-right">
<form action={{url_for("restart_crashed_queue_tasks", queue_name=name)}} method="POST">
<form action={{url_for('dashboard.restart_crashed_queue_tasks', queue_name=name)}} method="POST">
<button class="btn btn-sm btn-danger mx-1" type="submit" value="Submit" title="restart all tasks">Restart all</button>
</form>
<div class="divider"></div>
<form action={{url_for("cancel_crashed_queue_tasks", queue_name=name)}} method="POST" id="cancelQueue">
<form action={{url_for('dashboard.cancel_crashed_queue_tasks', queue_name=name)}} method="POST" id="cancelQueue">
<button class="btn btn-sm btn-danger" type="submit" form="cancelQueue" value="Submit" title="cancel all tasks">Cancel all</button>
</form>
</div>
Expand All @@ -28,7 +28,7 @@ <h4>
{% for task in queue.crashed_tasks|sort(attribute='last_update', reverse=True) %}
<tr>
<td>
<a href="/task/{{ task.uid }}">{{ task.uid }}</a>
<a href="{{url_for('dashboard.get_task', task_id=task.uid)}}">{{ task.uid }}</a>
</td>
<td><pre>{{ task.last_update|render_timestamp }}</pre></td>
<td>
Expand All @@ -54,15 +54,15 @@ <h4>
</td>
<td>
<div class="btn-group">
<form action={{url_for("restart_task", task_id=task.uid)}} method="POST">
<form action={{url_for('dashboard.restart_task', task_id=task.uid)}} method="POST">
<button class="btn btn-info mx-1" type="submit" value="Submit" title="restart task">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>
</button>
</form>
<form action={{url_for("cancel_task", task_id=task.uid)}} method="POST">
<form action={{url_for('dashboard.cancel_task', task_id=task.uid)}} method="POST">
<button class="btn btn-danger" type="submit" value="Submit" title="cancel task">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash-fill" viewBox="0 0 16 16">
<path d="M2.5 1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1H3v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4h.5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1H2.5zm3 4a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zM8 5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7A.5.5 0 0 1 8 5zm3 .5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 1 0z"/>
Expand Down
6 changes: 3 additions & 3 deletions karton/dashboard/templates/graph.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends 'layout.html' %}
{% block content %}
<div class="bs-component">
<link type="text/css" rel="stylesheet" href="../static/graph/style.css" />
<link rel="stylesheet" href="{{ url_for('dashboard.static', path='graph/style.css') }}">
<script
type="text/javascript"
src="https://code.jquery.com/jquery-3.4.1.min.js"
Expand Down Expand Up @@ -30,7 +30,7 @@
</div>
</div>

<div id="graph-container" style="height: 100vh;"></div>
<script src="../static/graph/graph.js"></script>
<div id="graph-container" data-generate-url="{{ url_for('dashboard.generate_graph') }}" style="height: 100vh;"></div>
<script src="{{ url_for('dashboard.static', path='graph/graph.js') }}"></script>
</div>
{% endblock %}
4 changes: 2 additions & 2 deletions karton/dashboard/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h3 class="text-center">binds</h3>
{% for (queue_name, queue) in queues|dictsort %}
<tr>
<td>
<a href="queue/{{ queue_name }}">{{ queue_name }}</a>
<a href="{{url_for('dashboard.get_queue', queue_name=queue_name)}}">{{ queue_name }}</a>
<div>
<span class="badge bg-info" title="karton-core library version">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-seam" viewBox="0 0 16 16">
Expand Down Expand Up @@ -67,7 +67,7 @@ <h3 class="text-center">binds</h3>
</td>
<td>
{% set length = queue.crashed_tasks | length %}
{% set url = url_for("get_crashed_queue", queue_name=queue_name) %}
{% set url = url_for('dashboard.get_crashed_queue', queue_name=queue_name) %}
{% set badgeClass = "bg-success" if length == 0 else "bg-danger" %}
<span class="badge {{badgeClass}}">
<a class="text-decoration-none" href={{url}} style="color: inherit">{{length}}</a>
Expand Down
12 changes: 6 additions & 6 deletions karton/dashboard/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>{% block title %}karton-dashboard{% endblock %}</title>
<link rel="stylesheet" href="/static/bootstrap.css">
<link rel="shortcut icon" href="{{ url_for('static', path='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('dashboard.static', path='bootstrap.css') }}">
<link rel="shortcut icon" href="{{ url_for('dashboard.static', path='favicon.ico') }}">
</head>

<body>
<div class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a href="/" class="navbar-brand">karton</a>
<a href="{{ url_for('dashboard.get_queues') }}" class="navbar-brand">karton</a>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">home</a>
<a class="nav-link" href="{{ url_for('dashboard.get_queues') }}">home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/services">services</a>
<a class="nav-link" href="{{ url_for('dashboard.get_services') }}">services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/graph">graph</a>
<a class="nav-link" href="{{ url_for('dashboard.get_graph') }}">graph</a>
</li>
</ul>
</div>
Expand Down
6 changes: 3 additions & 3 deletions karton/dashboard/templates/queue.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ <h4>
<span class="align-middle">Tasks</span>
{% if queue.pending_tasks %}
<div class="btn-group float-right">
<form action={{url_for("cancel_pending_queue_tasks", queue_name=name)}} method="POST" id="cancelQueue">
<form action={{url_for('dashboard.cancel_pending_queue_tasks', queue_name=name)}} method="POST" id="cancelQueue">
<button class="btn btn-sm btn-secondary" type="submit" form="cancelQueue" value="Submit" title="cancel all tasks">Cancel all</button>
</form>
</div>
Expand All @@ -23,7 +23,7 @@ <h4>
{% for task in queue.pending_tasks|sort(attribute='last_update', reverse=True) %}
<tr>
<td>
<a href="/task/{{task.uid}}">{{ task.uid }}</a>
<a href="{{ url_for('dashboard.get_task', task_id=task.uid) }}">{{ task.uid }}</a>
</td>
<td><pre>{{ task.last_update|render_timestamp }}</pre></td>
<td>
Expand All @@ -46,7 +46,7 @@ <h4>
</td>
<td>
<div class="btn-group">
<form action={{url_for("cancel_task", task_id=task.uid)}} method="POST">
<form action={{url_for('dashboard.cancel_task', task_id=task.uid)}} method="POST">
<button class="btn btn-dark" type="submit" value="Submit" title="cancel task">✕</button>
</form>
</div>
Expand Down
Loading
Loading