Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ state-snapshots.txt

request.json

# Tailwind CSS build output
daiv/accounts/static/accounts/css/styles.css

# SWE-bench
daiv-traces/
predictions.json
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added web-based authentication via django-allauth with GitHub and GitLab social login, passwordless login-by-code for existing users, and a styled dark-themed login page. Includes a post-login dashboard with API key management (create, list, revoke).

- Added `setup_langsmith_dashboard` management command to create a pre-configured LangSmith custom dashboard with 27 monitoring charts covering trace volume, latency, cost, tool usage, model comparison, and pipeline health. Supports `--project` and `--recreate` options.
- Added `model` and `thinking_level` metadata to all LangSmith traces, enabling per-model dashboards and A/B comparison when switching between models.
- Added async Jobs API (`POST /api/jobs`, `GET /api/jobs/{id}`) for programmatic agent execution with configurable per-user rate limiting (`JOBS_THROTTLE_RATE`). Enables scheduled CI pipelines, Slack bots, and scripted workflows to trigger DAIV agents without blocking.
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Makefile

.PHONY: help setup test test-ci lint lint-check lint-format lint-fix lint-typing evals
.PHONY: help setup test test-ci lint lint-check lint-format lint-fix lint-typing evals tailwind-build tailwind-watch

help:
@echo "Available commands:"
Expand Down Expand Up @@ -103,5 +103,11 @@ swerebench-clean:
docs-serve:
uv run --only-group=docs mkdocs serve -o -a localhost:4000 -w docs/

tailwind-build:
docker compose exec app tailwindcss -i daiv/accounts/static_src/css/input.css -o daiv/accounts/static/accounts/css/styles.css --minify

tailwind-watch:
docker compose exec app tailwindcss -i daiv/accounts/static_src/css/input.css -o daiv/accounts/static/accounts/css/styles.css --watch

langsmith-fetch:
uv run langsmith-fetch traces --project-uuid 00d1a04e-0087-4813-9a18-5995cd5bee5c --limit 1 ./daiv-traces
14 changes: 14 additions & 0 deletions daiv/accounts/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter


class AccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
"""Disable standard email/password signup. Users are created via social providers only."""
return False


class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin):
"""Allow new user creation via social providers (GitHub, GitLab)."""
return True
28 changes: 28 additions & 0 deletions daiv/accounts/allauth_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.urls import path

from allauth.account import views as account_views
from allauth.socialaccount import views as socialaccount_views
from allauth.urls import build_provider_urlpatterns

from accounts.socialaccount import oauth2_callback, oauth2_login

urlpatterns = [
# Custom GitLab OAuth adapter must come before auto-discovered provider
# URLs so it takes precedence. This lets us use a Docker-internal URL for
# server-side token exchange while keeping the browser-facing authorize URL
# unchanged.
path("gitlab/login/", oauth2_login, name="gitlab_login"),
path("gitlab/login/callback/", oauth2_callback, name="gitlab_callback"),
# Account views (login, logout, login-by-code only — no signup, password,
# or email management routes).
path("login/", account_views.login, name="account_login"),
path("logout/", account_views.logout, name="account_logout"),
path("login/code/", account_views.request_login_code, name="account_request_login_code"),
path("login/code/confirm/", account_views.confirm_login_code, name="account_confirm_login_code"),
# Social account views required by the OAuth flow.
path("3rdparty/login/cancelled/", socialaccount_views.login_cancelled, name="socialaccount_login_cancelled"),
path("3rdparty/login/error/", socialaccount_views.login_error, name="socialaccount_login_error"),
path("3rdparty/signup/", socialaccount_views.signup, name="socialaccount_signup"),
# Auto-discovered OAuth provider URLs (GitHub, GitLab, etc.).
*build_provider_urlpatterns(),
]
9 changes: 9 additions & 0 deletions daiv/accounts/api_keys_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from accounts.views import APIKeyCreateView, APIKeyListView, APIKeyRevokeView

urlpatterns = [
path("", APIKeyListView.as_view(), name="api_keys"),
path("create/", APIKeyCreateView.as_view(), name="api_key_create"),
path("<int:pk>/revoke/", APIKeyRevokeView.as_view(), name="api_key_revoke"),
]
5 changes: 5 additions & 0 deletions daiv/accounts/dashboard_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.urls import path

from accounts.views import DashboardView

urlpatterns = [path("", DashboardView.as_view(), name="dashboard")]
9 changes: 9 additions & 0 deletions daiv/accounts/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django import forms

from accounts.models import APIKey


class APIKeyCreateForm(forms.ModelForm):
class Meta:
model = APIKey
fields = ["name"]
20 changes: 20 additions & 0 deletions daiv/accounts/management/commands/setup_default_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand, CommandError

from core.conf import settings as core_settings


class Command(BaseCommand):
help = "Update the default Site domain from DAIV_EXTERNAL_URL."

def handle(self, *args, **options):
domain = core_settings.EXTERNAL_URL.host
if not domain:
raise CommandError("DAIV_EXTERNAL_URL has no valid host. Check your DAIV_EXTERNAL_URL setting.")

rows = Site.objects.filter(pk=settings.SITE_ID).update(domain=domain, name="DAIV")
if rows == 0:
raise CommandError(f"Site with pk={settings.SITE_ID} does not exist. Run 'migrate' first.")

self.stdout.write(self.style.SUCCESS(f"Default site updated: domain={domain}, name=DAIV"))
35 changes: 35 additions & 0 deletions daiv/accounts/socialaccount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from allauth.core import context
from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.providers.gitlab.views import GitLabOAuth2Adapter
from allauth.socialaccount.providers.oauth2.views import OAuth2CallbackView, OAuth2LoginView


class GitLabServerAwareAdapter(GitLabOAuth2Adapter):
"""
GitLab adapter that supports a separate ``gitlab_server_url`` app setting
for server-side HTTP calls (token exchange, profile fetch). This is needed
in Docker/compose environments where the browser-facing URL differs from
the URL reachable inside the container network.

When ``gitlab_server_url`` is empty or absent the adapter falls back to the
standard ``gitlab_url``.
"""

def _build_server_url(self, path):
app = get_adapter().get_app(context.request, provider=self.provider_id)
server_url = app.settings.get("gitlab_server_url")
if server_url:
return f"{server_url}{path}"
return self._build_url(path)

@property
def access_token_url(self):
return self._build_server_url("/oauth/token")

@property
def profile_url(self):
return self._build_server_url(f"/api/{self.provider_api_version}/user")


oauth2_login = OAuth2LoginView.adapter_view(GitLabServerAwareAdapter)
oauth2_callback = OAuth2CallbackView.adapter_view(GitLabServerAwareAdapter)
Binary file added daiv/accounts/static/accounts/img/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions daiv/accounts/static_src/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import "tailwindcss";

@theme {
--font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
}

@keyframes fade-up {
from {
opacity: 0;
transform: translateY(10px);
}
}

a, button, [role="button"] {
cursor: pointer;
}

.animate-fade-up {
animation: fade-up 0.5s ease-out both;
}
63 changes: 63 additions & 0 deletions daiv/accounts/templates/account/confirm_login_code.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{% extends "base.html" %}
{% load static %}

{% block title %}Enter code — DAIV{% endblock %}

{% block content %}
<div class="relative flex min-h-dvh items-center justify-center overflow-hidden px-4">
<div class="pointer-events-none absolute inset-0"
style="background: radial-gradient(ellipse 60% 50% at 50% 40%, rgba(255,255,255,0.025) 0%, transparent 100%)"></div>
<div class="pointer-events-none absolute inset-0 opacity-30"
style="background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.07) 1px, transparent 0); background-size: 32px 32px"></div>

<div class="relative w-full max-w-[380px]">
<!-- Logo -->
<div class="animate-fade-up mb-10 flex flex-col items-center">
<img src="{% static 'accounts/img/logo.png' %}" alt="DAIV" class="h-10">
</div>

<!-- Icon + message -->
<div class="animate-fade-up mb-8 text-center" style="animation-delay: 80ms">
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.03]">
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/>
</svg>
</div>
<h2 class="text-[16px] font-semibold text-white">Check your email</h2>
<p class="mt-2 text-[14px] font-light text-gray-400">
We sent a login code to your inbox.<br>Enter it below to continue.
</p>
</div>

<form method="post" class="animate-fade-up" style="animation-delay: 160ms">
{% csrf_token %}
<div class="space-y-3">
{% for field in form %}
<div>
{% if field.errors %}
<p class="mb-2 text-[14px] text-red-400">{{ field.errors.0 }}</p>
{% endif %}
<input type="text"
name="{{ field.html_name }}"
id="{{ field.id_for_label }}"
value="{{ field.value|default:'' }}"
{% if field.field.required %}required{% endif %}
autocomplete="one-time-code"
inputmode="numeric"
placeholder="Enter code"
class="block w-full rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3.5 text-center text-lg font-semibold tracking-[0.3em] text-white placeholder-gray-500 outline-none transition-all duration-200 focus:border-white/[0.15] focus:bg-white/[0.05] focus:ring-1 focus:ring-white/[0.08]">
</div>
{% endfor %}
<button type="submit"
class="w-full rounded-xl bg-white px-4 py-3 text-[14px] font-semibold text-[#030712] transition-all duration-200 hover:bg-gray-200 active:scale-[0.98]">
Verify &amp; sign in
</button>
</div>
</form>

<p class="animate-fade-up mt-6 text-center text-[14px] text-gray-500" style="animation-delay: 220ms">
<a href="{% url 'account_login' %}" class="text-gray-400 transition-colors hover:text-white">Back to sign in</a>
</p>
</div>
</div>
{% endblock %}
92 changes: 92 additions & 0 deletions daiv/accounts/templates/account/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{% extends "base.html" %}
{% load static socialaccount %}

{% block title %}Sign in — DAIV{% endblock %}

{% block content %}
<div class="relative flex min-h-dvh items-center justify-center overflow-hidden px-4">
<!-- Background: radial spotlight -->
<div class="pointer-events-none absolute inset-0"
style="background: radial-gradient(ellipse 60% 50% at 50% 40%, rgba(255,255,255,0.025) 0%, transparent 100%)">
</div>

<!-- Background: dot grid -->
<div class="pointer-events-none absolute inset-0 opacity-30"
style="background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.07) 1px, transparent 0); background-size: 32px 32px">
</div>

<!-- Background: geometric accent lines -->
<div class="pointer-events-none absolute inset-0 overflow-hidden">
<div class="absolute -right-32 top-[20%] h-px w-[500px] rotate-[28deg] bg-gradient-to-r from-transparent via-white/[0.06] to-transparent"></div>
<div class="absolute -right-16 top-[28%] h-px w-[400px] rotate-[28deg] bg-gradient-to-r from-transparent via-white/[0.04] to-transparent"></div>
<div class="absolute -left-32 bottom-[25%] h-px w-[500px] rotate-[28deg] bg-gradient-to-r from-transparent via-white/[0.06] to-transparent"></div>
<div class="absolute -left-16 bottom-[18%] h-px w-[400px] rotate-[28deg] bg-gradient-to-r from-transparent via-white/[0.04] to-transparent"></div>
</div>

<!-- Card -->
<div class="relative w-full max-w-[380px]">

<!-- Logo -->
<div class="animate-fade-up mb-10 flex flex-col items-center">
<img src="{% static 'accounts/img/logo.png' %}" alt="DAIV" class="h-10">
<p class="mt-4 text-[14px] font-light tracking-wide text-gray-400">Sign in to your account</p>
</div>

<!-- Social providers -->
{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
<div class="animate-fade-up space-y-2.5" style="animation-delay: 80ms">
{% for provider in socialaccount_providers %}
{% if provider.id == "github" %}
<a href="{% provider_login_url provider.id process='login' %}"
class="group flex w-full items-center justify-center gap-3 rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-[14px] font-medium text-gray-200 transition-all duration-200 hover:border-white/[0.12] hover:bg-white/[0.06] hover:text-white">
<svg class="h-5 w-5 text-gray-400 transition-colors group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clip-rule="evenodd"/>
</svg>
Continue with GitHub
</a>
{% elif provider.id == "gitlab" %}
<a href="{% provider_login_url provider.id process='login' %}"
class="group flex w-full items-center justify-center gap-3 rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-[14px] font-medium text-gray-200 transition-all duration-200 hover:border-white/[0.12] hover:bg-white/[0.06] hover:text-white">
<svg class="h-5 w-5 text-gray-400 transition-colors group-hover:text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="m18.88 9.7-.95-2.93-1.88-5.8a.38.38 0 0 0-.73 0L13.44 6.77H6.56L4.68.97a.38.38 0 0 0-.73 0L2.07 6.77 1.12 9.7a.76.76 0 0 0 .28.85L10 16.58l8.6-6.03a.76.76 0 0 0 .28-.85"/>
</svg>
Continue with GitLab
</a>
{% endif %}
{% endfor %}
</div>

<!-- Divider -->
<div class="animate-fade-up relative my-7" style="animation-delay: 140ms">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-white/[0.06]"></div>
</div>
<div class="relative flex justify-center">
<span class="bg-[#030712] px-4 text-[12px] font-medium uppercase tracking-[0.2em] text-gray-500">or</span>
</div>
</div>
{% endif %}

<!-- Login code form -->
<form method="post" action="{% url 'account_request_login_code' %}"
class="animate-fade-up" style="animation-delay: 200ms">
{% csrf_token %}
<div class="space-y-3">
<div class="relative">
<input type="email" name="email" id="email" required autocomplete="email"
placeholder="you@company.com"
class="block w-full rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-[14px] text-white placeholder-gray-500 outline-none transition-all duration-200 focus:border-white/[0.15] focus:bg-white/[0.05] focus:ring-1 focus:ring-white/[0.08]">
</div>
<button type="submit"
class="w-full rounded-xl bg-white px-4 py-3 text-[14px] font-semibold text-[#030712] transition-all duration-200 hover:bg-gray-200 active:scale-[0.98]">
Continue with email
</button>
</div>
<p class="mt-4 text-center text-[12px] font-light tracking-wide text-gray-500">
We'll send a one-time code to your inbox
</p>
</form>
</div>
</div>
{% endblock %}
25 changes: 25 additions & 0 deletions daiv/accounts/templates/account/logout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends "base.html" %}

{% block title %}Sign out — DAIV{% endblock %}

{% block content %}
<div class="flex min-h-dvh items-center justify-center px-4">
<div class="w-full max-w-[380px] text-center">
<div class="animate-fade-up">
<h2 class="text-lg font-semibold text-white">Sign out</h2>
<p class="mt-2 text-[14px] text-gray-500">Are you sure you want to sign out?</p>
</div>
<form method="post" class="animate-fade-up mt-6 flex gap-3" style="animation-delay: 80ms">
{% csrf_token %}
<a href="{% url 'dashboard' %}"
class="flex-1 rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3 text-[14px] font-medium text-gray-300 transition-all duration-200 hover:border-white/[0.12] hover:bg-white/[0.06] hover:text-white">
Cancel
</a>
<button type="submit"
class="flex-1 rounded-xl bg-white px-4 py-3 text-[14px] font-semibold text-[#030712] transition-all duration-200 hover:bg-gray-200 active:scale-[0.98]">
Sign out
</button>
</form>
</div>
</div>
{% endblock %}
Loading
Loading