LAN admin on 10.0.0.128 plus quick campaign mint form (#12)
Deploy Beta / unit-tests (push) Successful in 4s
Deploy Beta / docker (push) Successful in 10s
Deploy Beta / deploy-beta (push) Successful in 53s

Closes #11.

## Summary
- Serve Django admin on LAN IP `10.0.0.128` (not `piha.li`). Compose now publishes `0.0.0.0:8005` and passes `SHORT_ADMIN_HOSTS` into the container.
- Admin index has a campaign mint form: domain, campaign, source, metric. Save builds `https://{domain}/?utm_campaign=&utm_source=&utm_medium=` (metric) and shows a copyable short URL.
- Public `/admin/` on `piha.lc` / `piha.li` stays 404.

## Test plan
- [ ] `cd site && uv run python manage.py test`
- [ ] Recreate compose (`docker compose up --build`) so `WEB_BIND` / `SHORT_ADMIN_HOSTS` take effect
- [ ] From another LAN machine: `http://10.0.0.128:8005/admin/` (staff login) shows the mint form
- [ ] Save a link for an allowlisted domain, copy the short URL, confirm it 302s
- [ ] `https://piha.li/admin` still 404

Reviewed-on: #12
This commit was merged in pull request #12.
This commit is contained in:
2026-09-16 03:39:04 -07:00
parent dd627b75c9
commit 69d8b3e7a3
10 changed files with 351 additions and 16 deletions
+108
View File
@@ -0,0 +1,108 @@
{% extends "admin/base_site.html" %}
{% load i18n static admin_filters %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/dashboard.css" %}" {% csp_nonce_attr %}>{% endblock %}
{% block extrahead %}
{{ block.super }}
<script {% csp_nonce_attr %}>
function copyShortUrl() {
var el = document.getElementById("id_short_url_result");
var btn = document.getElementById("copy-short-url");
if (!el) return;
var done = function () {
if (btn) {
btn.textContent = "Copied";
setTimeout(function () { btn.textContent = "Copy"; }, 1500);
}
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(el.value).then(done, function () {
el.select();
document.execCommand("copy");
done();
});
} else {
el.select();
document.execCommand("copy");
done();
}
}
</script>
{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
{% block nav-breadcrumbs %}{% endblock %}
{% block nav-sidebar %}{% endblock %}
{% block content %}
<div id="content-main" class="app-list">
<div class="module" id="quick-mint-module">
<h2>Create a short link</h2>
{% if created_link %}
<div class="form-row" style="padding: 12px 16px 0;">
<label for="id_short_url_result">Short URL</label>
<input id="id_short_url_result" type="text" readonly value="{{ created_link.public_short_url }}" size="48" style="max-width: 28rem;">
<button type="button" class="default" id="copy-short-url" onclick="copyShortUrl()">Copy</button>
<p class="help">Target: {{ created_link.target_url }}</p>
</div>
{% endif %}
<form method="post" action="{% url 'admin:index' %}" style="padding: 8px 16px 16px;">
{% csrf_token %}
{{ quick_mint_form.non_field_errors }}
<datalist id="allowed-domains">
{% for host in allowed_domains %}<option value="{{ host }}">{% endfor %}
</datalist>
{% for field in quick_mint_form %}
<div class="form-row">
{{ field.errors }}
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}<p class="help">{{ field.help_text }}</p>{% endif %}
</div>
{% endfor %}
<div class="submit-row">
<input type="submit" class="default" value="Save">
</div>
</form>
</div>
{% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
</div>
{% endblock %}
{% block sidebar %}
<div id="content-related">
<div class="module" id="recent-actions-module">
<h2>{% translate 'Recent actions' %}</h2>
<h3>{% translate 'My actions' %}</h3>
{% load log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
<p>{% translate 'None available' %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
<span class="visually-hidden">{% if entry.is_addition %}{% translate 'Added:' %}{% elif entry.is_change %}{% translate 'Changed:' %}{% elif entry.is_deletion %}{% translate 'Deleted:' %}{% endif %}</span>
{% if entry.is_deletion or not entry.get_admin_url %}
{{ entry.object_repr|to_object_display_value }}
{% else %}
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr|to_object_display_value }}</a>
{% endif %}
<br>
{% if entry.content_type %}
<span class="mini quiet">{% filter capfirst %}{{ entry.content_type.name }}{% endfilter %}</span>
{% else %}
<span class="mini quiet">{% translate 'Unknown content' %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endblock %}