slimming doorboy admin methods
- enable/disable unused and broken, edit is good enough - admin use-case last many years has only been 'when has x' - can be expanded to 'who at x time' - current was broken anyway
This commit is contained in:
@@ -1,16 +1,13 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import const
|
import const
|
||||||
import pytz
|
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from common import User
|
from common import User
|
||||||
from dateutil.parser import ParserError, parse
|
|
||||||
from flask import Blueprint, abort, g, redirect, render_template, request
|
from flask import Blueprint, abort, g, redirect, render_template, request
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from oidc import login_required, read_user
|
from oidc import login_required, read_user
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from wtforms import (BooleanField, IntegerField, SelectField, StringField,
|
from wtforms import BooleanField, IntegerField, SelectField, StringField, validators
|
||||||
validators)
|
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
page_doorboy = Blueprint("doorboy", __name__)
|
page_doorboy = Blueprint("doorboy", __name__)
|
||||||
@@ -41,48 +38,6 @@ def view_doorboy_claim(event_id):
|
|||||||
|
|
||||||
return redirect("/m/doorboy")
|
return redirect("/m/doorboy")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/<token_id>/disable")
|
|
||||||
@login_required
|
|
||||||
def view_doorboy_disable(token_id):
|
|
||||||
user = read_user()
|
|
||||||
db.inventory.update_one({
|
|
||||||
"component": "doorboy",
|
|
||||||
"type": "token",
|
|
||||||
"_id": ObjectId(token_id),
|
|
||||||
"inventory.owner.username": user["username"]
|
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
"token.disabled": datetime.utcnow()
|
|
||||||
},
|
|
||||||
"$unset": {
|
|
||||||
"token.enabled": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
return redirect("/m/doorboy")
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/<token_id>/enable")
|
|
||||||
@login_required
|
|
||||||
def view_doorboy_enable(token_id):
|
|
||||||
user = read_user()
|
|
||||||
db.inventory.update_one({
|
|
||||||
"component": "doorboy",
|
|
||||||
"type": "token",
|
|
||||||
"_id": ObjectId(token_id),
|
|
||||||
"inventory.owner.username": user["username"]
|
|
||||||
}, {
|
|
||||||
"$set": {
|
|
||||||
"token.enabled": datetime.utcnow(),
|
|
||||||
},
|
|
||||||
"$unset": {
|
|
||||||
"token.disabled": ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return redirect("/m/doorboy")
|
|
||||||
|
|
||||||
|
|
||||||
class TokenEditForm(FlaskForm):
|
class TokenEditForm(FlaskForm):
|
||||||
comment = StringField("Comment")
|
comment = StringField("Comment")
|
||||||
enabled = BooleanField("Enabled")
|
enabled = BooleanField("Enabled")
|
||||||
@@ -148,6 +103,7 @@ def view_doorboy_hold():
|
|||||||
})
|
})
|
||||||
return redirect("/m/doorboy")
|
return redirect("/m/doorboy")
|
||||||
|
|
||||||
|
# Writes open event to log, which is picked up by doorboy-proxy.
|
||||||
@page_doorboy.route("/m/doorboy/<door>/open")
|
@page_doorboy.route("/m/doorboy/<door>/open")
|
||||||
@login_required
|
@login_required
|
||||||
def view_doorboy_open(door):
|
def view_doorboy_open(door):
|
||||||
@@ -182,14 +138,9 @@ def view_doorboy_open(door):
|
|||||||
def view_doorboy():
|
def view_doorboy():
|
||||||
user = read_user()
|
user = read_user()
|
||||||
workshop_access = "k-space:workshop" in g.users_lookup.get(user["username"], User()).groups
|
workshop_access = "k-space:workshop" in g.users_lookup.get(user["username"], User()).groups
|
||||||
# latest_events = db.eventlog.find({"component": "doorboy", "type":"open-door"}).sort([("timestamp", -1)]).limit(10);
|
|
||||||
latest_swipes = db.inventory.find({"component": "doorboy", "type":"token"}).sort([("last_seen", -1)]).limit(10);
|
|
||||||
return render_template("doorboy.html", **locals())
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/user/<username>/cards")
|
latest_swipes = db.inventory.find({"component": "doorboy", "type":"token"}).sort([("last_seen", -1)]).limit(10)
|
||||||
@login_required(groups=["k-space:board", "k-space:kubernetes:admins"])
|
return render_template("doorboy.html", **locals())
|
||||||
def view_user_cards(username):
|
|
||||||
return view_user_cards_inner(username)
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/me")
|
@page_doorboy.route("/m/doorboy/me")
|
||||||
@login_required
|
@login_required
|
||||||
@@ -210,71 +161,9 @@ def view_user_cards_inner(username):
|
|||||||
}).sort([("last_seen", -1)])
|
}).sort([("last_seen", -1)])
|
||||||
return render_template("doorboy_user.html", **locals())
|
return render_template("doorboy_user.html", **locals())
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/admin")
|
#TODO: only returns UID opens, not web or slack
|
||||||
|
@page_doorboy.route("/m/doorboy/log/<username>")
|
||||||
@login_required(groups=["k-space:board", "k-space:kubernetes:admins"])
|
@login_required(groups=["k-space:board", "k-space:kubernetes:admins"])
|
||||||
def view_doorboy_admin():
|
def view_user_events(username):
|
||||||
results = db.inventory.aggregate([
|
latest_events = db.eventlog.find({"component": "doorboy", "inventory.owner_id": username}).sort([("timestamp", -1)])
|
||||||
{ "$match": {"component": "doorboy", "type": "token"} },
|
return render_template("doorboy_log.html", latest_events=latest_events)
|
||||||
{
|
|
||||||
"$group": {
|
|
||||||
"_id": "$inventory.owner.username",
|
|
||||||
"cards": {
|
|
||||||
"$push" : {"$mergeObjects": [
|
|
||||||
"$token",
|
|
||||||
{"last_seen": "$last_seen"},
|
|
||||||
{"_id": "$_id"},
|
|
||||||
{"old_display_name": "$inventory.owner.display_name"},
|
|
||||||
{"old_foreign_id": "$inventory.owner.foreign_id"}
|
|
||||||
]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "$sort": { "_id" : 1 } }
|
|
||||||
])
|
|
||||||
user_keyfobs = {r["_id"] : r["cards"] for r in results}
|
|
||||||
|
|
||||||
orphaned_keyfobs = user_keyfobs.pop(None)
|
|
||||||
no_keyfobs = [u for u in g.users if not user_keyfobs.get(u.username)]
|
|
||||||
no_keyfobs = list(filter(lambda u : u.user_type == "person", no_keyfobs))
|
|
||||||
last_seen = {key : max(datetime_handle(card.get("last_seen")) for card in value) for key, value in user_keyfobs.items()}
|
|
||||||
|
|
||||||
orphaned_keyfobs = sorted(orphaned_keyfobs, key = lambda o : (not bool(o.get("comment")), o.get("comment", "")))
|
|
||||||
no_keyfobs = sorted(no_keyfobs, key = lambda u : u.display_name or u.username)
|
|
||||||
last_seen = dict(sorted(last_seen.items(), key=lambda i : datetime_handle(i[1]), reverse=True))
|
|
||||||
return render_template("doorboy_admin.html", **locals())
|
|
||||||
|
|
||||||
def datetime_handle(item):
|
|
||||||
if not item:
|
|
||||||
dt = datetime.min
|
|
||||||
elif type(item) is str:
|
|
||||||
try:
|
|
||||||
dt = parse(item)
|
|
||||||
except ParserError as e:
|
|
||||||
print(e)
|
|
||||||
dt = datetime.min
|
|
||||||
elif type(item) is datetime:
|
|
||||||
dt = item
|
|
||||||
else:
|
|
||||||
dt = datetime.min
|
|
||||||
|
|
||||||
try:
|
|
||||||
dt = pytz.UTC.localize(dt)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return dt
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/swipes")
|
|
||||||
@login_required
|
|
||||||
def view_doorboy_events():
|
|
||||||
user = read_user()
|
|
||||||
latest_events = db.eventlog.find({"component": "doorboy", "event":"card-swiped"}).sort([("timestamp", -1)]).limit(500);
|
|
||||||
return render_template("doorboy.html", **locals())
|
|
||||||
|
|
||||||
|
|
||||||
@page_doorboy.route("/m/doorboy/<token_id>/events")
|
|
||||||
@login_required
|
|
||||||
def view_doorboy_token_events(token_id):
|
|
||||||
user = read_user()
|
|
||||||
token = db.inventory.find_one({"_id": ObjectId(token_id)})
|
|
||||||
latest_events = db.eventlog.find({"component": "doorboy", "event":"card-swiped", "token.uid_hash": token.get("token").get("uid_hash")}).sort([("timestamp", -1)])
|
|
||||||
return render_template("doorboy.html", **locals())
|
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<ul class="collapsible expandable">
|
|
||||||
<li class="">
|
|
||||||
<div class="collapsible-header">
|
|
||||||
<i class="material-icons">access_time</i>Last seen
|
|
||||||
<div style="margin-left: auto;">
|
|
||||||
{{last_seen | length}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="collapsible-body collection collapsible-collection">
|
|
||||||
{% for u, t in last_seen.items() %}
|
|
||||||
<li class="collection-item">
|
|
||||||
<a href="/m/doorboy/user/{{u}}/cards">
|
|
||||||
<i class="material-icons tiny">person</i>
|
|
||||||
{{u | display_name}}
|
|
||||||
</a>
|
|
||||||
<div class="secondary-content black-text">
|
|
||||||
<i class="material-icons tiny">access_time</i>
|
|
||||||
{{t | timeago}}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="red lighten-3 collapsible-header">
|
|
||||||
<i class="material-icons">error_outline</i>No keyfobs enrolled
|
|
||||||
<div style="margin-left: auto;">
|
|
||||||
{{no_keyfobs | length}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="collapsible-body collection collapsible-collection">
|
|
||||||
{% for u in no_keyfobs %}
|
|
||||||
<div class="collection-item">{{u.display_name or u.username}}</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="red lighten-3 collapsible-header">
|
|
||||||
<i class="material-icons">error</i>Orphaned keyfobs
|
|
||||||
<div style="margin-left: auto;">
|
|
||||||
{{orphaned_keyfobs | length}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="collapsible-body collapsible-collection">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Comment</th>
|
|
||||||
<th>Hash tail</th>
|
|
||||||
<th>Old ownership info</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for c in orphaned_keyfobs %}
|
|
||||||
<tr>
|
|
||||||
<td>{{c.comment or "unnamed"}}</td>
|
|
||||||
<td><a href="/m/doorboy/{{ c._id }}/events">{{ c.uid_hash[-6:] }}</a></td>
|
|
||||||
<td>
|
|
||||||
{{c.old_display_name}}
|
|
||||||
{% if c.old_foreign_id %}
|
|
||||||
({{c.old_foreign_id}})
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.collapsible-collection {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
29
inventory-app/templates/doorboy_log.html
Normal file
29
inventory-app/templates/doorboy_log.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>This page only shows UID opens!</h3>
|
||||||
|
<p>Does not include Slack opens, Web opens (via Doorboy/Inventory). Formats need to be unified. <b>Use #door-log Slack channel Ctrl-f instead!</b></p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Approved</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Door</th>
|
||||||
|
<th>Who (UID hash)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for o in latest_events %}
|
||||||
|
<tr>
|
||||||
|
<td>{% if o.approved %}<i class="material-icons">check_circle</i>{% else %}no{% endif %}</td>
|
||||||
|
<td>{{ o.timestamp | timeago }}</td>
|
||||||
|
<td>{{ o.door }}</td>
|
||||||
|
<td>{{ o.inventory.owner_id }} (<a href="/m/doorboy/{{ o._id }}/events">{{ o.token.uid_hash[-6:] }})</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user