Rebuild topsecret dashboard for community growth.

CSS grid layout with cards for: people waiting on us (pending
namespace requests), moderation queue, spam filter catches, new
humans this week, most alive namespaces, and recent threads.
Direct, honest copy throughout.
This commit is contained in:
russell@unturf.com 2026-02-05 17:00:42 -05:00
parent 17a7b9df01
commit f70dfc9ed2
2 changed files with 318 additions and 45 deletions

View file

@ -2,51 +2,242 @@
{% block title %}{{ the_title }} | {{ request.domain }}{%- endblock -%}
{% block content -%}
<style>
.ts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5em;
margin-bottom: 2em;
}
@media (max-width: 760px) {
.ts-grid { grid-template-columns: 1fr; }
}
.ts-card {
border: 1px solid #333;
padding: 1em 1.2em;
}
.ts-card h4 {
margin: 0 0 0.5em 0;
border-bottom: 2px solid #d40000;
padding-bottom: 0.3em;
}
.ts-stat {
display: inline-block;
text-align: center;
margin-right: 1.5em;
margin-bottom: 0.5em;
}
.ts-stat .num {
display: block;
font-size: 2em;
font-weight: bold;
line-height: 1.1;
}
.ts-stat .label {
font-size: 0.85em;
opacity: 0.7;
}
.ts-badge {
display: inline-block;
background: #d40000;
color: #fff;
font-size: 0.8em;
font-weight: bold;
padding: 0.15em 0.5em;
margin-left: 0.3em;
vertical-align: middle;
}
.ts-item {
padding: 0.3em 0;
border-bottom: 1px solid rgba(128,128,128,0.2);
}
.ts-item:last-child { border-bottom: none; }
.ts-item .meta {
font-size: 0.85em;
opacity: 0.7;
}
.ts-full { grid-column: 1 / -1; }
.ts-nav {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 0.5em;
margin-bottom: 2em;
}
.ts-nav a {
display: block;
border: 1px solid #333;
padding: 0.6em 0.8em;
text-decoration: none;
text-align: center;
font-weight: bold;
}
.ts-nav a:hover {
border-color: #d40000;
}
.ts-spam-score {
display: inline-block;
font-size: 0.8em;
font-weight: bold;
padding: 0.1em 0.4em;
background: #d40000;
color: #fff;
}
</style>
<h3>{{ the_title }}</h3>
<h4>Content</h4>
<ul>
<li><a href="{{ request.route_url('topsecret') }}/nodes">All Nodes</a> &mdash; recent activity across all namespaces</li>
<li><a href="{{ request.route_url('topsecret') }}/namespaces">Namespaces</a> &mdash; list all namespaces</li>
<li><a href="{{ request.route_url('topsecret') }}/namespace-owners">Namespace Owners</a> &mdash; who owns what</li>
<li><a href="{{ request.route_url('topsecret') }}/namespace-requests">Namespace Requests</a> &mdash; pending ownership requests</li>
<li><a href="{{ request.route_url('topsecret') }}/notifications">Notifications</a> &mdash; all notifications</li>
</ul>
<h4>Users</h4>
<ul>
<li><a href="{{ request.route_url('topsecret-users') }}">Superusers</a> &mdash; promote and demote superusers</li>
</ul>
<h4>Recent Root Nodes</h4>
{% if nodes %}
{%- for node in nodes %}
<div class="summary">
<span class="author-and-date">
{% if node.user %}
{{ snippets.user_link(node.user) }}
{% else %}
<a href="{{ request.link_prefix }}/{{ node.id }}/{{ node.slug }}">thread</a>
{% endif %}
<span id="{{ node.id }}" class="date" title="{{ node.created_date }}">
{{ node.human_changed_timestamp }}
</span>
<div style="margin-bottom: 1.5em;">
<span class="ts-stat">
<span class="num">{{ total_users }}</span>
<span class="label">humans</span>
</span>
<span class="ts-stat">
<span class="num">{{ total_namespaces }}</span>
<span class="label">namespaces</span>
</span>
<span class="ts-stat">
<span class="num">{{ total_nodes }}</span>
<span class="label">nodes</span>
</span>
<span class="ts-stat">
<span class="num">{{ new_users|length }}</span>
<span class="label">new this week</span>
</span>
<br>
{% if node.has_uri -%}
<a href="{{ node.uri.data }}" target="_blank" rel="nofollow">{{ node.uri.data }}</a>
{% else -%}
<a href="{{ request.link_prefix }}/{{ node.id }}/{{ node.slug }}">{{ node.title }}</a>
{% endif -%}
<br>
<a href="{{ request.link_prefix }}/{{ node.id }}/{{ node.slug }}">
<span class="remark-count">{{ node.stats["root"]["visible_count"] }} remarks</span>
</a>
</div>
{% endfor -%}
{% else %}
<p>No root nodes found.</p>
{% endif %}
<nav class="ts-nav">
<a href="{{ request.route_url('topsecret') }}/nodes">all nodes</a>
<a href="{{ request.route_url('topsecret') }}/namespaces">namespaces</a>
<a href="{{ request.route_url('topsecret') }}/namespace-owners">owners</a>
<a href="{{ request.route_url('topsecret') }}/namespace-requests">requests</a>
<a href="{{ request.route_url('topsecret') }}/notifications">notifications</a>
<a href="{{ request.route_url('topsecret-users') }}">superusers</a>
</nav>
<div class="ts-grid">
{# --- PEOPLE WAITING ON US --- #}
<div class="ts-card">
<h4>people waiting on us {% if pending_requests %}<span class="ts-badge">{{ pending_requests|length }}</span>{% endif %}</h4>
{% if pending_requests %}
<p style="font-size:0.85em; opacity:0.7;">these people chose us over disqus. don't leave them hanging.</p>
{% for req in pending_requests %}
<div class="ts-item">
<strong>{{ req.namespace.name }}</strong>
<br>
<span class="meta">{{ req.user.name }} ({{ req.user.email }}) &mdash; {{ ago(req.created_timestamp) }}</span>
{% if req.target %}
<br><span class="meta">target: <a href="{{ req.target }}" target="_blank" rel="nofollow">{{ req.target[:60] }}{% if req.target|length > 60 %}...{% endif %}</a></span>
{% endif %}
</div>
{% endfor %}
{% else %}
<p>everyone's taken care of. nice work.</p>
{% endif %}
</div>
{# --- MODERATION QUEUE --- #}
<div class="ts-card">
<h4>needs a human eye {% if held_nodes %}<span class="ts-badge">{{ held_nodes|length }}</span>{% endif %}</h4>
{% if held_nodes %}
<p style="font-size:0.85em; opacity:0.7;">held for moderation. approve the good stuff, kill the noise.</p>
{% for node in held_nodes[:15] %}
<div class="ts-item">
<a href="{{ request.link_prefix }}/{{ node.root.id }}/{{ node.root.slug }}#{{ node.id }}">{{ (node.data[:80] + "...") if node.data|length > 80 else node.data }}</a>
<br>
<span class="meta">
{% if node.user %}{{ node.user.name }}{% elif node.user_surrogate %}{{ node.user_surrogate.name }}{% else %}?{% endif %}
&mdash; {{ node.human_created_timestamp }}
{% if node.root and node.root.namespace %} in {{ node.root.namespace.name }}{% endif %}
</span>
</div>
{% endfor %}
{% if held_nodes|length > 15 %}<p class="meta">...and {{ held_nodes|length - 15 }} more</p>{% endif %}
{% else %}
<p>queue is clean. the community is behaving.</p>
{% endif %}
</div>
{# --- SPAM FLAGGED --- #}
<div class="ts-card">
<h4>spam filter catches {% if spam_flagged %}<span class="ts-badge">{{ spam_flagged|length }}</span>{% endif %}</h4>
{% if spam_flagged %}
<p style="font-size:0.85em; opacity:0.7;">machine learning flagged these but didn't kill them. your call.</p>
{% for node in spam_flagged[:10] %}
<div class="ts-item">
<span class="ts-spam-score">{{ "%.0f"|format(node.spam_score * 100) }}%</span>
<a href="{{ request.link_prefix }}/{{ node.root.id }}/{{ node.root.slug }}#{{ node.id }}">{{ (node.data[:70] + "...") if node.data|length > 70 else node.data }}</a>
<br>
<span class="meta">
{% if node.user %}{{ node.user.name }}{% elif node.user_surrogate %}{{ node.user_surrogate.name }}{% else %}?{% endif %}
{% if node.spam_reason %}&mdash; {{ node.spam_reason[:60] }}{% endif %}
</span>
</div>
{% endfor %}
{% else %}
<p>nothing suspicious right now.</p>
{% endif %}
</div>
{# --- NEW HUMANS --- #}
<div class="ts-card">
<h4>new humans this week {% if new_users %}<span class="ts-badge">{{ new_users|length }}</span>{% endif %}</h4>
{% if new_users %}
{% for user in new_users[:20] %}
<div class="ts-item">
<a href="{{ request.link_prefix }}/u/{{ user.name }}">{{ user.name }}</a>
<span class="meta">&mdash; {{ user.email }}{% if user.verified %} (verified){% else %} (unverified){% endif %}</span>
</div>
{% endfor %}
{% if new_users|length > 20 %}<p class="meta">...and {{ new_users|length - 20 }} more</p>{% endif %}
{% else %}
<p>quiet week. that's okay.</p>
{% endif %}
</div>
{# --- MOST ALIVE NAMESPACES --- #}
<div class="ts-card ts-full">
<h4>most alive namespaces (last 30 days)</h4>
{% if active_namespaces %}
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.5em;">
{% for ns_name, ns_id, count in active_namespaces %}
<div class="ts-item">
<strong>{{ count }}</strong> <span class="meta">new threads</span>
<br>
<a href="{{ request.link_prefix }}/ns/{{ ns_name }}">{{ ns_name }}</a>
</div>
{% endfor %}
</div>
{% else %}
<p>no thread activity in the last 30 days.</p>
{% endif %}
</div>
{# --- RECENT THREADS --- #}
<div class="ts-card ts-full">
<h4>recent threads</h4>
{% if nodes %}
{%- for node in nodes %}
<div class="ts-item">
<span class="meta">
{% if node.user %}{{ snippets.user_link(node.user) }}{% endif %}
<span title="{{ node.created_date }}">{{ node.human_changed_timestamp }}</span>
{% if node.namespace %} in {{ node.namespace.name }}{% endif %}
</span>
<br>
{% if node.has_uri -%}
<a href="{{ node.uri.data }}" target="_blank" rel="nofollow">{{ node.uri.data }}</a>
{% else -%}
<a href="{{ request.link_prefix }}/{{ node.id }}/{{ node.slug }}">{{ node.title }}</a>
{% endif -%}
<span class="meta">{{ node.stats["root"]["visible_count"] }} remarks</span>
</div>
{% endfor -%}
{% else %}
<p>no threads yet. we're early.</p>
{% endif %}
</div>
</div>
{%- endblock -%}

View file

@ -1,6 +1,10 @@
import time
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from sqlalchemy import func, or_
from remarkbox.models import (
get_topsecret_namespace_requests,
get_topsecret_notifications,
@ -9,8 +13,12 @@ from remarkbox.models import (
get_topsecret_nodes,
)
from remarkbox.models.user import get_user_by_email, User
from remarkbox.models.node import Node
from remarkbox.models.namespace import Namespace
from remarkbox.models.namespace_request import NamespaceRequest
from remarkbox.models.sudo_otp import verify_sudo_otp
from remarkbox.lib import timestamp_to_ago_string
from remarkbox.lib.sudo import request_sudo_otp
from remarkbox.views import super_fly_required
@ -66,10 +74,84 @@ def topsecret_notifications(request):
@view_config(route_name="topsecret", renderer="topsecret-dashboard.j2")
@super_fly_required
def topsecret_roots(request):
def topsecret_dashboard(request):
db = request.dbsession
now_ms = int(time.time() * 1000)
seven_days_ms = 7 * 24 * 60 * 60 * 1000
thirty_days_ms = 30 * 24 * 60 * 60 * 1000
# --- Totals ---
total_users = db.query(func.count(User.id)).scalar()
total_namespaces = db.query(func.count(Namespace.id)).scalar()
total_nodes = db.query(func.count(Node.id)).scalar()
# --- People waiting on us ---
pending_requests = (
db.query(NamespaceRequest)
.filter(NamespaceRequest.verified == False)
.order_by(NamespaceRequest.created_timestamp.asc())
.all()
)
# --- New humans (last 7 days) ---
new_users = (
db.query(User)
.filter(User.created > now_ms - seven_days_ms, User.disabled == False)
.order_by(User.created.desc())
.all()
)
# --- Moderation queue ---
held_nodes = (
db.query(Node)
.filter(
or_(Node.approved == False, Node.approved.is_(None)),
Node.disabled == False,
)
.order_by(Node.created.desc())
.limit(50)
.all()
)
# --- Flagged by spam filter but not killed ---
spam_flagged = (
db.query(Node)
.filter(Node.spam_score >= 0.5, Node.disabled == False)
.order_by(Node.spam_score.desc())
.limit(50)
.all()
)
# --- Most active namespaces (last 30 days) ---
active_namespaces = (
db.query(
Namespace.name,
Namespace.id,
func.count(Node.id).label("node_count"),
)
.join(Node, Node.namespace_id == Namespace.id)
.filter(Node.created > now_ms - thirty_days_ms)
.group_by(Namespace.id)
.order_by(func.count(Node.id).desc())
.limit(20)
.all()
)
# --- Recent root nodes ---
recent_roots = get_topsecret_roots(db).limit(50)
return {
"nodes": get_topsecret_roots(request.dbsession).limit(1000),
"the_title": "topsecret dashboard",
"the_title": "topsecret",
"total_users": total_users,
"total_namespaces": total_namespaces,
"total_nodes": total_nodes,
"pending_requests": pending_requests,
"new_users": new_users,
"held_nodes": held_nodes,
"spam_flagged": spam_flagged,
"active_namespaces": active_namespaces,
"nodes": recent_roots,
"ago": timestamp_to_ago_string,
}