1
0

login improvements

This commit is contained in:
2026-03-06 21:59:41 +01:00
parent dc6c135473
commit 5fb89485f4
14 changed files with 616 additions and 76 deletions

1
.php-cs-fixer.cache Normal file

File diff suppressed because one or more lines are too long

311
bin/agent-prompts.sh Executable file
View File

@@ -0,0 +1,311 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
bin/agent-prompts.sh --task TASK_ID [options]
Options:
--task TASK_ID Required. Task identifier (e.g. AUTH-LOGIN-REVIEW-001)
--role ROLE planner|executor|executor-rerun|reviewer-guards|reviewer-acceptance|finalizer|all
Default: all
--goal TEXT Planner goal text
--scope-in TEXT Planner scope-in text
--scope-out TEXT Planner scope-out text
--guards CSV Planner required guards (e.g. GR-CORE-005,GR-SEC-001)
--gates CSV Planner required gates (e.g. QG-001,QG-002,QG-003,QG-006)
--run-dir PATH Override run directory (default: agent-system/runs/<TASK_ID>)
--short Emit 3-line one-pager prompt variants
-h, --help Show help
Examples:
bin/agent-prompts.sh --task AUTH-LOGIN-REVIEW-001
bin/agent-prompts.sh --task AUTH-LOGIN-REVIEW-001 --role planner --goal "Auth/Login review"
bin/agent-prompts.sh --task TASK-42 --short
USAGE
}
task_id=""
role="all"
goal="<KURZES_ZIEL>"
scope_in="<DATEIEN/BEREICHE>"
scope_out="<AUSSERHALB_SCOPE>"
guards="<GR-...>"
gates="<QG-...>"
short_mode=0
while [[ $# -gt 0 ]]; do
case "$1" in
--task)
[[ $# -ge 2 ]] || { echo "[ERROR] --task requires a value" >&2; exit 2; }
task_id="$2"
shift 2
;;
--role)
[[ $# -ge 2 ]] || { echo "[ERROR] --role requires a value" >&2; exit 2; }
role="$2"
shift 2
;;
--goal)
[[ $# -ge 2 ]] || { echo "[ERROR] --goal requires a value" >&2; exit 2; }
goal="$2"
shift 2
;;
--scope-in)
[[ $# -ge 2 ]] || { echo "[ERROR] --scope-in requires a value" >&2; exit 2; }
scope_in="$2"
shift 2
;;
--scope-out)
[[ $# -ge 2 ]] || { echo "[ERROR] --scope-out requires a value" >&2; exit 2; }
scope_out="$2"
shift 2
;;
--guards)
[[ $# -ge 2 ]] || { echo "[ERROR] --guards requires a value" >&2; exit 2; }
guards="$2"
shift 2
;;
--gates)
[[ $# -ge 2 ]] || { echo "[ERROR] --gates requires a value" >&2; exit 2; }
gates="$2"
shift 2
;;
--run-dir)
[[ $# -ge 2 ]] || { echo "[ERROR] --run-dir requires a value" >&2; exit 2; }
run_dir="$2"
shift 2
;;
--short)
short_mode=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "[ERROR] Unknown option: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$task_id" ]]; then
echo "[ERROR] --task is required" >&2
usage
exit 2
fi
if [[ "${run_dir:-}" == "" ]]; then
run_dir="agent-system/runs/${task_id}"
fi
is_valid_role() {
case "$1" in
planner|executor|executor-rerun|reviewer-guards|reviewer-acceptance|finalizer|all)
return 0
;;
*)
return 1
;;
esac
}
if ! is_valid_role "$role"; then
echo "[ERROR] Invalid --role: $role" >&2
usage
exit 2
fi
print_divider() {
echo
echo "============================================================"
echo "$1"
echo "============================================================"
}
print_short_prompts() {
cat <<EOF
Planner (3 Zeilen)
Du bist Planner im agent-system; liefere nur valid JSON nach planner.schema.json.
Task ${task_id}; Ziel ${goal}; Scope in/out + Pflicht-Guards ${guards} + Pflicht-Gates ${gates}.
Erzeuge SC-IDs, Schritte, Tests, Acceptance-Checks, Risiken -> ${run_dir}/plan.json.
Executor (3 Zeilen)
Du bist Executor im agent-system; liefere nur valid JSON nach executor.schema.json.
Input: ${run_dir}/plan.json; implementiere nur im Scope, keine Drift.
Führe relevante Gates aus (pass nur Exit 0), sonst blocked+open_items -> ${run_dir}/execution-report.json.
Executor Re-Run (3 Zeilen)
Du bist Executor Re-Run; liefere nur valid JSON nach executor.schema.json.
Input: plan.json + execution-report.json + review-guards.json; behebe alle Findings.
Gates neu ausführen, Report synchronisieren -> ${run_dir}/execution-report.json.
Reviewer-Guards (3 Zeilen)
Du bist Reviewer-Guards; liefere nur valid JSON nach reviewer-guards.schema.json.
Prüfe execution-report gegen alle required_guard_ids + required_quality_gate_ids aus plan.json.
Findings nur mit rule_ref, severity, file -> ${run_dir}/review-guards.json.
Reviewer-Acceptance (3 Zeilen)
Du bist Reviewer-Acceptance; liefere nur valid JSON nach reviewer-acceptance.schema.json.
Prüfe jedes SC-* aus plan.json gegen execution-report mit klarer Evidence.
Bei fail missing_or_wrong füllen -> ${run_dir}/review-acceptance.json.
Finalizer (3 Zeilen)
Du bist Finalizer; liefere nur valid JSON nach finalizer.schema.json.
Input: execution-report.json + review-guards.json + review-acceptance.json.
Nur commit/merge bei guard=pass, acceptance=pass, ci=pass; sonst hold+hold_reason -> ${run_dir}/finalize.json.
EOF
}
print_planner() {
cat <<EOF
Du bist Planner im agent-system.
Lies die Planner-Baseline + Guard/Gate-Kataloge und gib ausschließlich valid JSON nach agent-system/contracts/planner.schema.json aus.
Task: ${task_id}
Ziel: ${goal}
Scope in: ${scope_in}
Scope out: ${scope_out}
Pflicht-Guards: ${guards}
Pflicht-Gates: ${gates}
Erstelle: klare SC-IDs, konkrete Schritte, Tests, Acceptance-Checks, Risiken mit Mitigation.
Output-Datei: ${run_dir}/plan.json
EOF
}
print_executor() {
cat <<EOF
Du bist Executor im agent-system.
Lies die Executor-Baseline und gib ausschließlich valid JSON nach agent-system/contracts/executor.schema.json aus.
Task: ${task_id}
Input: ${run_dir}/plan.json
Regeln:
- Nur Plan-Scope umsetzen, keine Scope-Drift.
- Guard-Evidence und Gate-Results vollständig.
- Gates nach echtem Exit-Code reporten (pass nur bei Exit 0).
- Bei Blockern: status="blocked" + genaue open_items.
Output-Datei: ${run_dir}/execution-report.json
EOF
}
print_executor_rerun() {
cat <<EOF
Du bist Executor im agent-system.
Gib ausschließlich valid JSON nach agent-system/contracts/executor.schema.json aus.
Task: ${task_id}
Input:
- ${run_dir}/plan.json
- ${run_dir}/execution-report.json
- ${run_dir}/review-guards.json
Behebe alle offenen Findings und synchronisiere den Report.
Pflicht: Gates erneut ausführen und konsistent reporten (echter Exit-Code).
Wenn alles grün: status="done". Sonst status="blocked" + exakte Blocker.
Output-Datei: ${run_dir}/execution-report.json
EOF
}
print_reviewer_guards() {
cat <<EOF
Du bist Reviewer-Guards im agent-system.
Prüfe execution-report gegen Guardrails/Security und gib ausschließlich valid JSON nach agent-system/contracts/reviewer-guards.schema.json aus.
Task: ${task_id}
Input:
- ${run_dir}/plan.json
- ${run_dir}/execution-report.json
Prüfe mindestens:
- alle required_guard_ids aus plan.json
- alle required_quality_gate_ids aus plan.json
Jedes Finding braucht: rule_ref (GR-/QG-ID), severity, file.
Output-Datei: ${run_dir}/review-guards.json
EOF
}
print_reviewer_acceptance() {
cat <<EOF
Du bist Reviewer-Acceptance im agent-system.
Gib ausschließlich valid JSON nach agent-system/contracts/reviewer-acceptance.schema.json aus.
Task: ${task_id}
Input:
- ${run_dir}/plan.json
- ${run_dir}/execution-report.json
Bewerte jedes SC-* aus plan.json einzeln mit Evidence.
Bei fail: missing_or_wrong explizit befüllen.
Output-Datei: ${run_dir}/review-acceptance.json
EOF
}
print_finalizer() {
cat <<EOF
Du bist Finalizer im agent-system.
Gib ausschließlich valid JSON nach agent-system/contracts/finalizer.schema.json aus.
Task: ${task_id}
Input:
- ${run_dir}/execution-report.json
- ${run_dir}/review-guards.json
- ${run_dir}/review-acceptance.json
Regel:
- commit/merge nur wenn guard_review=pass, acceptance_review=pass, ci_status=pass
- sonst final_action="hold" mit klarer hold_reason
Output-Datei: ${run_dir}/finalize.json
EOF
}
if [[ $short_mode -eq 1 ]]; then
print_short_prompts
exit 0
fi
case "$role" in
planner)
print_planner
;;
executor)
print_executor
;;
executor-rerun)
print_executor_rerun
;;
reviewer-guards)
print_reviewer_guards
;;
reviewer-acceptance)
print_reviewer_acceptance
;;
finalizer)
print_finalizer
;;
all)
print_divider "Planner"
print_planner
print_divider "Executor"
print_executor
print_divider "Executor Re-Run"
print_executor_rerun
print_divider "Reviewer-Guards"
print_reviewer_guards
print_divider "Reviewer-Acceptance"
print_reviewer_acceptance
print_divider "Finalizer"
print_finalizer
;;
esac

View File

@@ -52,6 +52,9 @@
"Choose how you want to sign in": "Anmeldeart auswählen",
"Continue": "Weiter",
"Use different email": "Andere E-Mail verwenden",
"Encrypted connection": "Verschlüsselt",
"HTTPS/TLS 1.2+ in transit": "HTTPS/TLS 1.2+ bei der Übertragung",
"Problems logging in": "Probleme beim Login",
"We could not continue with these login details": "Mit diesen Login-Daten konnte nicht fortgefahren werden",
"Cached": "Cache",
"Global settings are stored in the database. config/settings.php is only a runtime cache for selected app settings.": "Globale Einstellungen werden in der Datenbank gespeichert. config/settings.php ist nur ein Laufzeit-Cache für ausgewählte App-Einstellungen.",

View File

@@ -52,6 +52,9 @@
"Choose how you want to sign in": "Choose how you want to sign in",
"Continue": "Continue",
"Use different email": "Use different email",
"Encrypted connection": "Encrypted",
"HTTPS/TLS 1.2+ in transit": "HTTPS/TLS 1.2+ in transit",
"Problems logging in": "Problems logging in",
"We could not continue with these login details": "We could not continue with these login details",
"Too many login attempts. Please wait and try again.": "Too many login attempts. Please wait and try again.",
"No login method is available for this tenant": "No login method is available for this tenant",

View File

@@ -36,8 +36,5 @@ $errorNoticeId = !empty($errors) ? 'auth-forgot-error-notice' : '';
<button type="submit"><?php e(t('Send code')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
<hr>
<p class="text-align-center">
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
</p>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
</article>

View File

@@ -24,6 +24,15 @@ $selectedTenantId = (int) ($selectedTenantId ?? 0);
$selectedTenant = is_array($selectedTenant ?? null) ? $selectedTenant : null;
$selectedTenantMethods = is_array($selectedTenantMethods ?? null) ? $selectedTenantMethods : ['local' => false, 'microsoft' => false];
$selectedTenantSlug = (string) ($selectedTenant['slug'] ?? '');
$selectedTenantLabel = trim((string) ($selectedTenant['description'] ?? ''));
if ($selectedTenantLabel === '') {
$selectedTenantLabel = t('Select tenant');
}
$selectedTenantAvatarUrl = (string) ($selectedTenant['avatar_url'] ?? '');
$selectedTenantInitial = strtoupper(substr($selectedTenantLabel, 0, 1));
if ($selectedTenantInitial === '') {
$selectedTenantInitial = '?';
}
$errors = is_array($errors ?? null) ? $errors : [];
$errorNoticeId = $errors ? 'auth-login-error-notice' : '';
$loginHeading = t('Login');
@@ -38,15 +47,17 @@ if ($stage === 'resolve_email') {
$loginHeading = t('Login options');
$loginSubheading = t('Choose how you want to sign in');
}
?>
<article>
<article class="login-card">
<header>
<hgroup>
<h1><?php e($loginHeading); ?></h1>
<p><?php e($loginSubheading); ?></p>
</hgroup>
</header>
<?php if ($errors): ?>
<div id="<?php e($errorNoticeId); ?>" class="notice" data-variant="error" role="alert" aria-live="assertive" tabindex="-1">
<ul>
@@ -58,7 +69,7 @@ if ($stage === 'resolve_email') {
<?php endif; ?>
<?php if ($stage === 'resolve_email'): ?>
<form method="post">
<form method="post" data-login-submit-lock="1">
<input type="hidden" name="stage" value="resolve_email">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<label for="email">
@@ -71,12 +82,12 @@ if ($stage === 'resolve_email') {
<?php Session::getCsrfInput(); ?>
</form>
<?php elseif ($stage === 'choose_tenant'): ?>
<form method="post" class="login-tenant-select-form">
<form method="post" class="login-tenant-select-form" data-login-submit-lock="1">
<input type="hidden" name="stage" value="choose_tenant">
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<fieldset class="login-tenant-fieldset">
<legend class="login-tenant-select-label"><?php e(t('Which tenant do you want to sign in to?')); ?></legend>
<!-- <legend class="login-tenant-select-label"><?php e(t('Which tenant do you want to sign in to?')); ?></legend> -->
<div class="login-tenant-choice-grid">
<?php foreach ($tenantCandidates as $tenantCandidate): ?>
<?php
@@ -116,49 +127,68 @@ if ($stage === 'resolve_email') {
<form method="post">
<input type="hidden" name="stage" value="reset">
<p>
<button type="submit" class="secondary outline"><?php e(t('Back')); ?></button>
<button type="submit" class="secondary outline"><?php e(t('Use different email')); ?></button>
</p>
<?php Session::getCsrfInput(); ?>
</form>
<?php else: ?>
<?php if (!empty($selectedTenantMethods['local'])): ?>
<form method="post">
<input type="hidden" name="stage" value="login_password">
<section class="login-tenant-context" role="status" aria-live="polite">
<div class="login-tenant-context-main">
<?php if ($selectedTenantAvatarUrl !== ''): ?>
<img class="login-tenant-context-avatar" src="<?php e($selectedTenantAvatarUrl); ?>" alt="" loading="lazy">
<?php else: ?>
<span class="login-tenant-context-avatar-placeholder"><?php e($selectedTenantInitial); ?></span>
<?php endif; ?>
<div class="login-tenant-context-text">
<p class="login-tenant-context-name"><?php e($selectedTenantLabel); ?></p>
</div>
</div>
<form method="post" class="login-tenant-context-form">
<input type="hidden" name="stage" value="resolve_email">
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<input type="hidden" name="tenant_id" value="<?php e((string) $selectedTenantId); ?>">
<label for="password">
<span><?php e(t('Password')); ?></span>
<input required type="password" name="password" id="password" autocomplete="current-password" <?php if ($errors): ?>aria-invalid="true" aria-describedby="<?php e($errorNoticeId); ?>"<?php endif; ?> />
</label>
<p>
<label class="app-field inline">
<input type="checkbox" name="remember" value="1">
<span><?php e(t('Remember me')); ?></span>
</label>
</p>
<p>
<button type="submit"><?php e(t('Login')); ?></button>
</p>
<p>
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Forgot password')); ?>?</a>
</p>
<button type="submit" class="secondary outline"><?php e(t('Switch tenant')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
<?php endif; ?>
</section>
<?php if (!empty($selectedTenantMethods['local']) && !empty($selectedTenantMethods['microsoft'])): ?>
<div class="login-alt-separator" aria-hidden="true">
<span><?php e(t('Or')); ?></span>
</div>
<?php endif; ?>
<div class="login-methods-stack">
<?php if (!empty($selectedTenantMethods['microsoft']) && $selectedTenantSlug !== ''): ?>
<a class="primary login-microsoft-link"
href="<?php e(lurl('auth/microsoft/start?tenant=' . rawurlencode($selectedTenantSlug))); ?>">
<i class="bi bi-microsoft" aria-hidden="true"></i> <?php e(t('Sign in with Microsoft')); ?>
</a>
<?php endif; ?>
<?php if (!empty($selectedTenantMethods['microsoft']) && $selectedTenantSlug !== ''): ?>
<a class="primary login-microsoft-link"
href="<?php e(lurl('auth/microsoft/start?tenant=' . rawurlencode($selectedTenantSlug))); ?>">
<i class="bi bi-microsoft" aria-hidden="true"></i> <?php e(t('Sign in with Microsoft')); ?>
</a>
<?php endif; ?>
<?php if (!empty($selectedTenantMethods['local']) && !empty($selectedTenantMethods['microsoft'])): ?>
<div class="login-alt-separator" aria-hidden="true">
<span><?php e(t('Or')); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($selectedTenantMethods['local'])): ?>
<form method="post" class="login-password-form" data-login-submit-lock="1">
<input type="hidden" name="stage" value="login_password">
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<input type="hidden" name="tenant_id" value="<?php e((string) $selectedTenantId); ?>">
<label for="password">
<span><?php e(t('Password')); ?></span>
<input required type="password" name="password" id="password" autocomplete="current-password" <?php if ($errors): ?>aria-invalid="true" aria-describedby="<?php e($errorNoticeId); ?>"<?php endif; ?> />
</label>
<p>
<label class="app-field inline">
<input type="checkbox" name="remember" value="1">
<span><?php e(t('Remember me')); ?></span>
</label>
</p>
<p>
<button type="submit"><?php e(t('Login')); ?></button>
</p>
<?php Session::getCsrfInput(); ?>
</form>
<?php endif; ?>
</div>
<?php if (empty($selectedTenantMethods['local']) && empty($selectedTenantMethods['microsoft'])): ?>
<div class="notice" data-variant="warning" role="status" aria-live="polite">
@@ -171,14 +201,17 @@ if ($stage === 'resolve_email') {
<input type="hidden" name="email" value="<?php e($email); ?>">
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
<p>
<button type="submit" class="secondary outline"><?php e(t('Back')); ?></button>
<button type="submit" class="secondary outline"><?php e(t('Use different email')); ?></button>
</p>
<?php Session::getCsrfInput(); ?>
</form>
<?php endif; ?>
<?php if (allowRegistration()): ?>
<hr>
<p class="text-align-center"><?php e(t('No account yet')); ?>? <a href="<?php e(lurl('register')); ?>"><?php e(t('Register')); ?></a></p>
<?php endif; ?>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
<hr>
<p class="login-security-note">
<i class="bi bi-lock-fill" aria-hidden="true"></i>
<span><?php e(t('Encrypted connection')); ?></span>
</p>
<small class="login-security-note-detail muted"><?php e(t('HTTPS/TLS 1.2+ in transit')); ?></small>
</article>

View File

@@ -18,8 +18,6 @@ $passwordHints = is_array($passwordHints ?? null) ? $passwordHints : [];
$errorNoticeId = !empty($error) ? 'auth-register-error-notice' : '';
$passwordHintId = 'auth-register-password-hints';
?>
<a class="back_to_login small secondary outline" href="<?php e(lurl('login')); ?>"><i
class="bi bi-arrow-left" aria-hidden="true"></i> <?php e(t('Back to Login')); ?></a>
<article>
<header>
<hgroup>
@@ -75,4 +73,5 @@ $passwordHintId = 'auth-register-password-hints';
<?php endif; ?>
<?php Session::getCsrfInput(); ?>
</form>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
</article>

View File

@@ -65,8 +65,5 @@ $passwordHintId = 'auth-reset-password-hints';
<button type="submit"><?php e(t('Reset password')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
<hr>
<p class="text-align-center">
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
</p>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
</article>

View File

@@ -42,10 +42,5 @@ $errorNoticeId = !empty($errors) ? 'auth-verify-error-notice' : '';
<button type="submit"><?php e(t('Verify')); ?></button>
<?php Session::getCsrfInput(); ?>
</form>
<hr>
<p class="text-align-center">
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Send code again')); ?></a>
&nbsp;·&nbsp;
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
</p>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
</article>

View File

@@ -49,7 +49,5 @@ $errorNoticeId = !empty($errors) ? 'auth-verify-email-error-notice' : '';
<?php Session::getCsrfInput(); ?>
<button type="submit" class="secondary outline"><?php e(t('Send code again')); ?></button>
</form>
<p class="text-align-center login-secondary-action">
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
</p>
<?php require templatePath('partials/auth-help-links.phtml'); ?>
</article>

View File

@@ -35,11 +35,11 @@ if ($bufferTitle !== '') {
</head>
<body>
<main>
<div class="container-small">
<main class="login-main">
<div class="container-small login-flash-container">
<?php require __DIR__ . '/partials/app-flash.phtml'; ?>
</div>
<div class="container-small">
<div class="container-small login-content-container">
<?php Buffer::get('html'); ?>
</div>
</main>

View File

@@ -0,0 +1,7 @@
<nav class="login-help-links" aria-label="<?php e(t('Support')); ?>">
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Forgot password')); ?></a>
<?php if (allowRegistration()): ?>
<a href="<?php e(lurl('register')); ?>"><?php e(t('Register')); ?></a>
<?php endif; ?>
<a href="<?php e(lurl('imprint')); ?>"><?php e(t('Problems logging in')); ?></a>
</nav>

View File

@@ -415,6 +415,7 @@
height: 100%;
z-index: -1;
pointer-events: none;
opacity: 0.85;
}
html[data-theme="light"] #gradient {
@@ -451,6 +452,7 @@
transparent 40%
);
background-blend-mode: normal, normal, normal, normal, normal, normal;
opacity: 0.62;
}
html[data-theme="dark"] #gradient {
@@ -536,7 +538,7 @@
var(--c-9) var(--s-start-9),
transparent var(--s-end-9)
);
animation: ani-gradient 10s linear infinite alternate;
animation: ani-gradient 24s linear infinite alternate;
background-blend-mode:
normal, normal, normal, normal, normal, normal, normal, normal, normal,
normal;
@@ -544,6 +546,38 @@
contain: paint;
}
.login-card header {
background: transparent;
}
main.login-main {
min-height: 100dvh;
display: grid;
align-content: center;
gap: 0.75rem;
padding-block: 1rem 2rem;
}
.login-flash-container:empty {
display: none;
}
.login-content-container.container-small,
.login-flash-container.container-small {
width: min(370px, calc(100vw - 2rem));
max-width: none;
}
.login-content-container {
position: relative;
z-index: 1;
}
.login-card {
border: 1px solid color-mix(in srgb, var(--app-border) 75%, transparent);
box-shadow: none;
}
.back_to_login {
margin-bottom: 1rem;
}
@@ -616,8 +650,8 @@
}
.login-tenant-choice-avatar {
width: 8rem;
height: 3rem;
width: 5rem;
height: 2rem;
display: block;
object-fit: contain;
object-position: left center;
@@ -637,13 +671,6 @@
color: var(--app-contrast);
}
.login-tenant-choice-text {
color: var(--app-color);
font-weight: 600;
overflow-wrap: anywhere;
text-align: right;
}
.login-alt-separator {
position: relative;
margin: 1rem 0;
@@ -667,7 +694,7 @@
padding: 0 0.75rem;
font-size: 0.875rem;
color: var(--app-muted);
background: var(--app-surface);
background: var(--app-card-background-color);
}
.login-microsoft-link {
@@ -676,14 +703,148 @@
text-align: center;
}
.login-methods-stack {
display: grid;
gap: 0.625rem;
}
.login-password-form {
margin-top: 0.25rem;
}
.login-password-form > p:last-of-type {
margin-bottom: 0;
}
.login-tenant-context {
border: 1px solid color-mix(in srgb, var(--app-border) 80%, transparent);
border-radius: var(--app-border-radius);
background: color-mix(
in srgb,
var(--app-primary) 6%,
var(--app-card-background-color)
);
padding: 0.75rem;
margin-bottom: 1rem;
display: grid;
gap: 0.75rem;
}
.login-tenant-context-main {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.login-tenant-context-avatar,
.login-tenant-context-avatar-placeholder {
width: 3.5rem;
height: 2.25rem;
border-radius: calc(var(--app-border-radius) * 0.7);
object-fit: contain;
object-position: left center;
background: color-mix(
in srgb,
var(--app-card-background-color) 80%,
var(--app-primary) 20%
);
}
.login-tenant-context-avatar-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
font-weight: 700;
color: var(--app-primary);
}
.login-tenant-context-text {
min-width: 0;
overflow-wrap: anywhere;
}
.login-tenant-context-title {
margin: 0;
font-size: 0.78rem;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--app-muted);
}
.login-tenant-context-name {
margin: 0;
font-size: 11px;
}
.login-tenant-context-form {
margin: 0;
}
.login-tenant-context-form button {
width: 100%;
}
.login-help-links {
margin: 1.75rem 0 0.25rem;
padding-top: 0.35rem;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
column-gap: 0.95rem;
row-gap: 0.45rem;
font-size: 0.72rem;
line-height: 1.25;
}
.login-security-note {
font-size: 0.74rem;
text-align: center;
margin-bottom: 1px;
}
.login-security-note i {
color: var(--app-primary);
font-size: 0.85rem;
}
.login-security-note-detail {
width: 100%;
text-align: center;
display: block;
font-size: 9px;
}
form[aria-busy="true"] button[type="submit"],
button[data-submit-state="loading"] {
opacity: 0.75;
cursor: wait;
}
.login-secondary-action {
margin-top: 1rem;
}
@media (prefers-reduced-motion: reduce) {
html[data-theme="dark"] #gradient {
animation: none;
}
}
@media (min-width: 600px) {
main {
main.login-main {
min-height: 90vh;
place-content: center;
}
.login-tenant-context {
grid-template-columns: 1fr auto;
align-items: center;
}
.login-tenant-context-form button {
width: auto;
}
}
}

View File

@@ -1,3 +1,38 @@
import './core/app-telemetry.js';
import './components/app-flash-auto-dismiss.js';
import './components/app-password-hints.js';
const focusPrimaryErrorNotice = () => {
const errorNotice = document.querySelector(
'.notice[data-variant="error"][role="alert"][tabindex="-1"]',
);
if (!(errorNotice instanceof HTMLElement)) {
return;
}
errorNotice.focus();
};
const lockLoginSubmitButtons = () => {
const forms = document.querySelectorAll('form[data-login-submit-lock="1"]');
forms.forEach((form) => {
if (!(form instanceof HTMLFormElement)) {
return;
}
form.addEventListener('submit', () => {
if (form.dataset.submitted === '1') {
return;
}
form.dataset.submitted = '1';
form.setAttribute('aria-busy', 'true');
const submitButton = form.querySelector('button[type="submit"]');
if (!(submitButton instanceof HTMLButtonElement)) {
return;
}
submitButton.disabled = true;
submitButton.dataset.submitState = 'loading';
});
});
};
focusPrimaryErrorNotice();
lockLoginSubmitButtons();