Add dark mode with toggle and localStorage persistence

Uses Tailwind v4 class-based dark mode with a Stimulus controller for
toggling. Respects prefers-color-scheme as default, prevents FOUC with
an inline script, and persists the user's choice in localStorage. All
views updated with dark: variants for backgrounds, text, borders,
badges, buttons, and form inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dan Milne
2026-03-22 00:37:58 +11:00
parent 43958f50ce
commit 3d98261a51
38 changed files with 744 additions and 636 deletions

View File

@@ -1,5 +1,5 @@
<div class="max-w-3xl">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Edit Application</h1>
<p class="text-sm text-gray-600 mb-6">Editing: <%= @application.name %></p>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Edit Application</h1>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Editing: <%= @application.name %></p>
<%= render "form", application: @application %>
</div>

View File

@@ -1,7 +1,7 @@
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-2xl font-semibold text-gray-900">Applications</h1>
<p class="mt-2 text-sm text-gray-700">Manage OIDC Clients.</p>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Applications</h1>
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300">Manage OIDC Clients.</p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<%= link_to "New Application", new_admin_application_path, class: "block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600" %>
@@ -11,29 +11,29 @@
<div class="mt-8 flow-root">
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<table class="min-w-full divide-y divide-gray-300">
<table class="min-w-full divide-y divide-gray-300 dark:divide-gray-600">
<thead>
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Application</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Slug</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Type</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Groups</th>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-gray-100 sm:pl-0">Application</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Slug</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Type</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Status</th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Groups</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
<span class="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<% @applications.each do |application| %>
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-0">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-0">
<div class="flex items-center gap-3">
<% if application.icon.attached? %>
<%= image_tag application.icon, class: "h-10 w-10 rounded-lg object-cover border border-gray-200 flex-shrink-0", alt: "#{application.name} icon" %>
<%= image_tag application.icon, class: "h-10 w-10 rounded-lg object-cover border border-gray-200 dark:border-gray-700 flex-shrink-0", alt: "#{application.name} icon" %>
<% else %>
<div class="h-10 w-10 rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center flex-shrink-0">
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="h-10 w-10 rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center flex-shrink-0">
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
@@ -41,29 +41,29 @@
<%= link_to application.name, admin_application_path(application), class: "text-blue-600 hover:text-blue-900" %>
</div>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= application.slug %></code>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
<code class="text-xs bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2 py-1 rounded"><%= application.slug %></code>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
<% case application.app_type %>
<% when "oidc" %>
<span class="inline-flex items-center rounded-full bg-purple-100 px-2 py-1 text-xs font-medium text-purple-700">OIDC</span>
<span class="inline-flex items-center rounded-full bg-purple-100 dark:bg-purple-900/50 px-2 py-1 text-xs font-medium text-purple-700 dark:text-purple-300">OIDC</span>
<% when "forward_auth" %>
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700">Forward Auth</span>
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-300">Forward Auth</span>
<% when "saml" %>
<span class="inline-flex items-center rounded-full bg-orange-100 px-2 py-1 text-xs font-medium text-orange-700">SAML</span>
<span class="inline-flex items-center rounded-full bg-orange-100 dark:bg-orange-900/50 px-2 py-1 text-xs font-medium text-orange-700 dark:text-orange-300">SAML</span>
<% end %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
<% if application.active? %>
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">Active</span>
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/50 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-300">Active</span>
<% else %>
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700">Inactive</span>
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300">Inactive</span>
<% end %>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
<% if application.allowed_groups.empty? %>
<span class="text-gray-400">All users</span>
<span class="text-gray-400 dark:text-gray-500">All users</span>
<% else %>
<%= application.allowed_groups.count %>
<% end %>

View File

@@ -1,4 +1,4 @@
<div class="max-w-3xl">
<h1 class="text-2xl font-semibold text-gray-900 mb-6">New Application</h1>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">New Application</h1>
<%= render "form", application: @application %>
</div>

View File

@@ -1,27 +1,27 @@
<div class="mb-6">
<% if flash[:client_id] %>
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-6">
<h4 class="text-sm font-medium text-yellow-800 mb-2">🔐 OIDC Client Credentials</h4>
<div class="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded-md p-4 mb-6">
<h4 class="text-sm font-medium text-yellow-800 dark:text-yellow-200 mb-2">🔐 OIDC Client Credentials</h4>
<% if flash[:public_client] %>
<p class="text-xs text-yellow-700 mb-3">This is a public client. Copy the client ID below.</p>
<p class="text-xs text-yellow-700 dark:text-yellow-300 mb-3">This is a public client. Copy the client ID below.</p>
<% else %>
<p class="text-xs text-yellow-700 mb-3">Copy these credentials now. The client secret will not be shown again.</p>
<p class="text-xs text-yellow-700 dark:text-yellow-300 mb-3">Copy these credentials now. The client secret will not be shown again.</p>
<% end %>
<div class="space-y-2">
<div>
<span class="text-xs font-medium text-yellow-700">Client ID:</span>
<span class="text-xs font-medium text-yellow-700 dark:text-yellow-300">Client ID:</span>
</div>
<code class="block bg-yellow-100 px-3 py-2 rounded font-mono text-xs break-all"><%= flash[:client_id] %></code>
<code class="block bg-yellow-100 dark:bg-yellow-900/50 px-3 py-2 rounded font-mono text-xs break-all"><%= flash[:client_id] %></code>
<% if flash[:client_secret] %>
<div class="mt-3">
<span class="text-xs font-medium text-yellow-700">Client Secret:</span>
<span class="text-xs font-medium text-yellow-700 dark:text-yellow-300">Client Secret:</span>
</div>
<code class="block bg-yellow-100 px-3 py-2 rounded font-mono text-xs break-all"><%= flash[:client_secret] %></code>
<code class="block bg-yellow-100 dark:bg-yellow-900/50 px-3 py-2 rounded font-mono text-xs break-all"><%= flash[:client_secret] %></code>
<% elsif flash[:public_client] %>
<div class="mt-3">
<span class="text-xs font-medium text-yellow-700">Client Secret:</span>
<span class="text-xs font-medium text-yellow-700 dark:text-yellow-300">Client Secret:</span>
</div>
<div class="bg-yellow-100 px-3 py-2 rounded text-xs text-yellow-600">
<div class="bg-yellow-100 dark:bg-yellow-900/50 px-3 py-2 rounded text-xs text-yellow-600 dark:text-yellow-400">
Public clients do not have a client secret. PKCE is required.
</div>
<% end %>
@@ -32,21 +32,21 @@
<div class="sm:flex sm:items-start sm:justify-between">
<div class="flex items-start gap-4">
<% if @application.icon.attached? %>
<%= image_tag @application.icon, class: "h-16 w-16 rounded-lg object-cover border border-gray-200 shrink-0", alt: "#{@application.name} icon" %>
<%= image_tag @application.icon, class: "h-16 w-16 rounded-lg object-cover border border-gray-200 dark:border-gray-700 shrink-0", alt: "#{@application.name} icon" %>
<% else %>
<div class="h-16 w-16 rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center shrink-0">
<svg class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="h-16 w-16 rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center shrink-0">
<svg class="h-8 w-8 text-gray-400 dark:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
<% end %>
<div>
<h1 class="text-2xl font-semibold text-gray-900"><%= @application.name %></h1>
<p class="mt-1 text-sm text-gray-500"><%= @application.description %></p>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100"><%= @application.name %></h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400"><%= @application.description %></p>
</div>
</div>
<div class="mt-4 sm:mt-0 flex gap-3">
<%= link_to "Edit", edit_admin_application_path(@application), class: "rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50" %>
<%= link_to "Edit", edit_admin_application_path(@application), class: "rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600" %>
<%= button_to "Delete", admin_application_path(@application), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500" %>
</div>
</div>
@@ -54,42 +54,42 @@
<div class="space-y-6">
<!-- Basic Information -->
<div class="bg-white shadow sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold leading-6 text-gray-900 mb-4">Basic Information</h3>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100 mb-4">Basic Information</h3>
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">Slug</dt>
<dd class="mt-1 text-sm text-gray-900"><code class="bg-gray-100 px-2 py-1 rounded"><%= @application.slug %></code></dd>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Slug</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100"><code class="bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-2 py-1 rounded"><%= @application.slug %></code></dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Type</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Type</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% case @application.app_type %>
<% when "oidc" %>
<span class="inline-flex items-center rounded-full bg-purple-100 px-2 py-1 text-xs font-medium text-purple-700">OIDC</span>
<span class="inline-flex items-center rounded-full bg-purple-100 dark:bg-purple-900/50 px-2 py-1 text-xs font-medium text-purple-700 dark:text-purple-300">OIDC</span>
<% when "forward_auth" %>
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700">Forward Auth</span>
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-300">Forward Auth</span>
<% end %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Status</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Status</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.active? %>
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">Active</span>
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/50 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-300">Active</span>
<% else %>
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700">Inactive</span>
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300">Inactive</span>
<% end %>
</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">Landing URL</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Landing URL</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.landing_url.present? %>
<%= link_to @application.landing_url, @application.landing_url, target: "_blank", rel: "noopener noreferrer", class: "text-blue-600 hover:text-blue-800 underline" %>
<% else %>
<span class="text-gray-400 italic">Not configured</span>
<span class="text-gray-400 dark:text-gray-500 italic">Not configured</span>
<% end %>
</dd>
</div>
@@ -99,59 +99,59 @@
<!-- OIDC Configuration (only for OIDC apps) -->
<% if @application.oidc? %>
<div class="bg-white shadow sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-base font-semibold leading-6 text-gray-900">OIDC Configuration</h3>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100">OIDC Configuration</h3>
<%= button_to "Regenerate Credentials", regenerate_credentials_admin_application_path(@application), method: :post, data: { turbo_confirm: "This will invalidate the current credentials. Continue?" }, class: "text-sm text-red-600 hover:text-red-900" %>
</div>
<dl class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<dt class="text-sm font-medium text-gray-500">Client Type</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Client Type</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.public_client? %>
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700">Public</span>
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-300">Public</span>
<% else %>
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700">Confidential</span>
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300">Confidential</span>
<% end %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">PKCE</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">PKCE</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.requires_pkce? %>
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">Required</span>
<span class="inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/50 px-2 py-1 text-xs font-medium text-green-700 dark:text-green-300">Required</span>
<% else %>
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700">Optional</span>
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300">Optional</span>
<% end %>
</dd>
</div>
</div>
<% unless flash[:client_id] %>
<div>
<dt class="text-sm font-medium text-gray-500">Client ID</dt>
<dd class="mt-1 text-sm text-gray-900">
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs break-all"><%= @application.client_id %></code>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Client ID</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<code class="block bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-3 py-2 rounded font-mono text-xs break-all"><%= @application.client_id %></code>
</dd>
</div>
<% if @application.confidential_client? %>
<div>
<dt class="text-sm font-medium text-gray-500">Client Secret</dt>
<dd class="mt-1 text-sm text-gray-900">
<div class="bg-gray-100 px-3 py-2 rounded text-xs text-gray-500 italic">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Client Secret</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<div class="bg-gray-100 dark:bg-gray-700 px-3 py-2 rounded text-xs text-gray-500 dark:text-gray-400 italic">
🔒 Client secret is stored securely and cannot be displayed
</div>
<p class="mt-2 text-xs text-gray-500">
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
To get a new client secret, use the "Regenerate Credentials" button above.
</p>
</dd>
</div>
<% else %>
<div>
<dt class="text-sm font-medium text-gray-500">Client Secret</dt>
<dd class="mt-1 text-sm text-gray-900">
<div class="bg-blue-50 px-3 py-2 rounded text-xs text-blue-600">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Client Secret</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<div class="bg-blue-50 dark:bg-blue-900/30 px-3 py-2 rounded text-xs text-blue-600 dark:text-blue-400">
Public clients do not use a client secret. PKCE is required for authorization.
</div>
</dd>
@@ -159,33 +159,33 @@
<% end %>
<% end %>
<div>
<dt class="text-sm font-medium text-gray-500">Redirect URIs</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Redirect URIs</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.redirect_uris.present? %>
<% @application.parsed_redirect_uris.each do |uri| %>
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs break-all mb-2"><%= uri %></code>
<code class="block bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-3 py-2 rounded font-mono text-xs break-all mb-2"><%= uri %></code>
<% end %>
<% else %>
<span class="text-gray-400">No redirect URIs configured</span>
<span class="text-gray-400 dark:text-gray-500">No redirect URIs configured</span>
<% end %>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
Backchannel Logout URI
<% if @application.supports_backchannel_logout? %>
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">Enabled</span>
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/50 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300">Enabled</span>
<% end %>
</dt>
<dd class="mt-1 text-sm text-gray-900">
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.backchannel_logout_uri.present? %>
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs break-all"><%= @application.backchannel_logout_uri %></code>
<p class="mt-2 text-xs text-gray-500">
<code class="block bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-3 py-2 rounded font-mono text-xs break-all"><%= @application.backchannel_logout_uri %></code>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
When users log out, Clinch will send logout notifications to this endpoint for immediate session termination.
</p>
<% else %>
<span class="text-gray-400 italic">Not configured</span>
<p class="mt-1 text-xs text-gray-500">
<span class="text-gray-400 dark:text-gray-500 italic">Not configured</span>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Backchannel logout is optional. Configure it if the application supports OpenID Connect Backchannel Logout.
</p>
<% end %>
@@ -198,23 +198,23 @@
<!-- Forward Auth Configuration (only for Forward Auth apps) -->
<% if @application.forward_auth? %>
<div class="bg-white shadow sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold leading-6 text-gray-900 mb-4">Forward Auth Configuration</h3>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100 mb-4">Forward Auth Configuration</h3>
<dl class="space-y-4">
<div>
<dt class="text-sm font-medium text-gray-500">Domain Pattern</dt>
<dd class="mt-1 text-sm text-gray-900">
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs"><%= @application.domain_pattern %></code>
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Domain Pattern</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<code class="block bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-3 py-2 rounded font-mono text-xs"><%= @application.domain_pattern %></code>
</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Headers Configuration</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Headers Configuration</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @application.headers_config.present? && @application.headers_config.any? %>
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs whitespace-pre-wrap"><%= JSON.pretty_generate(@application.headers_config) %></code>
<code class="block bg-gray-100 dark:bg-gray-700 dark:text-gray-200 px-3 py-2 rounded font-mono text-xs whitespace-pre-wrap"><%= JSON.pretty_generate(@application.headers_config) %></code>
<% else %>
<div class="bg-gray-100 px-3 py-2 rounded text-xs text-gray-500">
<div class="bg-gray-100 dark:bg-gray-700 px-3 py-2 rounded text-xs text-gray-500 dark:text-gray-400">
Using default headers: X-Remote-User, X-Remote-Email, X-Remote-Name, X-Remote-Username, X-Remote-Groups, X-Remote-Admin
</div>
<% end %>
@@ -226,29 +226,29 @@
<% end %>
<!-- Group Access Control -->
<div class="bg-white shadow sm:rounded-lg">
<div class="bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-base font-semibold leading-6 text-gray-900 mb-4">Access Control</h3>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100 mb-4">Access Control</h3>
<div>
<dt class="text-sm font-medium text-gray-500 mb-2">Allowed Groups</dt>
<dd class="mt-1 text-sm text-gray-900">
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">Allowed Groups</dt>
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
<% if @allowed_groups.empty? %>
<div class="rounded-md bg-blue-50 p-4">
<div class="rounded-md bg-blue-50 dark:bg-blue-900/30 p-4">
<div class="flex">
<div class="ml-3">
<p class="text-sm text-blue-700">
<p class="text-sm text-blue-700 dark:text-blue-300">
No groups assigned - all active users can access this application.
</p>
</div>
</div>
</div>
<% else %>
<ul class="divide-y divide-gray-200 border border-gray-200 rounded-md">
<ul class="divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-md">
<% @allowed_groups.each do |group| %>
<li class="px-4 py-3 flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-900"><%= group.name %></p>
<p class="text-xs text-gray-500"><%= pluralize(group.users.count, "member") %></p>
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= group.name %></p>
<p class="text-xs text-gray-500 dark:text-gray-400"><%= pluralize(group.users.count, "member") %></p>
</div>
</li>
<% end %>