Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| b382eb857b |
8 changed files with 134 additions and 12 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -8,6 +8,19 @@ the git tags (`vX.Y.Z`) and `daybook_project.__version__`.
|
|||
|
||||
_Nothing yet._
|
||||
|
||||
## [0.2.1] — 2026-09-04
|
||||
|
||||
### Added
|
||||
- Top-bar **avatar**: a round initials badge (deterministic per-user color)
|
||||
replaces the gear/username button; lays the groundwork for profile photos.
|
||||
- Django admin now registers `ApiKey` and `TagColor`, so `/admin/` is complete
|
||||
as an operator break-glass tool (key value shown read-only).
|
||||
|
||||
### Changed
|
||||
- Settings nav regrouped into **You** (Account, API Keys, Storage, Demo Data)
|
||||
and a superuser-only **Admin** (Users) section, with About on its own — the
|
||||
personal/admin split now mirrors who each setting affects.
|
||||
|
||||
## [0.2.0] — 2026-09-04
|
||||
|
||||
### Added
|
||||
|
|
@ -85,7 +98,8 @@ First tagged release.
|
|||
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
|
||||
runbook (`deploy/cloud_agent_deploy.md`) and health check.
|
||||
|
||||
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.2.0...HEAD
|
||||
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.2.1...HEAD
|
||||
[0.2.1]: https://git.cygnus.systems/peter/daybook/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://git.cygnus.systems/peter/daybook/compare/v0.1.1...v0.2.0
|
||||
[0.1.1]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...v0.1.1
|
||||
[0.1.0]: https://git.cygnus.systems/peter/daybook/releases/tag/v0.1.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Artifact, Asset, UserProfile
|
||||
from .models import ApiKey, Artifact, Asset, TagColor, UserProfile
|
||||
|
||||
|
||||
@admin.register(Artifact)
|
||||
|
|
@ -21,3 +21,18 @@ class AssetAdmin(admin.ModelAdmin):
|
|||
list_display = ("id", "owner", "artifact", "content_type", "size_bytes", "created_at")
|
||||
list_filter = ("owner", "content_type")
|
||||
search_fields = ("id",)
|
||||
|
||||
|
||||
@admin.register(ApiKey)
|
||||
class ApiKeyAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "user", "key", "created_at", "last_used_at")
|
||||
list_filter = ("user",)
|
||||
search_fields = ("name", "key", "user__username")
|
||||
# The key value and timestamps are system-managed; show but don't hand-edit.
|
||||
readonly_fields = ("key", "created_at", "last_used_at")
|
||||
|
||||
|
||||
@admin.register(TagColor)
|
||||
class TagColorAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "color")
|
||||
search_fields = ("name",)
|
||||
|
|
|
|||
0
artifacts/templatetags/__init__.py
Normal file
0
artifacts/templatetags/__init__.py
Normal file
30
artifacts/templatetags/daybook_extras.py
Normal file
30
artifacts/templatetags/daybook_extras.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
# A small fixed palette. A user's avatar color is picked deterministically from
|
||||
# their username, so it stays stable across sessions and page loads.
|
||||
_AVATAR_COLORS = [
|
||||
"#4a6ea8",
|
||||
"#3f8f7b",
|
||||
"#a3712a",
|
||||
"#8a4f9e",
|
||||
"#a3312a",
|
||||
"#2f7d95",
|
||||
"#6b7280",
|
||||
"#7b6a3f",
|
||||
]
|
||||
|
||||
|
||||
@register.filter
|
||||
def avatar_color(value):
|
||||
text = str(value or "")
|
||||
if not text:
|
||||
return _AVATAR_COLORS[0]
|
||||
return _AVATAR_COLORS[sum(ord(c) for c in text) % len(_AVATAR_COLORS)]
|
||||
|
||||
|
||||
@register.filter
|
||||
def avatar_initial(value):
|
||||
text = str(value or "").strip()
|
||||
return text[0].upper() if text else "?"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Single source of truth for the app version. Bump this, commit, then tag the
|
||||
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
|
||||
# GET /api/capabilities ("version").
|
||||
__version__ = "0.2.0"
|
||||
__version__ = "0.2.1"
|
||||
|
|
|
|||
|
|
@ -128,6 +128,42 @@ body {
|
|||
border-color: var(--control-border);
|
||||
}
|
||||
|
||||
.avatar-button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.avatar-button:hover .avatar,
|
||||
.avatar-button:focus-visible .avatar {
|
||||
box-shadow: 0 0 0 2px var(--content-bg), 0 0 0 4px var(--control-border);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
height: 2rem;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.user-menu-name {
|
||||
border-bottom: 1px solid var(--control-border);
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
padding: 0.5rem 0.8rem;
|
||||
}
|
||||
|
||||
.user-menu-dropdown {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--control-border);
|
||||
|
|
@ -968,6 +1004,21 @@ kbd {
|
|||
padding-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
.settings-nav-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0.9rem;
|
||||
}
|
||||
|
||||
.settings-nav-heading {
|
||||
color: var(--muted);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 0.3rem 0.7rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
max-width: 42rem;
|
||||
padding: 1.6rem 2rem;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{% load daybook_extras %}
|
||||
<header class="topbar">
|
||||
<div class="brandline">
|
||||
<a class="brand-link" href="{% url 'feed' %}"><img class="brand-logo" src="/static/logo.png" alt=""><h1>AgentFeed</h1></a>
|
||||
|
|
@ -6,12 +7,12 @@
|
|||
{% if topbar_toolbar %}{% include topbar_toolbar %}{% endif %}
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<button class="icon-button" type="button" data-user-menu-toggle aria-haspopup="true" aria-expanded="false" title="{{ request.user.username }}">
|
||||
⚙ {{ request.user.username }}
|
||||
<button class="avatar-button" type="button" data-user-menu-toggle aria-haspopup="true" aria-expanded="false" title="{{ request.user.username }}" aria-label="{{ request.user.username }} menu">
|
||||
<span class="avatar" style="background: {{ request.user.username|avatar_color }};">{{ request.user.username|avatar_initial }}</span>
|
||||
</button>
|
||||
<div class="user-menu-dropdown" hidden data-user-menu>
|
||||
<div class="user-menu-name">{{ request.user.username }}</div>
|
||||
<a href="{% url 'settings-account' %}">Settings</a>
|
||||
<a href="{% url 'settings-demo-data' %}">Demo Data</a>
|
||||
<a href="{% url 'settings-about' %}">About</a>
|
||||
<button type="button" data-theme-toggle>Dark mode</button>
|
||||
<form method="post" action="{% url 'logout' %}">{% csrf_token %}<button type="submit">Log out</button></form>
|
||||
|
|
|
|||
|
|
@ -24,14 +24,25 @@
|
|||
<main class="settings-layout">
|
||||
<nav class="settings-nav">
|
||||
<a class="settings-nav-back" href="{% url 'feed' %}">← Back to Feed</a>
|
||||
|
||||
<div class="settings-nav-group">
|
||||
<span class="settings-nav-heading">You</span>
|
||||
<a class="{% if active_section == 'account' %}selected{% endif %}" href="{% url 'settings-account' %}">Account</a>
|
||||
<a class="{% if active_section == 'api-keys' %}selected{% endif %}" href="{% url 'settings-api-keys' %}">API Keys</a>
|
||||
<a class="{% if active_section == 'storage' %}selected{% endif %}" href="{% url 'settings-storage' %}">Storage</a>
|
||||
<a class="{% if active_section == 'demo-data' %}selected{% endif %}" href="{% url 'settings-demo-data' %}">Demo Data</a>
|
||||
</div>
|
||||
|
||||
{% if request.user.is_superuser %}
|
||||
<div class="settings-nav-group">
|
||||
<span class="settings-nav-heading">Admin</span>
|
||||
<a class="{% if active_section == 'users' %}selected{% endif %}" href="{% url 'settings-users' %}">Users</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="settings-nav-group">
|
||||
<a class="{% if active_section == 'about' %}selected{% endif %}" href="{% url 'settings-about' %}">About</a>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="settings-panel">
|
||||
{% if messages %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue