Squashed 'l2/' content from commit 79caa24

git-subtree-dir: l2
git-subtree-split: 79caa24e2129bf6e2cee819327d5622425306b67
This commit is contained in:
giles
2026-02-24 23:07:31 +00:00
commit f54b0fb5da
43 changed files with 10021 additions and 0 deletions

11
app/templates/404.html Normal file
View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}Not Found - Art-DAG{% endblock %}
{% block content %}
<div class="text-center py-16">
<h2 class="text-6xl font-bold text-gray-600 mb-4">404</h2>
<p class="text-xl text-gray-400 mb-8">Page not found</p>
<a href="/" class="text-blue-400 hover:text-blue-300">Go to home page</a>
</div>
{% endblock %}

View File

@@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block title %}Activities - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Activities</h1>
</div>
{% if activities %}
<div class="space-y-4">
{% for activity in activities %}
<a href="/activities/{{ activity.activity_id }}"
class="block bg-gray-800 border border-gray-700 rounded-lg p-4 hover:border-blue-500 transition-colors">
<div class="flex items-center justify-between mb-2">
<span class="text-blue-400 font-medium">{{ activity.activity_type }}</span>
<span class="text-gray-500 text-sm">{{ activity.published }}</span>
</div>
<div class="text-gray-300 text-sm truncate">
{{ activity.actor_id }}
</div>
</a>
{% endfor %}
</div>
{% if has_more %}
<div class="mt-6 text-center">
<a href="?offset={{ offset + limit }}&limit={{ limit }}"
class="text-blue-400 hover:text-blue-300">Load More</a>
</div>
{% endif %}
{% else %}
<div class="text-center py-12 text-gray-400">
<p>No activities yet.</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}Anchors - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Bitcoin Anchors</h1>
</div>
{% if anchors %}
<div class="space-y-4">
{% for anchor in anchors %}
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="font-mono text-sm text-blue-400 truncate">{{ anchor.merkle_root[:16] }}...</span>
{% if anchor.confirmed_at %}
<span class="bg-green-600 text-xs px-2 py-1 rounded">Confirmed</span>
{% else %}
<span class="bg-yellow-600 text-xs px-2 py-1 rounded">Pending</span>
{% endif %}
</div>
<div class="text-gray-400 text-sm">
{{ anchor.activity_count or 0 }} activities | Created: {{ anchor.created_at }}
</div>
{% if anchor.bitcoin_txid %}
<div class="mt-2 text-xs text-gray-500 font-mono truncate">
TX: {{ anchor.bitcoin_txid }}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% if has_more %}
<div class="mt-6 text-center">
<a href="?offset={{ offset + limit }}&limit={{ limit }}"
class="text-blue-400 hover:text-blue-300">Load More</a>
</div>
{% endif %}
{% else %}
<div class="text-center py-12 text-gray-400">
<p>No anchors yet.</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Assets - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-6xl mx-auto">
<h1 class="text-3xl font-bold mb-6">Your Assets</h1>
{% if assets %}
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" id="assets-grid">
{% for asset in assets %}
<a href="/assets/{{ asset.id }}"
class="bg-gray-800 rounded-lg overflow-hidden hover:ring-2 hover:ring-blue-500 transition-all">
{% if asset.asset_type == 'image' %}
<img src="{{ asset.thumbnail_url or '/assets/' + asset.id + '/thumb' }}"
alt="{{ asset.name }}"
class="w-full h-40 object-cover">
{% elif asset.asset_type == 'video' %}
<div class="w-full h-40 bg-gray-900 flex items-center justify-center">
<svg class="w-12 h-12 text-gray-600" fill="currentColor" viewBox="0 0 20 20">
<path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z"/>
</svg>
</div>
{% else %}
<div class="w-full h-40 bg-gray-900 flex items-center justify-center">
<span class="text-gray-600">{{ asset.asset_type }}</span>
</div>
{% endif %}
<div class="p-3">
<div class="font-medium text-white truncate">{{ asset.name }}</div>
<div class="text-xs text-gray-500">{{ asset.asset_type }}</div>
{% if asset.ipfs_cid %}
<div class="text-xs text-green-400 mt-1">Published</div>
{% endif %}
</div>
</a>
{% endfor %}
</div>
{% if has_more %}
<div hx-get="/assets?offset={{ offset + limit }}"
hx-trigger="revealed"
hx-swap="beforeend"
hx-target="#assets-grid"
class="h-20 flex items-center justify-center text-gray-500 mt-4">
Loading more...
</div>
{% endif %}
{% else %}
<div class="bg-gray-800 border border-gray-700 rounded-lg p-12 text-center">
<p class="text-gray-500 mb-4">No assets yet</p>
<p class="text-gray-600 text-sm">Create content on an L1 renderer and publish it here.</p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Already Logged In - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-md mx-auto text-center">
<div class="bg-green-900/50 border border-green-700 text-green-300 px-4 py-3 rounded-lg mb-4">
You are already logged in as <strong>{{ user.username }}</strong>
</div>
<p><a href="/" class="text-blue-400 hover:text-blue-300">Go to home page</a></p>
</div>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}Login - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-md mx-auto">
<h2 class="text-xl font-semibold mb-6">Login</h2>
<div id="login-result"></div>
<form hx-post="/auth/login" hx-target="#login-result" hx-swap="innerHTML" class="space-y-4">
{% if return_to %}
<input type="hidden" name="return_to" value="{{ return_to }}">
{% endif %}
<div>
<label for="username" class="block text-sm font-medium text-gray-300 mb-2">Username</label>
<input type="text" id="username" name="username" required
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-300 mb-2">Password</label>
<input type="password" id="password" name="password" required
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<button type="submit" class="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg">
Login
</button>
</form>
<p class="mt-6 text-gray-400">
Don't have an account? <a href="/auth/register" class="text-blue-400 hover:text-blue-300">Register</a>
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block title %}Register - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-md mx-auto">
<h2 class="text-xl font-semibold mb-6">Register</h2>
<div id="register-result"></div>
<form hx-post="/auth/register" hx-target="#register-result" hx-swap="innerHTML" class="space-y-4">
<div>
<label for="username" class="block text-sm font-medium text-gray-300 mb-2">Username</label>
<input type="text" id="username" name="username" required pattern="[a-zA-Z0-9_-]+"
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-300 mb-2">Email (optional)</label>
<input type="email" id="email" name="email"
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-300 mb-2">Password</label>
<input type="password" id="password" name="password" required minlength="6"
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<div>
<label for="password2" class="block text-sm font-medium text-gray-300 mb-2">Confirm Password</label>
<input type="password" id="password2" name="password2" required minlength="6"
class="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:border-blue-500 focus:outline-none">
</div>
<button type="submit" class="w-full px-4 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg">
Register
</button>
</form>
<p class="mt-6 text-gray-400">
Already have an account? <a href="/auth/login" class="text-blue-400 hover:text-blue-300">Login</a>
</p>
</div>
{% endblock %}

47
app/templates/base.html Normal file
View File

@@ -0,0 +1,47 @@
{% extends "_base.html" %}
{% block brand %}
<a href="https://blog.rose-ash.com/" class="no-underline text-stone-900">Rose Ash</a>
<span class="text-stone-400 mx-1">|</span>
<a href="/" class="no-underline text-stone-900">Art-DAG</a>
<span class="text-stone-400 mx-1">/</span>
<span class="text-stone-600 text-3xl">L2</span>
{% endblock %}
{% block cart_mini %}
{% if request and request.state.cart_mini_html %}
{{ request.state.cart_mini_html | safe }}
{% endif %}
{% endblock %}
{% block nav_tree %}
{% if request and request.state.nav_tree_html %}
{{ request.state.nav_tree_html | safe }}
{% endif %}
{% endblock %}
{% block auth_menu %}
{% if request and request.state.auth_menu_html %}
{{ request.state.auth_menu_html | safe }}
{% endif %}
{% endblock %}
{% block auth_menu_mobile %}
{% if request and request.state.auth_menu_html %}
{{ request.state.auth_menu_html | safe }}
{% endif %}
{% endblock %}
{% block sub_nav %}
<div class="bg-stone-200 border-b border-stone-300">
<div class="max-w-screen-2xl mx-auto px-4">
<nav class="flex items-center gap-4 py-2 text-sm overflow-x-auto no-scrollbar">
<a href="/assets" class="whitespace-nowrap px-3 py-1.5 rounded {% if active_tab == 'assets' %}bg-stone-500 text-white{% else %}text-stone-700 hover:bg-stone-300{% endif %}">Assets</a>
<a href="/activities" class="whitespace-nowrap px-3 py-1.5 rounded {% if active_tab == 'activities' %}bg-stone-500 text-white{% else %}text-stone-700 hover:bg-stone-300{% endif %}">Activities</a>
<a href="/anchors" class="whitespace-nowrap px-3 py-1.5 rounded {% if active_tab == 'anchors' %}bg-stone-500 text-white{% else %}text-stone-700 hover:bg-stone-300{% endif %}">Anchors</a>
<a href="/storage" class="whitespace-nowrap px-3 py-1.5 rounded {% if active_tab == 'storage' %}bg-stone-500 text-white{% else %}text-stone-700 hover:bg-stone-300{% endif %}">Storage</a>
<a href="/renderers" class="whitespace-nowrap px-3 py-1.5 rounded {% if active_tab == 'renderers' %}bg-stone-500 text-white{% else %}text-stone-700 hover:bg-stone-300{% endif %}">Renderers</a>
</nav>
</div>
</div>
{% endblock %}

42
app/templates/home.html Normal file
View File

@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %}Art-DAG{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
{% if readme_html %}
<div class="prose prose-invert max-w-none mb-12">
{{ readme_html | safe }}
</div>
{% else %}
<div class="text-center py-12">
<h1 class="text-4xl font-bold mb-4">Art-DAG</h1>
<p class="text-xl text-gray-400 mb-8">Content-Addressable Media with ActivityPub Federation</p>
{% if not user %}
<div class="flex justify-center space-x-4">
<a href="/auth/login" class="bg-gray-700 hover:bg-gray-600 px-6 py-3 rounded-lg font-medium">Login</a>
<a href="/auth/register" class="bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded-lg font-medium">Register</a>
</div>
{% endif %}
</div>
{% endif %}
{% if activities %}
<h2 class="text-2xl font-bold mb-4">Recent Activity</h2>
<div class="space-y-4">
{% for activity in activities %}
<div class="bg-gray-800 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-blue-400">{{ activity.actor }}</span>
<span class="text-gray-500 text-sm">{{ activity.created_at }}</span>
</div>
<div class="text-gray-300">
{{ activity.type }}: {{ activity.summary or activity.object_type }}
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-4xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Renderers</h1>
<p class="text-gray-400 mb-6">
Renderers are L1 servers that process your media. Connect to a renderer to create and run recipes.
</p>
{% if error %}
<div class="bg-red-900/50 border border-red-500 text-red-200 px-4 py-3 rounded mb-6">
{{ error }}
</div>
{% endif %}
{% if success %}
<div class="bg-green-900/50 border border-green-500 text-green-200 px-4 py-3 rounded mb-6">
{{ success }}
</div>
{% endif %}
<div class="space-y-4">
{% for server in servers %}
<div class="bg-gray-800 rounded-lg p-4 flex items-center justify-between">
<div>
<a href="{{ server.url }}" target="_blank" class="text-blue-400 hover:text-blue-300 font-medium">
{{ server.url }}
</a>
{% if server.healthy %}
<span class="ml-2 text-green-400 text-sm">Online</span>
{% else %}
<span class="ml-2 text-red-400 text-sm">Offline</span>
{% endif %}
</div>
<div class="flex gap-2">
<a href="{{ server.url }}/auth?auth_token={{ user.token }}"
class="px-3 py-1 bg-blue-600 hover:bg-blue-500 rounded text-sm">
Connect
</a>
</div>
</div>
{% else %}
<p class="text-gray-500">No renderers configured.</p>
{% endfor %}
</div>
<div class="mt-8 text-gray-500 text-sm">
<p>Renderers are configured by the system administrator.</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}Storage - Art-DAG{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold">Storage Providers</h1>
<a href="/storage/add" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg text-sm">
Add Storage
</a>
</div>
{% if storages %}
<div class="space-y-4">
{% for storage in storages %}
<div class="bg-gray-800 border border-gray-700 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="font-medium">{{ storage.name or storage.provider_type }}</span>
<span class="text-xs px-2 py-1 rounded {% if storage.is_active %}bg-green-600{% else %}bg-gray-600{% endif %}">
{{ storage.provider_type }}
</span>
</div>
<div class="text-gray-400 text-sm">
{% if storage.endpoint %}
{{ storage.endpoint }}
{% elif storage.bucket %}
Bucket: {{ storage.bucket }}
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-12 text-gray-400">
<p>No storage providers configured.</p>
<a href="/storage/add" class="text-blue-400 hover:text-blue-300 mt-2 inline-block">Add one now</a>
</div>
{% endif %}
</div>
{% endblock %}