agnes-the-ai-analyst/app/web/templates/login.html
Vojtech 79a958ec26
feat(setup): configurable instance brand + connector setup overhaul (#268)
- instance.brand (env AGNES_INSTANCE_BRAND, default "Agnes") +
  instance.workspace_dir replace hard-coded "Agnes" / "~/Agnes" across
  /home, /setup, /setup-advanced, /login, /install, /me/debug, and the
  Claude Code clipboard setup script. Terraform-friendly env override;
  defaults preserve existing Agnes branding.

- Explicit "create workspace folder" step on /home (OS-tabbed mkdir+cd)
  + same step baked into the clipboard script as step 2. Drops the
  implicit assumption that `agnes init --workspace .` lands in a
  sensibly-cd'd shell.

- Final "Restart Claude Code" step in the setup script (unconditional,
  between connectors and Confirm) so freshly-installed plugins, MCP
  servers, and SessionStart hooks load on the next Claude Code session.

- Asana reverted from hosted Remote MCP back to PAT + raw REST against
  app.asana.com/api/1.0. MCP envelope shape consumed ~5x tokens per
  call; the PAT path lets the agent read flat REST fields. Existing
  MCP registration is detected and the user is asked whether to remove
  it (default Y, with benefits listed: token cost, no third-party hop,
  no OAuth refresh dance, deterministic envelope shape).

- Atlassian connector instructs picking the longest API-token expiry
  (today "1 year") to cut re-mint friction. No public query-parameter
  hook exists on id.atlassian.com to pre-select expiry, so the prompt
  documents the manual click and acknowledges that limitation.

- Uniform  /  per-connector marker contract (Asana, GWS, Atlassian)
  for the Confirm summary to grep. Each connector now ends with a
  Claude-driven end-to-end test that uses Claude Code's own bash to
  exercise the stored credential and prints
  " <Connector> integration verified — ..." (or the failure variant).
2026-05-12 17:10:08 +02:00

142 lines
7.4 KiB
HTML

{% extends "base_login.html" %}
{% block title %}Login - Data Analyst Portal{% endblock %}
{% block content %}
<div class="login-page">
<div class="login-split">
<!-- Left: Features & Value Proposition -->
<div class="login-features">
<div class="features-content">
<h1 class="features-title">{{ config.INSTANCE_NAME }}</h1>
<p class="features-subtitle">
Your local AI agent works with centralized data, shared context, and corporate memory that learns from everyone.
</p>
<div class="feature-cards">
<div class="feature-card">
<div class="feature-icon feature-icon-data">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<ellipse cx="12" cy="5" rx="9" ry="3"/>
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
</svg>
</div>
<div class="feature-text">
<h3>Unified Data Access</h3>
<p>Synced data from central server with semantic layer, metrics definitions, and full documentation.</p>
</div>
</div>
<div class="feature-card">
<div class="feature-icon feature-icon-memory">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a4 4 0 0 1 4 4c0 1.1-.9 2-2 2h-4c-1.1 0-2-.9-2-2a4 4 0 0 1 4-4z"/>
<path d="M12 8v8"/>
<path d="M8 12h8"/>
<circle cx="12" cy="19" r="3"/>
</svg>
</div>
<div class="feature-text">
<h3>Corporate Memory</h3>
<p>Shared context across all users. When someone discovers something, everyone's AI knows it.</p>
</div>
</div>
<div class="feature-card">
<div class="feature-icon feature-icon-auto">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
</svg>
</div>
<div class="feature-text">
<h3>Instant Automation</h3>
<p>Turn any analysis into automated scripts. Runs without AI - fast, cheap, and repeatable.</p>
</div>
</div>
<div class="feature-card">
<div class="feature-icon feature-icon-notif">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>
</div>
<div class="feature-text">
<h3>Smart Notifications</h3>
<p>Your AI agent can set up alerts and deploy them to the cloud. Server monitors 24/7.</p>
</div>
</div>
<div class="feature-card">
<div class="feature-icon feature-icon-performance">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 3v18h18"/>
<path d="M18 17V9"/>
<path d="M13 17v-6"/>
<path d="M8 17v-3"/>
<polyline points="7 7 12 3 17 6 22 2"/>
</svg>
</div>
<div class="feature-text">
<h3>Performance Intelligence</h3>
<p>Track how data and AI drive measurable business outcomes. Real-time visibility into team maturity, process improvements, and ROI across departments.</p>
</div>
</div>
</div>
</div>
</div>
<!-- Right: Login Card -->
<div class="login-card-wrapper">
<div class="login-card">
<h2>Sign In</h2>
<p class="login-description">
Access your AI data analysis workspace
</p>
{% set _err = request.query_params.get('error') %}
{% set _err_messages = {
'not_in_allowed_group': "Your Google account isn't a member of any group permitted to use this " ~ (instance_brand or "Agnes") ~ " instance. Ask your " ~ (instance_brand or "Agnes") ~ " administrator to grant you access.",
'group_check_unavailable': "We couldn't verify your group membership with Google right now. Please try signing in again in a moment.",
'deactivated': "This account has been deactivated. Contact your " ~ (instance_brand or "Agnes") ~ " administrator if you believe this is in error.",
'oauth_failed': "Google sign-in failed. Please try again.",
'no_email': "Google didn't return an email for this account.",
'domain_not_allowed': "This email's domain is not permitted to sign in to this " ~ (instance_brand or "Agnes") ~ " instance.",
'google_not_configured': "Google sign-in is not configured on this server.",
} %}
{% if _err and _err_messages.get(_err) %}
<div class="login-error" role="alert" style="background:#fef2f2;border:1px solid #fecaca;color:#b91c1c;padding:12px 14px;border-radius:8px;font-size:13px;line-height:1.5;margin:0 auto 16px;max-width:280px;text-align:left;">
{{ _err_messages.get(_err) }}
</div>
{% endif %}
{% for btn in login_buttons %}
<a href="{{ btn.url }}" class="btn {{ btn.css_class|default('btn-secondary') }}" style="width: 100%; max-width: 280px;">
{% if btn.icon_html %}{{ btn.icon_html|safe }}{% endif %}
{{ btn.text }}
</a>
{% if btn.subtitle %}
<p class="login-note">
{{ btn.subtitle|safe }}
</p>
{% endif %}
{% if not loop.last %}
<div class="divider">
<span>or</span>
</div>
{% endif %}
{% endfor %}
{% if not login_buttons %}
<p class="login-note">
No authentication providers are configured. Please set up at least one provider.
</p>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}