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:
@@ -1,3 +1,23 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "@tailwindcss/forms";
|
@plugin "@tailwindcss/forms";
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
.dark input:where([type="text"], [type="email"], [type="password"], [type="number"], [type="url"], [type="tel"], [type="search"]),
|
||||||
|
.dark textarea,
|
||||||
|
.dark select {
|
||||||
|
background-color: var(--color-gray-800);
|
||||||
|
border-color: var(--color-gray-600);
|
||||||
|
color: var(--color-gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark input::placeholder,
|
||||||
|
.dark textarea::placeholder {
|
||||||
|
color: var(--color-gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark input:where([type="checkbox"], [type="radio"]) {
|
||||||
|
background-color: var(--color-gray-700);
|
||||||
|
border-color: var(--color-gray-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ module ApplicationHelper
|
|||||||
|
|
||||||
def border_class_for(type)
|
def border_class_for(type)
|
||||||
case type.to_s
|
case type.to_s
|
||||||
when "notice" then "border-green-200"
|
when "notice" then "border-green-200 dark:border-green-700"
|
||||||
when "alert", "error" then "border-red-200"
|
when "alert", "error" then "border-red-200 dark:border-red-700"
|
||||||
when "warning" then "border-yellow-200"
|
when "warning" then "border-yellow-200 dark:border-yellow-700"
|
||||||
when "info" then "border-blue-200"
|
when "info" then "border-blue-200 dark:border-blue-700"
|
||||||
else "border-gray-200"
|
else "border-gray-200 dark:border-gray-700"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
27
app/javascript/controllers/dark_mode_controller.js
Normal file
27
app/javascript/controllers/dark_mode_controller.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["icon"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
document.documentElement.classList.toggle("dark")
|
||||||
|
const isDark = document.documentElement.classList.contains("dark")
|
||||||
|
localStorage.setItem("theme", isDark ? "dark" : "light")
|
||||||
|
this.updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIcon() {
|
||||||
|
const isDark = document.documentElement.classList.contains("dark")
|
||||||
|
this.iconTargets.forEach(icon => {
|
||||||
|
if (icon.dataset.mode === "dark") {
|
||||||
|
icon.classList.toggle("hidden", !isDark)
|
||||||
|
} else {
|
||||||
|
icon.classList.toggle("hidden", isDark)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,50 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Sessions</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Sessions</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">Manage your active sessions and connected applications.</p>
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Manage your active sessions and connected applications.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Connected Applications -->
|
<!-- Connected Applications -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Connected Applications</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Connected Applications</h3>
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
||||||
<p>These applications have access to your account. You can revoke access at any time.</p>
|
<p>These applications have access to your account. You can revoke access at any time.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<% if @connected_applications.any? %>
|
<% if @connected_applications.any? %>
|
||||||
<ul role="list" class="divide-y divide-gray-200">
|
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @connected_applications.each do |consent| %>
|
<% @connected_applications.each do |consent| %>
|
||||||
<li class="py-4">
|
<li class="py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p class="text-sm font-medium text-gray-900">
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
<%= consent.application.name %>
|
<%= consent.application.name %>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-500">
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
Access to: <%= consent.formatted_scopes %>
|
Access to: <%= consent.formatted_scopes %>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-gray-400">
|
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||||
Authorized <%= time_ago_in_words(consent.granted_at) %> ago
|
Authorized <%= time_ago_in_words(consent.granted_at) %> ago
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<%= button_to "Revoke Access", revoke_consent_active_sessions_path(application_id: consent.application.id), method: :delete,
|
<%= button_to "Revoke Access", revoke_consent_active_sessions_path(application_id: consent.application.id), method: :delete,
|
||||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2",
|
class: "inline-flex items-center rounded-md border border-red-300 bg-white dark:bg-gray-700 dark:ring-gray-600 dark:text-gray-200 px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900",
|
||||||
form: { data: { turbo_confirm: "Are you sure you want to revoke access to #{consent.application.name}? You'll need to re-authorize this application to use it again." } } %>
|
form: { data: { turbo_confirm: "Are you sure you want to revoke access to #{consent.application.name}? You'll need to re-authorize this application to use it again." } } %>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500">No connected applications.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">No connected applications.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @connected_applications.any? %>
|
<% if @connected_applications.any? %>
|
||||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
<div class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<div class="inline-block">
|
<div class="inline-block">
|
||||||
<%= button_to "Revoke All App Access", revoke_all_consents_active_sessions_path, method: :delete,
|
<%= button_to "Revoke All App Access", revoke_all_consents_active_sessions_path, method: :delete,
|
||||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 whitespace-nowrap",
|
class: "inline-flex items-center rounded-md border border-red-300 bg-white dark:bg-gray-700 dark:ring-gray-600 dark:text-gray-200 px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900 whitespace-nowrap",
|
||||||
form: { data: { turbo_confirm: "This will revoke access from all connected applications. You'll need to re-authorize each application to use them again. Are you sure?" } } %>
|
form: { data: { turbo_confirm: "This will revoke access from all connected applications. You'll need to re-authorize each application to use them again. Are you sure?" } } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,37 +55,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active Sessions -->
|
<!-- Active Sessions -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Active Sessions</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Active Sessions</h3>
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
||||||
<p>These devices are currently signed in to your account. Revoke any sessions that you don't recognize.</p>
|
<p>These devices are currently signed in to your account. Revoke any sessions that you don't recognize.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<% if @active_sessions.any? %>
|
<% if @active_sessions.any? %>
|
||||||
<ul role="list" class="divide-y divide-gray-200">
|
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @active_sessions.each do |session| %>
|
<% @active_sessions.each do |session| %>
|
||||||
<li class="py-4">
|
<li class="py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<p class="text-sm font-medium text-gray-900">
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
<%= session.device_name || "Unknown Device" %>
|
<%= session.device_name || "Unknown Device" %>
|
||||||
<% if session.id == Current.session.id %>
|
<% if session.id == Current.session.id %>
|
||||||
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 dark:bg-green-900/50 px-2.5 py-0.5 text-xs font-medium text-green-800 dark:text-green-200">
|
||||||
This device
|
This device
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-sm text-gray-500">
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<%= session.ip_address %>
|
<%= session.ip_address %>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-gray-400">
|
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
|
||||||
Last active <%= time_ago_in_words(session.last_activity_at || session.updated_at) %> ago
|
Last active <%= time_ago_in_words(session.last_activity_at || session.updated_at) %> ago
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% if session.id != Current.session.id %>
|
<% if session.id != Current.session.id %>
|
||||||
<%= button_to "Revoke", session_path(session), method: :delete,
|
<%= button_to "Revoke", session_path(session), method: :delete,
|
||||||
class: "inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
class: "inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 dark:text-gray-200 px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900",
|
||||||
form: { data: { turbo_confirm: "Are you sure you want to revoke this session?" } } %>
|
form: { data: { turbo_confirm: "Are you sure you want to revoke this session?" } } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,15 +93,15 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500">No other active sessions.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">No other active sessions.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @active_sessions.count > 1 %>
|
<% if @active_sessions.count > 1 %>
|
||||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
<div class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<div class="inline-block">
|
<div class="inline-block">
|
||||||
<%= button_to "Sign Out Everywhere Else", session_path(Current.session), method: :delete,
|
<%= button_to "Sign Out Everywhere Else", session_path(Current.session), method: :delete,
|
||||||
class: "inline-flex items-center rounded-md border border-orange-300 bg-white px-3 py-2 text-sm font-medium text-orange-700 shadow-sm hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 whitespace-nowrap",
|
class: "inline-flex items-center rounded-md border border-orange-300 bg-white dark:bg-gray-700 dark:ring-gray-600 dark:text-gray-200 px-3 py-2 text-sm font-medium text-orange-700 shadow-sm hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900 whitespace-nowrap",
|
||||||
form: { data: { turbo_confirm: "This will sign you out from all other devices except this one. Are you sure?" } } %>
|
form: { data: { turbo_confirm: "This will sign you out from all other devices except this one. Are you sure?" } } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,4 +111,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="max-w-3xl">
|
<div class="max-w-3xl">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Edit Application</h1>
|
<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 mb-6">Editing: <%= @application.name %></p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Editing: <%= @application.name %></p>
|
||||||
<%= render "form", application: @application %>
|
<%= render "form", application: @application %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="sm:flex sm:items-center">
|
<div class="sm:flex sm:items-center">
|
||||||
<div class="sm:flex-auto">
|
<div class="sm:flex-auto">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900">Applications</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Applications</h1>
|
||||||
<p class="mt-2 text-sm text-gray-700">Manage OIDC Clients.</p>
|
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300">Manage OIDC Clients.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
<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" %>
|
<%= 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="mt-8 flow-root">
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<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">
|
<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>
|
<thead>
|
||||||
<tr>
|
<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="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">Slug</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">Type</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">Status</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">Groups</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">
|
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
|
||||||
<span class="sr-only">Actions</span>
|
<span class="sr-only">Actions</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @applications.each do |application| %>
|
<% @applications.each do |application| %>
|
||||||
<tr>
|
<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">
|
<div class="flex items-center gap-3">
|
||||||
<% if application.icon.attached? %>
|
<% 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 %>
|
<% else %>
|
||||||
<div class="h-10 w-10 rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center flex-shrink-0">
|
<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" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,29 +41,29 @@
|
|||||||
<%= link_to application.name, admin_application_path(application), class: "text-blue-600 hover:text-blue-900" %>
|
<%= link_to application.name, admin_application_path(application), class: "text-blue-600 hover:text-blue-900" %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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">
|
||||||
<code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= application.slug %></code>
|
<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>
|
||||||
<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 %>
|
<% case application.app_type %>
|
||||||
<% when "oidc" %>
|
<% 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" %>
|
<% 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" %>
|
<% 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 %>
|
<% end %>
|
||||||
</td>
|
</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? %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</td>
|
</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? %>
|
<% 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 %>
|
<% else %>
|
||||||
<%= application.allowed_groups.count %>
|
<%= application.allowed_groups.count %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="max-w-3xl">
|
<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 %>
|
<%= render "form", application: @application %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<% if flash[:client_id] %>
|
<% if flash[:client_id] %>
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded-md p-4 mb-6">
|
<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 mb-2">🔐 OIDC Client Credentials</h4>
|
<h4 class="text-sm font-medium text-yellow-800 dark:text-yellow-200 mb-2">🔐 OIDC Client Credentials</h4>
|
||||||
<% if flash[:public_client] %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div>
|
<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>
|
</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] %>
|
<% if flash[:client_secret] %>
|
||||||
<div class="mt-3">
|
<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>
|
||||||
<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] %>
|
<% elsif flash[:public_client] %>
|
||||||
<div class="mt-3">
|
<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>
|
||||||
<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.
|
Public clients do not have a client secret. PKCE is required.
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -32,21 +32,21 @@
|
|||||||
<div class="sm:flex sm:items-start sm:justify-between">
|
<div class="sm:flex sm:items-start sm:justify-between">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<% if @application.icon.attached? %>
|
<% 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 %>
|
<% else %>
|
||||||
<div class="h-16 w-16 rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center shrink-0">
|
<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" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-semibold text-gray-900"><%= @application.name %></h1>
|
<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"><%= @application.description %></p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400"><%= @application.description %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 sm:mt-0 flex gap-3">
|
<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" %>
|
<%= 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>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,42 +54,42 @@
|
|||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Basic Information -->
|
<!-- 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">
|
<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">
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Slug</dt>
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">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>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Type</dt>
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Type</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-900">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% case @application.app_type %>
|
<% case @application.app_type %>
|
||||||
<% when "oidc" %>
|
<% 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" %>
|
<% 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 %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Status</dt>
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Status</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.active? %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="sm:col-span-2">
|
<div class="sm:col-span-2">
|
||||||
<dt class="text-sm font-medium text-gray-500">Landing URL</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% if @application.landing_url.present? %>
|
<% 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" %>
|
<%= link_to @application.landing_url, @application.landing_url, target: "_blank", rel: "noopener noreferrer", class: "text-blue-600 hover:text-blue-800 underline" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 italic">Not configured</span>
|
<span class="text-gray-400 dark:text-gray-500 italic">Not configured</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,59 +99,59 @@
|
|||||||
|
|
||||||
<!-- OIDC Configuration (only for OIDC apps) -->
|
<!-- OIDC Configuration (only for OIDC apps) -->
|
||||||
<% if @application.oidc? %>
|
<% 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="px-4 py-5 sm:p-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<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" %>
|
<%= 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>
|
</div>
|
||||||
<dl class="space-y-4">
|
<dl class="space-y-4">
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Client Type</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% if @application.public_client? %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">PKCE</dt>
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">PKCE</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.requires_pkce? %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% unless flash[:client_id] %>
|
<% unless flash[:client_id] %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Client ID</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs break-all"><%= @application.client_id %></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"><%= @application.client_id %></code>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% if @application.confidential_client? %>
|
<% if @application.confidential_client? %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Client Secret</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<div class="bg-gray-100 px-3 py-2 rounded text-xs text-gray-500 italic">
|
<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
|
🔒 Client secret is stored securely and cannot be displayed
|
||||||
</div>
|
</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.
|
To get a new client secret, use the "Regenerate Credentials" button above.
|
||||||
</p>
|
</p>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Client Secret</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<div class="bg-blue-50 px-3 py-2 rounded text-xs text-blue-600">
|
<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.
|
Public clients do not use a client secret. PKCE is required for authorization.
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
@@ -159,33 +159,33 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Redirect URIs</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% if @application.redirect_uris.present? %>
|
<% if @application.redirect_uris.present? %>
|
||||||
<% @application.parsed_redirect_uris.each do |uri| %>
|
<% @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 %>
|
<% end %>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<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
|
Backchannel Logout URI
|
||||||
<% if @application.supports_backchannel_logout? %>
|
<% 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 %>
|
<% end %>
|
||||||
</dt>
|
</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? %>
|
<% 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>
|
<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">
|
<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.
|
When users log out, Clinch will send logout notifications to this endpoint for immediate session termination.
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400 italic">Not configured</span>
|
<span class="text-gray-400 dark:text-gray-500 italic">Not configured</span>
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
<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.
|
Backchannel logout is optional. Configure it if the application supports OpenID Connect Backchannel Logout.
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -198,23 +198,23 @@
|
|||||||
|
|
||||||
<!-- Forward Auth Configuration (only for Forward Auth apps) -->
|
<!-- Forward Auth Configuration (only for Forward Auth apps) -->
|
||||||
<% if @application.forward_auth? %>
|
<% 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">
|
<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">
|
<dl class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Domain Pattern</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<code class="block bg-gray-100 px-3 py-2 rounded font-mono text-xs"><%= @application.domain_pattern %></code>
|
<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>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500">Headers Configuration</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% if @application.headers_config.present? && @application.headers_config.any? %>
|
<% 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 %>
|
<% 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
|
Using default headers: X-Remote-User, X-Remote-Email, X-Remote-Name, X-Remote-Username, X-Remote-Groups, X-Remote-Admin
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -226,29 +226,29 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Group Access Control -->
|
<!-- 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">
|
<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>
|
<div>
|
||||||
<dt class="text-sm font-medium text-gray-500 mb-2">Allowed Groups</dt>
|
<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">
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">
|
||||||
<% if @allowed_groups.empty? %>
|
<% 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="flex">
|
||||||
<div class="ml-3">
|
<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.
|
No groups assigned - all active users can access this application.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% 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| %>
|
<% @allowed_groups.each do |group| %>
|
||||||
<li class="px-4 py-3 flex items-center justify-between">
|
<li class="px-4 py-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900"><%= group.name %></p>
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= group.name %></p>
|
||||||
<p class="text-xs text-gray-500"><%= pluralize(group.users.count, "member") %></p>
|
<p class="text-xs text-gray-500 dark:text-gray-400"><%= pluralize(group.users.count, "member") %></p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Admin Dashboard</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Admin Dashboard</h1>
|
||||||
<p class="mt-2 text-gray-600">System overview and quick actions</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">System overview and quick actions</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<!-- Users Card -->
|
<!-- Users Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Total Users
|
Total Users
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="flex items-baseline">
|
<dd class="flex items-baseline">
|
||||||
<div class="text-2xl font-semibold text-gray-900">
|
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
<%= @user_count %>
|
<%= @user_count %>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-2 text-sm text-gray-600">
|
<div class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
(<%= @active_user_count %> active)
|
(<%= @active_user_count %> active)
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
@@ -30,30 +30,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Manage users", admin_users_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Manage users", admin_users_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Applications Card -->
|
<!-- Applications Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h4a1 1 0 010 2H6v10a1 1 0 001 1h10a1 1 0 001-1v-3a1 1 0 112 0v3a3 3 0 01-3 3H7a3 3 0 01-3-3V6a1 1 0 011-1zm9 1a1 1 0 10-2 0v3a1 1 0 102 0V6zm-4 8a1 1 0 100 2h.01a1 1 0 100-2H9zm4 0a1 1 0 100 2h.01a1 1 0 100-2H13z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h4a1 1 0 010 2H6v10a1 1 0 001 1h10a1 1 0 001-1v-3a1 1 0 112 0v3a3 3 0 01-3 3H7a3 3 0 01-3-3V6a1 1 0 011-1zm9 1a1 1 0 10-2 0v3a1 1 0 102 0V6zm-4 8a1 1 0 100 2h.01a1 1 0 100-2H9zm4 0a1 1 0 100 2h.01a1 1 0 100-2H13z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Applications
|
Applications
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="flex items-baseline">
|
<dd class="flex items-baseline">
|
||||||
<div class="text-2xl font-semibold text-gray-900">
|
<div class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
<%= @application_count %>
|
<%= @application_count %>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-2 text-sm text-gray-600">
|
<div class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
(<%= @active_application_count %> active)
|
(<%= @active_application_count %> active)
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
@@ -61,33 +61,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Manage applications", admin_applications_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Manage applications", admin_applications_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Groups Card -->
|
<!-- Groups Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Groups
|
Groups
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-2xl font-semibold text-gray-900">
|
<dd class="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
<%= @group_count %>
|
<%= @group_count %>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Manage groups", admin_groups_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Manage groups", admin_groups_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,26 +95,26 @@
|
|||||||
|
|
||||||
<!-- Recent Users -->
|
<!-- Recent Users -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Recent Users</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Recent Users</h2>
|
||||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg">
|
||||||
<ul class="divide-y divide-gray-200">
|
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @recent_users.each do |user| %>
|
<% @recent_users.each do |user| %>
|
||||||
<li class="px-6 py-4">
|
<li class="px-6 py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900"><%= user.email_address %></p>
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= user.email_address %></p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Created <%= time_ago_in_words(user.created_at) %> ago
|
Created <%= time_ago_in_words(user.created_at) %> ago
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700">Admin</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">Admin</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if user.totp_enabled? %>
|
<% if user.totp_enabled? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700">2FA</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">2FA</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700"><%= user.status.titleize %></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"><%= user.status.titleize %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -125,21 +125,21 @@
|
|||||||
|
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Quick Actions</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Quick Actions</h2>
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<%= link_to new_admin_user_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to new_admin_user_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Create User</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Create User</h3>
|
||||||
<p class="text-sm text-gray-600">Add a new user to the system</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">Add a new user to the system</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= link_to new_admin_application_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to new_admin_application_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Register Application</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Register Application</h3>
|
||||||
<p class="text-sm text-gray-600">Add a new OIDC or ForwardAuth app</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">Add a new OIDC or ForwardAuth app</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= link_to new_admin_group_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to new_admin_group_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Create Group</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Create Group</h3>
|
||||||
<p class="text-sm text-gray-600">Organize users into a new group</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">Organize users into a new group</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,51 +2,51 @@
|
|||||||
<%= render "shared/form_errors", form: form %>
|
<%= render "shared/form_errors", form: form %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :name, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_field :name, required: true, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "developers" %>
|
<%= form.text_field :name, required: true, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "developers" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Group names are automatically normalized to lowercase.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Group names are automatically normalized to lowercase.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :description, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :description, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_area :description, rows: 3, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "Optional description of this group" %>
|
<%= form.text_area :description, rows: 3, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "Optional description of this group" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :user_ids, "Group Members", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :user_ids, "Group Members", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<div class="mt-2 space-y-2 max-h-64 overflow-y-auto border border-gray-200 rounded-md p-3">
|
<div class="mt-2 space-y-2 max-h-64 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-md p-3">
|
||||||
<% if @available_users.any? %>
|
<% if @available_users.any? %>
|
||||||
<% @available_users.each do |user| %>
|
<% @available_users.each do |user| %>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<%= check_box_tag "group[user_ids][]", user.id, group.users.include?(user), class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
|
<%= check_box_tag "group[user_ids][]", user.id, group.users.include?(user), class: "h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" %>
|
||||||
<%= label_tag "group_user_ids_#{user.id}", user.email_address, class: "ml-2 text-sm text-gray-900" %>
|
<%= label_tag "group_user_ids_#{user.id}", user.email_address, class: "ml-2 text-sm text-gray-900 dark:text-gray-100" %>
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="ml-2 inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700">Admin</span>
|
<span class="ml-2 inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-300">Admin</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-sm text-gray-500">No users available.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">No users available.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-1 text-sm text-gray-500">Select which users should be members of this group.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Select which users should be members of this group.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-controller="json-validator" data-json-validator-valid-class="border-green-500 focus:border-green-500 focus:ring-green-500" data-json-validator-invalid-class="border-red-500 focus:border-red-500 focus:ring-red-500" data-json-validator-valid-status-class="text-green-600" data-json-validator-invalid-status-class="text-red-600">
|
<div data-controller="json-validator" data-json-validator-valid-class="border-green-500 focus:border-green-500 focus:ring-green-500" data-json-validator-invalid-class="border-red-500 focus:border-red-500 focus:ring-red-500" data-json-validator-valid-status-class="text-green-600" data-json-validator-invalid-status-class="text-red-600">
|
||||||
<%= form.label :custom_claims, "Custom Claims (JSON)", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :custom_claims, "Custom Claims (JSON)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_area :custom_claims, value: (group.custom_claims.present? ? JSON.pretty_generate(group.custom_claims) : ""), rows: 8,
|
<%= form.text_area :custom_claims, value: (group.custom_claims.present? ? JSON.pretty_generate(group.custom_claims) : ""), rows: 8,
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
||||||
placeholder: '{"roles": ["admin", "editor"]}',
|
placeholder: '{"roles": ["admin", "editor"]}',
|
||||||
data: {
|
data: {
|
||||||
action: "input->json-validator#validate blur->json-validator#format",
|
action: "input->json-validator#validate blur->json-validator#format",
|
||||||
json_validator_target: "textarea"
|
json_validator_target: "textarea"
|
||||||
} %>
|
} %>
|
||||||
<div class="mt-2 text-sm text-gray-600 space-y-1">
|
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<p>Optional: Custom claims to add to OIDC tokens for all members. These will be merged with user-level claims.</p>
|
<p>Optional: Custom claims to add to OIDC tokens for all members. These will be merged with user-level claims.</p>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button type="button" data-action="json-validator#format" class="text-xs bg-gray-100 hover:bg-gray-200 px-2 py-1 rounded">Format JSON</button>
|
<button type="button" data-action="json-validator#format" class="text-xs bg-gray-100 dark:bg-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 px-2 py-1 rounded">Format JSON</button>
|
||||||
<button type="button" data-action="json-validator#insertSample" data-json-sample='{"roles": ["admin", "editor"], "permissions": ["read", "write"], "team": "backend"}' class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded">Insert Example</button>
|
<button type="button" data-action="json-validator#insertSample" data-json-sample='{"roles": ["admin", "editor"], "permissions": ["read", "write"], "team": "backend"}' class="text-xs bg-blue-100 dark:bg-blue-900/50 hover:bg-blue-200 dark:hover:bg-blue-900 text-blue-700 dark:text-blue-300 px-2 py-1 rounded">Insert Example</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
||||||
@@ -55,6 +55,6 @@
|
|||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<%= form.submit group.persisted? ? "Update Group" : "Create Group", class: "rounded-md bg-blue-600 px-3 py-2 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" %>
|
<%= form.submit group.persisted? ? "Update Group" : "Create Group", class: "rounded-md bg-blue-600 px-3 py-2 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" %>
|
||||||
<%= link_to "Cancel", admin_groups_path, 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 "Cancel", admin_groups_path, 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" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Edit Group</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Edit Group</h1>
|
||||||
<p class="text-sm text-gray-600 mb-6">Editing: <%= @group.name %></p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Editing: <%= @group.name %></p>
|
||||||
<%= render "form", group: @group %>
|
<%= render "form", group: @group %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="sm:flex sm:items-center">
|
<div class="sm:flex sm:items-center">
|
||||||
<div class="sm:flex-auto">
|
<div class="sm:flex-auto">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900">Groups</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Groups</h1>
|
||||||
<p class="mt-2 text-sm text-gray-700">Organize users into groups for application access control.</p>
|
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300">Organize users into groups for application access control.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||||
<%= link_to "New Group", new_admin_group_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" %>
|
<%= link_to "New Group", new_admin_group_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,31 +11,31 @@
|
|||||||
<div class="mt-8 flow-root">
|
<div class="mt-8 flow-root">
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<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">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Name</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">Name</th>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Description</th>
|
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Description</th>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Members</th>
|
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Members</th>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Applications</th>
|
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Applications</th>
|
||||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
|
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
|
||||||
<span class="sr-only">Actions</span>
|
<span class="sr-only">Actions</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @groups.each do |group| %>
|
<% @groups.each do |group| %>
|
||||||
<tr>
|
<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">
|
||||||
<%= link_to group.name, admin_group_path(group), class: "text-blue-600 hover:text-blue-900" %>
|
<%= link_to group.name, admin_group_path(group), class: "text-blue-600 hover:text-blue-900" %>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-500">
|
<td class="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<%= truncate(group.description, length: 80) || content_tag(:span, "No description", class: "text-gray-400") %>
|
<%= truncate(group.description, length: 80) || content_tag(:span, "No description", class: "text-gray-400 dark:text-gray-500") %>
|
||||||
</td>
|
</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">
|
||||||
<%= pluralize(group.users.count, "member") %>
|
<%= pluralize(group.users.count, "member") %>
|
||||||
</td>
|
</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">
|
||||||
<%= pluralize(group.applications.count, "app") %>
|
<%= pluralize(group.applications.count, "app") %>
|
||||||
</td>
|
</td>
|
||||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
|
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">New Group</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">New Group</h1>
|
||||||
<%= render "form", group: @group %>
|
<%= render "form", group: @group %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="sm:flex sm:items-center sm:justify-between">
|
<div class="sm:flex sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-semibold text-gray-900"><%= @group.name %></h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100"><%= @group.name %></h1>
|
||||||
<% if @group.description.present? %>
|
<% if @group.description.present? %>
|
||||||
<p class="mt-1 text-sm text-gray-500"><%= @group.description %></p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400"><%= @group.description %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 sm:mt-0 flex gap-3">
|
<div class="mt-4 sm:mt-0 flex gap-3">
|
||||||
<%= link_to "Edit", edit_admin_group_path(@group), 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_group_path(@group), 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_group_path(@group), 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" %>
|
<%= button_to "Delete", admin_group_path(@group), 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>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,25 +15,25 @@
|
|||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Members -->
|
<!-- Members -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 mb-4">
|
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||||
Members (<%= @members.count %>)
|
Members (<%= @members.count %>)
|
||||||
</h3>
|
</h3>
|
||||||
<% if @members.any? %>
|
<% if @members.any? %>
|
||||||
<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">
|
||||||
<% @members.each do |user| %>
|
<% @members.each do |user| %>
|
||||||
<li class="px-4 py-3 flex items-center justify-between">
|
<li class="px-4 py-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900"><%= user.email_address %></p>
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= user.email_address %></p>
|
||||||
<div class="flex gap-2 mt-1">
|
<div class="flex gap-2 mt-1">
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700">Admin</span>
|
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-300">Admin</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if user.totp_enabled? %>
|
<% if user.totp_enabled? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700">2FA</span>
|
<span class="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">2FA</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700"><%= user.status.titleize %></span>
|
<span class="inline-flex items-center rounded-full bg-gray-100 dark:bg-gray-700 px-2 py-0.5 text-xs font-medium text-gray-700 dark:text-gray-300"><%= user.status.titleize %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= link_to "View", admin_user_path(user), class: "text-blue-600 hover:text-blue-900 text-sm" %>
|
<%= link_to "View", admin_user_path(user), class: "text-blue-600 hover:text-blue-900 text-sm" %>
|
||||||
@@ -41,36 +41,36 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="rounded-md bg-gray-50 p-4">
|
<div class="rounded-md bg-gray-50 dark:bg-gray-700 p-4">
|
||||||
<p class="text-sm text-gray-500">No members in this group yet.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">No members in this group yet.</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Applications -->
|
<!-- Applications -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 mb-4">
|
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-gray-100 mb-4">
|
||||||
Assigned Applications (<%= @applications.count %>)
|
Assigned Applications (<%= @applications.count %>)
|
||||||
</h3>
|
</h3>
|
||||||
<% if @applications.any? %>
|
<% if @applications.any? %>
|
||||||
<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">
|
||||||
<% @applications.each do |app| %>
|
<% @applications.each do |app| %>
|
||||||
<li class="px-4 py-3 flex items-center justify-between">
|
<li class="px-4 py-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-gray-900"><%= app.name %></p>
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= app.name %></p>
|
||||||
<div class="flex gap-2 mt-1">
|
<div class="flex gap-2 mt-1">
|
||||||
<% case app.app_type %>
|
<% case app.app_type %>
|
||||||
<% when "oidc" %>
|
<% when "oidc" %>
|
||||||
<span class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 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-0.5 text-xs font-medium text-purple-700 dark:text-purple-300">OIDC</span>
|
||||||
<% when "trusted_header" %>
|
<% when "trusted_header" %>
|
||||||
<span class="inline-flex items-center rounded-full bg-indigo-100 px-2 py-0.5 text-xs font-medium text-indigo-700">ForwardAuth</span>
|
<span class="inline-flex items-center rounded-full bg-indigo-100 dark:bg-indigo-900/50 px-2 py-0.5 text-xs font-medium text-indigo-700 dark:text-indigo-300">ForwardAuth</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if app.active? %>
|
<% if app.active? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 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-0.5 text-xs font-medium text-green-700 dark:text-green-300">Active</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 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-0.5 text-xs font-medium text-gray-700 dark:text-gray-300">Inactive</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="rounded-md bg-gray-50 p-4">
|
<div class="rounded-md bg-gray-50 dark:bg-gray-700 p-4">
|
||||||
<p class="text-sm text-gray-500">This group is not assigned to any applications.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">This group is not assigned to any applications.</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,29 +3,29 @@
|
|||||||
|
|
||||||
<!-- OIDC Apps: Custom Claims -->
|
<!-- OIDC Apps: Custom Claims -->
|
||||||
<% if oidc_apps.any? %>
|
<% if oidc_apps.any? %>
|
||||||
<div class="mt-12 border-t pt-8">
|
<div class="mt-12 border-t dark:border-gray-700 pt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">OIDC App-Specific Claims</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">OIDC App-Specific Claims</h2>
|
||||||
<p class="text-sm text-gray-600 mb-6">
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
||||||
Configure custom claims that apply only to specific OIDC applications. These override both group and user global claims and are included in ID tokens.
|
Configure custom claims that apply only to specific OIDC applications. These override both group and user global claims and are included in ID tokens.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<% oidc_apps.each do |app| %>
|
<% oidc_apps.each do |app| %>
|
||||||
<% app_claim = user.application_user_claims.find_by(application: app) %>
|
<% app_claim = user.application_user_claims.find_by(application: app) %>
|
||||||
<details class="border rounded-lg" <%= "open" if app_claim&.custom_claims&.any? %>>
|
<details class="border dark:border-gray-700 rounded-lg" <%= "open" if app_claim&.custom_claims&.any? %>>
|
||||||
<summary class="cursor-pointer bg-gray-50 px-4 py-3 hover:bg-gray-100 rounded-t-lg flex items-center justify-between">
|
<summary class="cursor-pointer bg-gray-50 dark:bg-gray-800 px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-t-lg flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="font-medium text-gray-900"><%= app.name %></span>
|
<span class="font-medium text-gray-900 dark:text-gray-100"><%= app.name %></span>
|
||||||
<span class="text-xs px-2 py-1 rounded-full bg-blue-100 text-blue-700">
|
<span class="text-xs px-2 py-1 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300">
|
||||||
OIDC
|
OIDC
|
||||||
</span>
|
</span>
|
||||||
<% if app_claim&.custom_claims&.any? %>
|
<% if app_claim&.custom_claims&.any? %>
|
||||||
<span class="text-xs px-2 py-1 rounded-full bg-amber-100 text-amber-700">
|
<span class="text-xs px-2 py-1 rounded-full bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300">
|
||||||
<%= app_claim.custom_claims.keys.count %> claim(s)
|
<%= app_claim.custom_claims.keys.count %> claim(s)
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<svg class="h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-5 w-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</summary>
|
</summary>
|
||||||
@@ -35,22 +35,22 @@
|
|||||||
<%= hidden_field_tag :application_id, app.id %>
|
<%= hidden_field_tag :application_id, app.id %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">Custom Claims (JSON)</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Custom Claims (JSON)</label>
|
||||||
<%= text_area_tag :custom_claims,
|
<%= text_area_tag :custom_claims,
|
||||||
(app_claim&.custom_claims.present? ? JSON.pretty_generate(app_claim.custom_claims) : ""),
|
(app_claim&.custom_claims.present? ? JSON.pretty_generate(app_claim.custom_claims) : ""),
|
||||||
rows: 8,
|
rows: 8,
|
||||||
class: "w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
class: "w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
||||||
placeholder: '{"kavita_groups": ["admin"], "library_access": "all"}',
|
placeholder: '{"kavita_groups": ["admin"], "library_access": "all"}',
|
||||||
data: {
|
data: {
|
||||||
action: "input->json-validator#validate blur->json-validator#format",
|
action: "input->json-validator#validate blur->json-validator#format",
|
||||||
json_validator_target: "textarea"
|
json_validator_target: "textarea"
|
||||||
} %>
|
} %>
|
||||||
<div class="mt-2 space-y-1">
|
<div class="mt-2 space-y-1">
|
||||||
<p class="text-xs text-gray-600">
|
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||||||
Example for <%= app.name %>: Add claims that this app specifically needs to read.
|
Example for <%= app.name %>: Add claims that this app specifically needs to read.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-amber-600">
|
<p class="text-xs text-amber-600">
|
||||||
<strong>Note:</strong> Do not use reserved claim names (<code class="bg-amber-50 px-1 rounded">groups</code>, <code class="bg-amber-50 px-1 rounded">email</code>, <code class="bg-amber-50 px-1 rounded">name</code>, etc.). Use app-specific names like <code class="bg-amber-50 px-1 rounded">kavita_groups</code> instead.
|
<strong>Note:</strong> Do not use reserved claim names (<code class="bg-amber-50 dark:bg-amber-900/30 px-1 rounded">groups</code>, <code class="bg-amber-50 dark:bg-amber-900/30 px-1 rounded">email</code>, <code class="bg-amber-50 dark:bg-amber-900/30 px-1 rounded">name</code>, etc.). Use app-specific names like <code class="bg-amber-50 dark:bg-amber-900/30 px-1 rounded">kavita_groups</code> instead.
|
||||||
</p>
|
</p>
|
||||||
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,27 +66,27 @@
|
|||||||
delete_application_claims_admin_user_path(user, application_id: app.id),
|
delete_application_claims_admin_user_path(user, application_id: app.id),
|
||||||
method: :delete,
|
method: :delete,
|
||||||
data: { turbo_confirm: "Remove app-specific claims for #{app.name}?" },
|
data: { turbo_confirm: "Remove app-specific claims for #{app.name}?" },
|
||||||
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" %>
|
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" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Preview merged claims -->
|
<!-- Preview merged claims -->
|
||||||
<div class="mt-4 border-t pt-4">
|
<div class="mt-4 border-t dark:border-gray-700 pt-4">
|
||||||
<h4 class="text-sm font-medium text-gray-700 mb-2">Preview: Final ID Token Claims for <%= app.name %></h4>
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Preview: Final ID Token Claims for <%= app.name %></h4>
|
||||||
<div class="bg-gray-50 rounded-lg p-3">
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-3">
|
||||||
<pre class="text-xs font-mono text-gray-800 overflow-x-auto"><%= JSON.pretty_generate(preview_user_claims(user, app)) %></pre>
|
<pre class="text-xs font-mono text-gray-800 dark:text-gray-200 overflow-x-auto"><%= JSON.pretty_generate(preview_user_claims(user, app)) %></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="mt-2">
|
<details class="mt-2">
|
||||||
<summary class="cursor-pointer text-xs text-gray-600 hover:text-gray-900">Show claim sources</summary>
|
<summary class="cursor-pointer text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">Show claim sources</summary>
|
||||||
<div class="mt-2 space-y-1">
|
<div class="mt-2 space-y-1">
|
||||||
<% claim_sources(user, app).each do |source| %>
|
<% claim_sources(user, app).each do |source| %>
|
||||||
<div class="flex gap-2 items-start text-xs">
|
<div class="flex gap-2 items-start text-xs">
|
||||||
<span class="px-2 py-1 rounded <%= source[:type] == :group ? 'bg-blue-100 text-blue-700' : (source[:type] == :user ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700') %>">
|
<span class="px-2 py-1 rounded <%= source[:type] == :group ? 'bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300' : (source[:type] == :user ? 'bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300' : 'bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-300') %>">
|
||||||
<%= source[:name] %>
|
<%= source[:name] %>
|
||||||
</span>
|
</span>
|
||||||
<code class="text-gray-700"><%= source[:claims].to_json %></code>
|
<code class="text-gray-700 dark:text-gray-300"><%= source[:claims].to_json %></code>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,32 +101,32 @@
|
|||||||
|
|
||||||
<!-- ForwardAuth Apps: Headers Preview -->
|
<!-- ForwardAuth Apps: Headers Preview -->
|
||||||
<% if forward_auth_apps.any? %>
|
<% if forward_auth_apps.any? %>
|
||||||
<div class="mt-12 border-t pt-8">
|
<div class="mt-12 border-t dark:border-gray-700 pt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">ForwardAuth Headers Preview</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">ForwardAuth Headers Preview</h2>
|
||||||
<p class="text-sm text-gray-600 mb-6">
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
||||||
ForwardAuth applications receive HTTP headers (not OIDC tokens). Headers are based on user's email, name, groups, and admin status.
|
ForwardAuth applications receive HTTP headers (not OIDC tokens). Headers are based on user's email, name, groups, and admin status.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<% forward_auth_apps.each do |app| %>
|
<% forward_auth_apps.each do |app| %>
|
||||||
<details class="border rounded-lg">
|
<details class="border dark:border-gray-700 rounded-lg">
|
||||||
<summary class="cursor-pointer bg-gray-50 px-4 py-3 hover:bg-gray-100 rounded-t-lg flex items-center justify-between">
|
<summary class="cursor-pointer bg-gray-50 dark:bg-gray-800 px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-t-lg flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="font-medium text-gray-900"><%= app.name %></span>
|
<span class="font-medium text-gray-900 dark:text-gray-100"><%= app.name %></span>
|
||||||
<span class="text-xs px-2 py-1 rounded-full bg-green-100 text-green-700">
|
<span class="text-xs px-2 py-1 rounded-full bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300">
|
||||||
FORWARD AUTH
|
FORWARD AUTH
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-gray-500">
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
<%= app.domain_pattern %>
|
<%= app.domain_pattern %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<svg class="h-5 w-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-5 w-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<div class="p-4 space-y-4">
|
<div class="p-4 space-y-4">
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
<div class="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-700 rounded-lg p-3">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<svg class="h-5 w-5 text-blue-400 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="h-5 w-5 text-blue-400 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||||
@@ -135,33 +135,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-sm font-medium text-gray-700 mb-2">Headers Sent to <%= app.name %></h4>
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Headers Sent to <%= app.name %></h4>
|
||||||
<div class="bg-gray-50 rounded-lg p-3 border">
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-3 border dark:border-gray-700">
|
||||||
<% headers = app.headers_for_user(user) %>
|
<% headers = app.headers_for_user(user) %>
|
||||||
<% if headers.any? %>
|
<% if headers.any? %>
|
||||||
<dl class="space-y-2 text-xs font-mono">
|
<dl class="space-y-2 text-xs font-mono">
|
||||||
<% headers.each do |header_name, value| %>
|
<% headers.each do |header_name, value| %>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<dt class="text-blue-600 font-semibold w-48"><%= header_name %>:</dt>
|
<dt class="text-blue-600 dark:text-blue-400 font-semibold w-48"><%= header_name %>:</dt>
|
||||||
<dd class="text-gray-800 flex-1"><%= value %></dd>
|
<dd class="text-gray-800 dark:text-gray-200 flex-1"><%= value %></dd>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</dl>
|
</dl>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="text-xs text-gray-500 italic">All headers disabled for this application.</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 italic">All headers disabled for this application.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-xs text-gray-500">
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
These headers are configured in the application settings and sent by your reverse proxy (Caddy/Traefik) to the upstream application.
|
These headers are configured in the application settings and sent by your reverse proxy (Caddy/Traefik) to the upstream application.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if user.groups.any? %>
|
<% if user.groups.any? %>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="text-sm font-medium text-gray-700 mb-2">User's Groups</h4>
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">User's Groups</h4>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<% user.groups.each do |group| %>
|
<% user.groups.each do |group| %>
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200">
|
||||||
<%= group.name %>
|
<%= group.name %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -176,10 +176,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if oidc_apps.empty? && forward_auth_apps.empty? %>
|
<% if oidc_apps.empty? && forward_auth_apps.empty? %>
|
||||||
<div class="mt-12 border-t pt-8">
|
<div class="mt-12 border-t dark:border-gray-700 pt-8">
|
||||||
<div class="text-center py-12 bg-gray-50 rounded-lg">
|
<div class="text-center py-12 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<p class="text-gray-500">No active applications found.</p>
|
<p class="text-gray-500 dark:text-gray-400">No active applications found.</p>
|
||||||
<p class="text-sm text-gray-400 mt-1">Create applications in the Admin panel first.</p>
|
<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Create applications in the Admin panel first.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -2,49 +2,49 @@
|
|||||||
<%= render "shared/form_errors", form: form %>
|
<%= render "shared/form_errors", form: form %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :email_address, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :email_address, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.email_field :email_address, required: true, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "user@example.com" %>
|
<%= form.email_field :email_address, required: true, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "user@example.com" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :username, "Username (Optional)", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :username, "Username (Optional)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_field :username, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "jsmith" %>
|
<%= form.text_field :username, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "jsmith" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Optional: Short username/handle for login. Can only contain letters, numbers, underscores, and hyphens.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Optional: Short username/handle for login. Can only contain letters, numbers, underscores, and hyphens.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :name, "Display Name (Optional)", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :name, "Display Name (Optional)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "John Smith" %>
|
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "John Smith" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Optional: Full name shown in applications. Defaults to email address if not set.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Optional: Full name shown in applications. Defaults to email address if not set.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :password, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :password, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: user.persisted? ? "Leave blank to keep current password" : "Enter password" %>
|
<%= form.password_field :password, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: user.persisted? ? "Leave blank to keep current password" : "Enter password" %>
|
||||||
<% if user.persisted? %>
|
<% if user.persisted? %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Leave blank to keep the current password</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Leave blank to keep the current password</p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Leave blank to generate a random password</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Leave blank to generate a random password</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :status, class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :status, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.select :status, User.statuses.keys.map { |s| [s.titleize, s] }, {}, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
<%= form.select :status, User.statuses.keys.map { |s| [s.titleize, s] }, {}, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<%= form.check_box :admin, class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500", disabled: (user == Current.session.user) %>
|
<%= form.check_box :admin, class: "h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500", disabled: (user == Current.session.user) %>
|
||||||
<%= form.label :admin, "Administrator", class: "ml-2 block text-sm text-gray-900" %>
|
<%= form.label :admin, "Administrator", class: "ml-2 block text-sm text-gray-900 dark:text-gray-100" %>
|
||||||
<% if user == Current.session.user %>
|
<% if user == Current.session.user %>
|
||||||
<span class="ml-2 text-xs text-gray-500">(Cannot change your own admin status)</span>
|
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">(Cannot change your own admin status)</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<%= form.check_box :totp_required, class: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" %>
|
<%= form.check_box :totp_required, class: "h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" %>
|
||||||
<%= form.label :totp_required, "Require Two-Factor Authentication", class: "ml-2 block text-sm text-gray-900" %>
|
<%= form.label :totp_required, "Require Two-Factor Authentication", class: "ml-2 block text-sm text-gray-900 dark:text-gray-100" %>
|
||||||
<% if user.totp_required? && !user.totp_enabled? %>
|
<% if user.totp_required? && !user.totp_enabled? %>
|
||||||
<span class="ml-2 text-xs text-amber-600">(User has not set up 2FA yet)</span>
|
<span class="ml-2 text-xs text-amber-600">(User has not set up 2FA yet)</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -57,24 +57,24 @@
|
|||||||
Warning: This user will be prompted to set up 2FA on their next login.
|
Warning: This user will be prompted to set up 2FA on their next login.
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p class="mt-1 text-sm text-gray-500">When enabled, this user must use two-factor authentication to sign in.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">When enabled, this user must use two-factor authentication to sign in.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-controller="json-validator" data-json-validator-valid-class="border-green-500 focus:border-green-500 focus:ring-green-500" data-json-validator-invalid-class="border-red-500 focus:border-red-500 focus:ring-red-500" data-json-validator-valid-status-class="text-green-600" data-json-validator-invalid-status-class="text-red-600">
|
<div data-controller="json-validator" data-json-validator-valid-class="border-green-500 focus:border-green-500 focus:ring-green-500" data-json-validator-invalid-class="border-red-500 focus:border-red-500 focus:ring-red-500" data-json-validator-valid-status-class="text-green-600" data-json-validator-invalid-status-class="text-red-600">
|
||||||
<%= form.label :custom_claims, "Custom Claims (JSON)", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :custom_claims, "Custom Claims (JSON)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.text_area :custom_claims, value: (user.custom_claims.present? ? JSON.pretty_generate(user.custom_claims) : ""), rows: 8,
|
<%= form.text_area :custom_claims, value: (user.custom_claims.present? ? JSON.pretty_generate(user.custom_claims) : ""), rows: 8,
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono",
|
||||||
placeholder: '{"department": "engineering", "level": "senior"}',
|
placeholder: '{"department": "engineering", "level": "senior"}',
|
||||||
data: {
|
data: {
|
||||||
action: "input->json-validator#validate blur->json-validator#format",
|
action: "input->json-validator#validate blur->json-validator#format",
|
||||||
json_validator_target: "textarea"
|
json_validator_target: "textarea"
|
||||||
} %>
|
} %>
|
||||||
<div class="mt-2 text-sm text-gray-600 space-y-1">
|
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<p>Optional: User-specific custom claims to add to OIDC tokens. These override group-level claims.</p>
|
<p>Optional: User-specific custom claims to add to OIDC tokens. These override group-level claims.</p>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button type="button" data-action="json-validator#format" class="text-xs bg-gray-100 hover:bg-gray-200 px-2 py-1 rounded">Format JSON</button>
|
<button type="button" data-action="json-validator#format" class="text-xs bg-gray-100 dark:bg-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 px-2 py-1 rounded">Format JSON</button>
|
||||||
<button type="button" data-action="json-validator#insertSample" data-json-sample='{"department": "engineering", "level": "senior", "location": "remote"}' class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded">Insert Example</button>
|
<button type="button" data-action="json-validator#insertSample" data-json-sample='{"department": "engineering", "level": "senior", "location": "remote"}' class="text-xs bg-blue-100 dark:bg-blue-900/50 hover:bg-blue-200 dark:hover:bg-blue-900 text-blue-700 dark:text-blue-300 px-2 py-1 rounded">Insert Example</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
<div data-json-validator-target="status" class="text-xs font-medium"></div>
|
||||||
@@ -83,6 +83,6 @@
|
|||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<%= form.submit user.persisted? ? "Update User" : "Create User", class: "rounded-md bg-blue-600 px-3 py-2 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" %>
|
<%= form.submit user.persisted? ? "Update User" : "Create User", class: "rounded-md bg-blue-600 px-3 py-2 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" %>
|
||||||
<%= link_to "Cancel", admin_users_path, 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 "Cancel", admin_users_path, 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" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="max-w-4xl">
|
<div class="max-w-4xl">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Edit User</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">Edit User</h1>
|
||||||
<p class="text-sm text-gray-600 mb-6">Editing: <%= @user.email_address %></p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">Editing: <%= @user.email_address %></p>
|
||||||
|
|
||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<%= render "form", user: @user %>
|
<%= render "form", user: @user %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="sm:flex sm:items-center">
|
<div class="sm:flex sm:items-center">
|
||||||
<div class="sm:flex-auto">
|
<div class="sm:flex-auto">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900">Users</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100">Users</h1>
|
||||||
<p class="mt-2 text-sm text-gray-700">A list of all users in the system.</p>
|
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300">A list of all users in the system.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||||
<%= link_to "New User", new_admin_user_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" %>
|
<%= link_to "New User", new_admin_user_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" %>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% unless smtp_configured? %>
|
<% unless smtp_configured? %>
|
||||||
<div class="mt-6 rounded-md bg-yellow-50 p-4">
|
<div class="mt-6 rounded-md bg-yellow-50 dark:bg-yellow-900/30 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<h3 class="text-sm font-medium text-yellow-800">
|
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
|
||||||
Email delivery not configured
|
Email delivery not configured
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 text-sm text-yellow-700">
|
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
|
||||||
<p>
|
<p>
|
||||||
<% if Rails.env.development? %>
|
<% if Rails.env.development? %>
|
||||||
Emails are being delivered using <span class="font-mono"><%= email_delivery_method %></span> and will open in your browser.
|
Emails are being delivered using <span class="font-mono"><%= email_delivery_method %></span> and will open in your browser.
|
||||||
@@ -44,63 +44,63 @@
|
|||||||
<div class="mt-8 flow-root">
|
<div class="mt-8 flow-root">
|
||||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<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">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0">Email</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">Email</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 dark:text-gray-100">Status</th>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th>
|
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">Role</th>
|
||||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">2FA</th>
|
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-gray-100">2FA</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="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">
|
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-0">
|
||||||
<span class="sr-only">Actions</span>
|
<span class="sr-only">Actions</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @users.each do |user| %>
|
<% @users.each do |user| %>
|
||||||
<tr>
|
<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">
|
||||||
<%= user.email_address %>
|
<%= user.email_address %>
|
||||||
</td>
|
</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 user.status.present? %>
|
<% if user.status.present? %>
|
||||||
<% case user.status.to_sym %>
|
<% case user.status.to_sym %>
|
||||||
<% when :active %>
|
<% when :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>
|
||||||
<% when :disabled %>
|
<% when :disabled %>
|
||||||
<span class="inline-flex items-center rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-700">Disabled</span>
|
<span class="inline-flex items-center rounded-full bg-red-100 dark:bg-red-900/50 px-2 py-1 text-xs font-medium text-red-700 dark:text-red-300">Disabled</span>
|
||||||
<% when :pending_invitation %>
|
<% when :pending_invitation %>
|
||||||
<span class="inline-flex items-center rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-700">Pending</span>
|
<span class="inline-flex items-center rounded-full bg-yellow-100 dark:bg-yellow-900/50 px-2 py-1 text-xs font-medium text-yellow-700 dark:text-yellow-300">Pending</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-400">-</span>
|
<span class="text-gray-400 dark:text-gray-500">-</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</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 user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700">Admin</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">Admin</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-gray-500">User</span>
|
<span class="text-gray-500 dark:text-gray-400">User</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</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">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<% if user.totp_enabled? %>
|
<% if user.totp_enabled? %>
|
||||||
<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="2FA Enabled">
|
<svg class="h-5 w-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="2FA Enabled">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<% else %>
|
<% else %>
|
||||||
<svg class="h-5 w-5 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="2FA Not Enabled">
|
<svg class="h-5 w-5 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" title="2FA Not Enabled">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if user.totp_required? %>
|
<% if user.totp_required? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700" title="2FA Required by Admin">Required</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" title="2FA Required by Admin">Required</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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">
|
||||||
<%= user.groups.count %>
|
<%= user.groups.count %>
|
||||||
</td>
|
</td>
|
||||||
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
|
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-0">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="max-w-2xl">
|
<div class="max-w-2xl">
|
||||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">New User</h1>
|
<h1 class="text-2xl font-semibold text-gray-900 dark:text-gray-100 mb-6">New User</h1>
|
||||||
<%= render "form", user: @user %>
|
<%= render "form", user: @user %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div class="mb-8 flex items-center justify-between">
|
<div class="mb-8 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900">API Keys</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">API Keys</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Bearer tokens for server-to-server access to forward auth applications.
|
Bearer tokens for server-to-server access to forward auth applications.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<%= link_to "New API Key", new_api_key_path,
|
<%= link_to "New API Key", new_api_key_path,
|
||||||
class: "inline-flex items-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex items-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @api_keys.any? %>
|
<% if @api_keys.any? %>
|
||||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
<div class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg">
|
||||||
<table class="min-w-full divide-y divide-gray-200">
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Application</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Application</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Created</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Used</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Last Used</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expires</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Expires</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
||||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
<% @api_keys.each do |key| %>
|
<% @api_keys.each do |key| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"><%= key.name %></td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100"><%= key.name %></td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= key.application.name %></td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= key.application.name %></td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= key.created_at.strftime("%b %d, %Y") %></td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= key.created_at.strftime("%b %d, %Y") %></td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= key.last_used_at ? time_ago_in_words(key.last_used_at) + " ago" : "Never" %></td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= key.last_used_at ? time_ago_in_words(key.last_used_at) + " ago" : "Never" %></td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"><%= key.expires_at ? key.expires_at.strftime("%b %d, %Y") : "Never" %></td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400"><%= key.expires_at ? key.expires_at.strftime("%b %d, %Y") : "Never" %></td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
<% if key.revoked? %>
|
<% if key.revoked? %>
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Revoked</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200">Revoked</span>
|
||||||
<% elsif key.expired? %>
|
<% elsif key.expired? %>
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">Expired</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 dark:bg-yellow-900/50 text-yellow-800 dark:text-yellow-200">Expired</span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Active</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200">Active</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
@@ -54,12 +54,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-gray-50 rounded-lg border border-gray-200 p-8 text-center">
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-8 text-center">
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="mt-4 text-lg font-medium text-gray-900">No API keys</h3>
|
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">No API keys</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500">
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
Create an API key to authenticate server-to-server requests.
|
Create an API key to authenticate server-to-server requests.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<div class="max-w-lg mx-auto">
|
<div class="max-w-lg mx-auto">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">New API Key</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">New API Key</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Create a bearer token for server-to-server access to a forward auth application.
|
Create a bearer token for server-to-server access to a forward auth application.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<%= form_with(model: @api_key, class: "space-y-6") do |f| %>
|
<%= form_with(model: @api_key, class: "space-y-6") do |f| %>
|
||||||
<% if @api_key.errors.any? %>
|
<% if @api_key.errors.any? %>
|
||||||
<div class="rounded-md bg-red-50 p-4">
|
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||||
<div class="text-sm text-red-700">
|
<div class="text-sm text-red-700 dark:text-red-300">
|
||||||
<ul class="list-disc pl-5 space-y-1">
|
<ul class="list-disc pl-5 space-y-1">
|
||||||
<% @api_key.errors.full_messages.each do |msg| %>
|
<% @api_key.errors.full_messages.each do |msg| %>
|
||||||
<li><%= msg %></li>
|
<li><%= msg %></li>
|
||||||
@@ -22,32 +22,32 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :name, class: "block text-sm font-medium text-gray-700" %>
|
<%= f.label :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= f.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
|
<%= f.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm",
|
||||||
placeholder: "e.g., Video Player WebDAV" %>
|
placeholder: "e.g., Video Player WebDAV" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :application_id, "Application", class: "block text-sm font-medium text-gray-700" %>
|
<%= f.label :application_id, "Application", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<% if @applications.any? %>
|
<% if @applications.any? %>
|
||||||
<%= f.collection_select :application_id, @applications, :id, :name,
|
<%= f.collection_select :application_id, @applications, :id, :name,
|
||||||
{ prompt: "Select an application" },
|
{ prompt: "Select an application" },
|
||||||
{ class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
{ class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" } %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="mt-1 text-sm text-gray-500">No forward auth applications available.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">No forward auth applications available.</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= f.label :expires_at, "Expiration (optional)", class: "block text-sm font-medium text-gray-700" %>
|
<%= f.label :expires_at, "Expiration (optional)", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= f.datetime_local_field :expires_at, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
<%= f.datetime_local_field :expires_at, class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
<p class="mt-1 text-xs text-gray-500">Leave blank for no expiration.</p>
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Leave blank for no expiration.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-3">
|
<div class="flex items-center justify-end gap-3">
|
||||||
<%= link_to "Cancel", api_keys_path, class: "text-sm font-medium text-gray-700 hover:text-gray-500" %>
|
<%= link_to "Cancel", api_keys_path, class: "text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-400" %>
|
||||||
<%= f.submit "Create API Key",
|
<%= f.submit "Create API Key",
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<div class="max-w-2xl mx-auto" data-controller="clipboard">
|
<div class="max-w-2xl mx-auto" data-controller="clipboard">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">API Key Created</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">API Key Created</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Copy your API key now. You won't be able to see it again.
|
Copy your API key now. You won't be able to see it again.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<div class="rounded-md bg-yellow-50 p-4 mb-6">
|
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/30 p-4 mb-6">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-sm text-yellow-800">
|
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<p class="font-medium">Save this key now!</p>
|
<p class="font-medium">Save this key now!</p>
|
||||||
<p class="mt-1">This is the only time you'll see the full API key. Store it securely.</p>
|
<p class="mt-1">This is the only time you'll see the full API key. Store it securely.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -21,14 +21,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">API Key</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">API Key</label>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input type="text" readonly value="<%= @plaintext_token %>"
|
<input type="text" readonly value="<%= @plaintext_token %>"
|
||||||
data-clipboard-target="source"
|
data-clipboard-target="source"
|
||||||
class="flex-1 rounded-md border-gray-300 bg-gray-50 font-mono text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500" />
|
class="flex-1 rounded-md border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 dark:text-gray-100 font-mono text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500" />
|
||||||
<button data-action="click->clipboard#copy"
|
<button data-action="click->clipboard#copy"
|
||||||
data-clipboard-target="button"
|
data-clipboard-target="button"
|
||||||
class="inline-flex items-center rounded-md border border-gray-300 bg-white py-2 px-3 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 py-2 px-3 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -37,22 +37,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 space-y-2 text-sm text-gray-600">
|
<div class="mt-6 space-y-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<p><strong>Name:</strong> <%= @api_key.name %></p>
|
<p><strong>Name:</strong> <%= @api_key.name %></p>
|
||||||
<p><strong>Application:</strong> <%= @api_key.application.name %></p>
|
<p><strong>Application:</strong> <%= @api_key.application.name %></p>
|
||||||
<p><strong>Expires:</strong> <%= @api_key.expires_at ? @api_key.expires_at.strftime("%b %d, %Y %H:%M") : "Never" %></p>
|
<p><strong>Expires:</strong> <%= @api_key.expires_at ? @api_key.expires_at.strftime("%b %d, %Y %H:%M") : "Never" %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 rounded-md bg-gray-50 p-4">
|
<div class="mt-6 rounded-md bg-gray-50 dark:bg-gray-700 p-4">
|
||||||
<p class="text-sm font-medium text-gray-700 mb-2">Usage example:</p>
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Usage example:</p>
|
||||||
<pre class="text-xs text-gray-600 overflow-x-auto">curl -H "Authorization: Bearer <%= @plaintext_token %>" \
|
<pre class="text-xs text-gray-600 dark:text-gray-200 overflow-x-auto">curl -H "Authorization: Bearer <%= @plaintext_token %>" \
|
||||||
-H "X-Forwarded-Host: your-app.example.com" \
|
-H "X-Forwarded-Host: your-app.example.com" \
|
||||||
<%= request.base_url %>/api/verify</pre>
|
<%= request.base_url %>/api/verify</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<%= link_to "Done", api_keys_path,
|
<%= link_to "Done", api_keys_path,
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
Welcome, <%= @user.email_address %>
|
Welcome, <%= @user.email_address %>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-gray-600">
|
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
||||||
<% if @user.admin? %>
|
<% if @user.admin? %>
|
||||||
Administrator
|
Administrator
|
||||||
<% else %>
|
<% else %>
|
||||||
@@ -13,34 +13,34 @@
|
|||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<!-- Active Sessions Card -->
|
<!-- Active Sessions Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Active Sessions
|
Active Sessions
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-semibold text-gray-900">
|
<dd class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
<%= @user.sessions.active.count %>
|
<%= @user.sessions.active.count %>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "View all sessions", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "View all sessions", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if @user.totp_enabled? %>
|
<% if @user.totp_enabled? %>
|
||||||
<!-- 2FA Status Card -->
|
<!-- 2FA Status Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Two-Factor Authentication
|
Two-Factor Authentication
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-semibold text-green-600">
|
<dd class="text-lg font-semibold text-green-600">
|
||||||
@@ -60,13 +60,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Manage 2FA", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Manage 2FA", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<!-- 2FA Disabled Card -->
|
<!-- 2FA Disabled Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg border-2 border-yellow-200">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg border-2 border-yellow-200">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
Two-Factor Authentication
|
Two-Factor Authentication
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-semibold text-yellow-600">
|
<dd class="text-lg font-semibold text-yellow-600">
|
||||||
@@ -86,34 +86,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Enable 2FA", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Enable 2FA", profile_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- API Keys Card -->
|
<!-- API Keys Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow rounded-lg">
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="h-6 w-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">
|
||||||
API Keys
|
API Keys
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-lg font-semibold text-gray-900">
|
<dd class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||||
<%= @user.api_keys.active.count %>
|
<%= @user.api_keys.active.count %>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-5 py-3">
|
<div class="bg-gray-50 dark:bg-gray-700 px-5 py-3">
|
||||||
<%= link_to "Manage API Keys", api_keys_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
<%= link_to "Manage API Keys", api_keys_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,39 +121,39 @@
|
|||||||
|
|
||||||
<!-- Your Applications Section -->
|
<!-- Your Applications Section -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Your Applications</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Your Applications</h2>
|
||||||
|
|
||||||
<% if @applications.any? %>
|
<% if @applications.any? %>
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<% @applications.each do |app| %>
|
<% @applications.each do |app| %>
|
||||||
<div class="bg-white rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition">
|
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md transition">
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
<div class="flex items-start gap-3 mb-4">
|
<div class="flex items-start gap-3 mb-4">
|
||||||
<% if app.icon.attached? %>
|
<% if app.icon.attached? %>
|
||||||
<%= image_tag app.icon, class: "h-12 w-12 rounded-lg object-cover border border-gray-200 shrink-0", alt: "#{app.name} icon" %>
|
<%= image_tag app.icon, class: "h-12 w-12 rounded-lg object-cover border border-gray-200 dark:border-gray-700 shrink-0", alt: "#{app.name} icon" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="h-12 w-12 rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center shrink-0">
|
<div class="h-12 w-12 rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-700 flex items-center justify-center shrink-0">
|
||||||
<svg class="h-6 w-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 truncate">
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 truncate">
|
||||||
<%= app.name %>
|
<%= app.name %>
|
||||||
</h3>
|
</h3>
|
||||||
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium shrink-0
|
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium shrink-0
|
||||||
<% if app.oidc? %>
|
<% if app.oidc? %>
|
||||||
bg-blue-100 text-blue-800
|
bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-200
|
||||||
<% else %>
|
<% else %>
|
||||||
bg-green-100 text-green-800
|
bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200
|
||||||
<% end %>">
|
<% end %>">
|
||||||
<%= app.app_type.humanize %>
|
<%= app.app_type.humanize %>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<% if app.description.present? %>
|
<% if app.description.present? %>
|
||||||
<p class="text-sm text-gray-600 mt-1 line-clamp-2">
|
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
|
||||||
<%= app.description %>
|
<%= app.description %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -165,30 +165,40 @@
|
|||||||
<%= link_to "Open Application", app.landing_url,
|
<%= link_to "Open Application", app.landing_url,
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
rel: "noopener noreferrer",
|
rel: "noopener noreferrer",
|
||||||
class: "w-full flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition" %>
|
class: "w-full flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:ring-blue-500 transition" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="text-sm text-gray-500 italic">
|
<div class="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||||
No landing URL configured
|
No landing URL configured
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if app.user_has_active_session?(@user) %>
|
<% if app.user_has_active_session?(@user) %>
|
||||||
<%= button_to "Require Re-Auth", logout_from_app_active_sessions_path(application_id: app.id), method: :delete,
|
<%= button_to "Require Re-Auth", logout_from_app_active_sessions_path(application_id: app.id), method: :delete,
|
||||||
class: "w-full flex justify-center items-center px-4 py-2 border border-orange-300 text-sm font-medium rounded-md text-orange-700 bg-white hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 transition",
|
class: "w-full flex justify-center items-center px-4 py-2 border border-orange-300 text-sm font-medium rounded-md text-orange-700 bg-white dark:bg-gray-700 dark:ring-gray-600 dark:text-gray-200 hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:ring-orange-500 transition",
|
||||||
form: { data: { turbo_confirm: "This will revoke #{app.name}'s access tokens. The next time #{app.name} needs to authenticate, you'll sign in again (no re-authorization needed). Continue?" } } %>
|
form: { data: { turbo_confirm: "This will revoke #{app.name}'s access tokens. The next time #{app.name} needs to authenticate, you'll sign in again (no re-authorization needed). Continue?" } } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if @user.admin? %>
|
||||||
|
<div class="flex gap-2 mt-1">
|
||||||
|
<%= link_to "View", admin_application_path(app),
|
||||||
|
class: "text-sm font-medium text-gray-600 dark:text-gray-400 hover:text-blue-600 transition" %>
|
||||||
|
<span class="text-gray-300 dark:text-gray-600">|</span>
|
||||||
|
<%= link_to "Edit", edit_admin_application_path(app),
|
||||||
|
class: "text-sm font-medium text-gray-600 dark:text-gray-400 hover:text-blue-600 transition" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="bg-gray-50 rounded-lg border border-gray-200 p-8 text-center">
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-8 text-center">
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="mt-4 text-lg font-medium text-gray-900">No applications available</h3>
|
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">No applications available</h3>
|
||||||
<p class="mt-2 text-sm text-gray-500">
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
You don't have access to any applications yet. Contact your administrator if you think this is an error.
|
You don't have access to any applications yet. Contact your administrator if you think this is an error.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,21 +207,21 @@
|
|||||||
|
|
||||||
<% if @user.admin? %>
|
<% if @user.admin? %>
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-semibold text-gray-900 mb-4">Admin Quick Actions</h2>
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-4">Admin Quick Actions</h2>
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<%= link_to admin_users_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to admin_users_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Manage Users</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Manage Users</h3>
|
||||||
<p class="text-sm text-gray-600">View, edit, and invite users</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">View, edit, and invite users</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= link_to admin_applications_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to admin_applications_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Manage Applications</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Manage Applications</h3>
|
||||||
<p class="text-sm text-gray-600">Register and configure applications</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">Register and configure applications</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= link_to admin_groups_path, class: "block p-6 bg-white rounded-lg border border-gray-200 shadow-sm hover:bg-gray-50 hover:shadow-md transition" do %>
|
<%= link_to admin_groups_path, class: "block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 hover:shadow-md transition" do %>
|
||||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">Manage Groups</h3>
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">Manage Groups</h3>
|
||||||
<p class="text-sm text-gray-600">Create and organize user groups</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">Create and organize user groups</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<h1 class="font-bold text-4xl">Welcome to Clinch!</h1>
|
<h1 class="font-bold text-4xl">Welcome to Clinch!</h1>
|
||||||
<p class="mt-2 text-gray-600">You've been invited to join Clinch. Please create your password to complete your account setup.</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">You've been invited to join Clinch. Please create your password to complete your account setup.</p>
|
||||||
|
|
||||||
<%= form_with url: invitation_path(params[:token]), method: :put, class: "contents" do |form| %>
|
<%= form_with url: invitation_path(params[:token]), method: :put, class: "contents" do |form| %>
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Confirm your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Confirm your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<%= form.submit "Create Account", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
|
<%= form.submit "Create Account", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,15 @@
|
|||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<%= csp_meta_tag %>
|
<%= csp_meta_tag %>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var theme = localStorage.getItem('theme');
|
||||||
|
if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<%= yield :head %>
|
<%= yield :head %>
|
||||||
|
|
||||||
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
||||||
@@ -23,15 +32,15 @@
|
|||||||
<%= javascript_importmap_tags %>
|
<%= javascript_importmap_tags %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="dark:bg-gray-900 dark:text-gray-100">
|
||||||
<% if authenticated? %>
|
<% if authenticated? %>
|
||||||
<div data-controller="mobile-sidebar">
|
<div data-controller="mobile-sidebar">
|
||||||
<%= render "shared/sidebar" %>
|
<%= render "shared/sidebar" %>
|
||||||
<div class="lg:pl-64">
|
<div class="lg:pl-64">
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:hidden">
|
<div class="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:hidden">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="-m-2.5 p-2.5 text-gray-700"
|
class="-m-2.5 p-2.5 text-gray-700 dark:text-gray-300"
|
||||||
id="mobile-menu-button"
|
id="mobile-menu-button"
|
||||||
data-action="click->mobile-sidebar#openSidebar">
|
data-action="click->mobile-sidebar#openSidebar">
|
||||||
<span class="sr-only">Open sidebar</span>
|
<span class="sr-only">Open sidebar</span>
|
||||||
@@ -51,6 +60,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<!-- Public layout (signup/signin) -->
|
<!-- Public layout (signup/signin) -->
|
||||||
|
<div class="absolute top-4 right-4" data-controller="dark-mode">
|
||||||
|
<button type="button" data-action="click->dark-mode#toggle" class="rounded-lg p-2 text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800">
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="light" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
||||||
|
</svg>
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="dark" class="hidden h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<main class="container mx-auto mt-28 px-5">
|
<main class="container mx-auto mt-28 px-5">
|
||||||
<%= render "shared/flash" %>
|
<%= render "shared/flash" %>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
<div class="mx-auto max-w-md">
|
<div class="mx-auto max-w-md">
|
||||||
<div class="bg-white py-8 px-6 shadow rounded-lg sm:px-10">
|
<div class="bg-white dark:bg-gray-800 py-8 px-6 shadow rounded-lg sm:px-10">
|
||||||
<div class="mb-8 text-center">
|
<div class="mb-8 text-center">
|
||||||
<% if @application.icon.attached? %>
|
<% if @application.icon.attached? %>
|
||||||
<%= image_tag @application.icon, class: "mx-auto h-20 w-20 rounded-xl object-cover border-2 border-gray-200 shadow-sm mb-4", alt: "#{@application.name} icon" %>
|
<%= image_tag @application.icon, class: "mx-auto h-20 w-20 rounded-xl object-cover border-2 border-gray-200 dark:border-gray-700 shadow-sm mb-4", alt: "#{@application.name} icon" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mx-auto h-20 w-20 rounded-xl bg-gray-100 border-2 border-gray-200 flex items-center justify-center mb-4">
|
<div class="mx-auto h-20 w-20 rounded-xl bg-gray-100 dark:bg-gray-700 border-2 border-gray-200 dark:border-gray-700 flex items-center justify-center mb-4">
|
||||||
<svg class="h-10 w-10 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-10 w-10 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" />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<h2 class="text-2xl font-bold text-gray-900">Authorize Application</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Authorize Application</h2>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<strong><%= @application.name %></strong> is requesting access to your account.
|
<strong><%= @application.name %></strong> is requesting access to your account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h3 class="text-sm font-medium text-gray-900 mb-3">This application will be able to:</h3>
|
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">This application will be able to:</h3>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<% if @scopes.include?("openid") %>
|
<% if @scopes.include?("openid") %>
|
||||||
<li class="flex items-start">
|
<li class="flex items-start">
|
||||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm text-gray-700">Verify your identity</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">Verify your identity</span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @scopes.include?("email") %>
|
<% if @scopes.include?("email") %>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm text-gray-700">Access your email address (<%= Current.session.user.email_address %>)</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">Access your email address (<%= Current.session.user.email_address %>)</span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @scopes.include?("profile") %>
|
<% if @scopes.include?("profile") %>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm text-gray-700">Access your profile information</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">Access your profile information</span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @scopes.include?("groups") %>
|
<% if @scopes.include?("groups") %>
|
||||||
@@ -48,18 +48,18 @@
|
|||||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm text-gray-700">Access your group memberships</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">Access your group memberships</span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-md bg-blue-50 p-4 mb-6">
|
<div class="rounded-md bg-blue-50 dark:bg-blue-900/30 p-4 mb-6">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-blue-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-blue-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-sm text-blue-700">
|
<div class="text-sm text-blue-700 dark:text-blue-300">
|
||||||
<p>You'll be redirected to:</p>
|
<p>You'll be redirected to:</p>
|
||||||
<p class="mt-1 font-mono text-xs break-all"><%= @redirect_uri %></p>
|
<p class="mt-1 font-mono text-xs break-all"><%= @redirect_uri %></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,13 +68,13 @@
|
|||||||
|
|
||||||
<%= form_with url: "/oauth/authorize/consent", method: :post, class: "space-y-3", data: { turbo: false }, local: true do |form| %>
|
<%= form_with url: "/oauth/authorize/consent", method: :post, class: "space-y-3", data: { turbo: false }, local: true do |form| %>
|
||||||
<%= form.submit "Authorize",
|
<%= form.submit "Authorize",
|
||||||
class: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
class: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:ring-blue-500" %>
|
||||||
|
|
||||||
<%= button_tag "Deny",
|
<%= button_tag "Deny",
|
||||||
type: :submit,
|
type: :submit,
|
||||||
name: :deny,
|
name: :deny,
|
||||||
value: "1",
|
value: "1",
|
||||||
class: "w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
class: "w-full flex justify-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 focus:ring-blue-500" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
<%= form_with url: password_path(params[:token]), method: :put, class: "contents" do |form| %>
|
<%= form_with url: password_path(params[:token]), method: :put, class: "contents" do |form| %>
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<%= form_with url: passwords_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
<%= form_with url: passwords_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address], class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address], class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<div class="space-y-8" data-controller="modal">
|
<div class="space-y-8" data-controller="modal">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Account Security</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Account Security</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">Manage your account settings, active sessions, and connected applications.</p>
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Manage your account settings, active sessions, and connected applications.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Account Information -->
|
<!-- Account 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">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Account Information</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Account Information</h3>
|
||||||
<div class="mt-5 space-y-6">
|
<div class="mt-5 space-y-6">
|
||||||
<%= form_with model: @user, url: profile_path, method: :patch, class: "space-y-6" do |form| %>
|
<%= form_with model: @user, url: profile_path, method: :patch, class: "space-y-6" do |form| %>
|
||||||
<% if @user.errors.any? %>
|
<% if @user.errors.any? %>
|
||||||
<div class="rounded-md bg-red-50 p-4">
|
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4">
|
||||||
<h3 class="text-sm font-medium text-red-800">
|
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||||
<%= pluralize(@user.errors.count, "error") %> prohibited this from being saved:
|
<%= pluralize(@user.errors.count, "error") %> prohibited this from being saved:
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="mt-2 list-disc list-inside text-sm text-red-700">
|
<ul class="mt-2 list-disc list-inside text-sm text-red-700 dark:text-red-300">
|
||||||
<% @user.errors.each do |error| %>
|
<% @user.errors.each do |error| %>
|
||||||
<li><%= error.full_message %></li>
|
<li><%= error.full_message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -24,24 +24,24 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :email_address, "Email Address", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :email_address, "Email Address", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.email_field :email_address,
|
<%= form.email_field :email_address,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "email",
|
autocomplete: "email",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :current_password, "Current Password", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :current_password, "Current Password", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :current_password,
|
<%= form.password_field :current_password,
|
||||||
autocomplete: "current-password",
|
autocomplete: "current-password",
|
||||||
placeholder: "Required to change email",
|
placeholder: "Required to change email",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Enter your current password to confirm this change</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Enter your current password to confirm this change</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.submit "Update Email", class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
<%= form.submit "Update Email", class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,38 +49,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Change Password -->
|
<!-- Change Password -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Change Password</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Change Password</h3>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<%= form_with model: @user, url: profile_path, method: :patch, class: "space-y-6" do |form| %>
|
<%= form_with model: @user, url: profile_path, method: :patch, class: "space-y-6" do |form| %>
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :current_password, "Current Password", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :current_password, "Current Password", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :current_password,
|
<%= form.password_field :current_password,
|
||||||
autocomplete: "current-password",
|
autocomplete: "current-password",
|
||||||
placeholder: "Enter current password",
|
placeholder: "Enter current password",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :password, "New Password", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :password, "New Password", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password,
|
<%= form.password_field :password,
|
||||||
autocomplete: "new-password",
|
autocomplete: "new-password",
|
||||||
placeholder: "Enter new password",
|
placeholder: "Enter new password",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Must be at least 8 characters</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Must be at least 8 characters</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :password_confirmation, "Confirm New Password", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :password_confirmation, "Confirm New Password", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password_confirmation,
|
<%= form.password_field :password_confirmation,
|
||||||
autocomplete: "new-password",
|
autocomplete: "new-password",
|
||||||
placeholder: "Confirm new password",
|
placeholder: "Confirm new password",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.submit "Update Password", class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
<%= form.submit "Update Password", class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,15 +88,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Two-Factor Authentication -->
|
<!-- Two-Factor Authentication -->
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Two-Factor Authentication</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Two-Factor Authentication</h3>
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
||||||
<p>Add an extra layer of security to your account by enabling two-factor authentication.</p>
|
<p>Add an extra layer of security to your account by enabling two-factor authentication.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<% if @user.totp_enabled? %>
|
<% if @user.totp_enabled? %>
|
||||||
<div class="rounded-md bg-green-50 p-4">
|
<div class="rounded-md bg-green-50 dark:bg-green-900/30 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
|
||||||
@@ -104,11 +104,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 flex-1">
|
<div class="ml-3 flex-1">
|
||||||
<p class="text-sm font-medium text-green-800">
|
<p class="text-sm font-medium text-green-800 dark:text-green-200">
|
||||||
Two-factor authentication is enabled
|
Two-factor authentication is enabled
|
||||||
</p>
|
</p>
|
||||||
<% if @user.totp_required? %>
|
<% if @user.totp_required? %>
|
||||||
<p class="mt-1 text-sm text-green-700">
|
<p class="mt-1 text-sm text-green-700 dark:text-green-300">
|
||||||
<svg class="inline h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="inline h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -119,12 +119,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% if @user.totp_required? %>
|
<% if @user.totp_required? %>
|
||||||
<div class="mt-4 rounded-md bg-blue-50 p-4">
|
<div class="mt-4 rounded-md bg-blue-50 dark:bg-blue-900/30 p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-blue-400 mr-2 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-blue-400 mr-2 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-sm text-blue-800">
|
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||||
Your administrator requires two-factor authentication. You cannot disable it.
|
Your administrator requires two-factor authentication. You cannot disable it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->modal#show"
|
data-action="click->modal#show"
|
||||||
data-modal-id="view-backup-codes-modal"
|
data-modal-id="view-backup-codes-modal"
|
||||||
class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
View Backup Codes
|
View Backup Codes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,19 +142,19 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->modal#show"
|
data-action="click->modal#show"
|
||||||
data-modal-id="disable-2fa-modal"
|
data-modal-id="disable-2fa-modal"
|
||||||
class="inline-flex items-center rounded-md border border-red-300 bg-white px-4 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
|
class="inline-flex items-center rounded-md border border-red-300 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
Disable 2FA
|
Disable 2FA
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->modal#show"
|
data-action="click->modal#show"
|
||||||
data-modal-id="view-backup-codes-modal"
|
data-modal-id="view-backup-codes-modal"
|
||||||
class="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
View Backup Codes
|
View Backup Codes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to new_totp_path, class: "inline-flex items-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" do %>
|
<%= link_to new_totp_path, class: "inline-flex items-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" do %>
|
||||||
Enable 2FA
|
Enable 2FA
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -166,17 +166,17 @@
|
|||||||
<div id="disable-2fa-modal"
|
<div id="disable-2fa-modal"
|
||||||
data-action="click->modal#closeOnBackdrop keyup@window->modal#closeOnEscape"
|
data-action="click->modal#closeOnBackdrop keyup@window->modal#closeOnEscape"
|
||||||
class="hidden fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
class="hidden fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg px-4 pt-5 pb-4 shadow-xl max-w-md w-full">
|
<div class="bg-white dark:bg-gray-800 rounded-lg px-4 pt-5 pb-4 shadow-xl max-w-md w-full">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/50 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-1">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-1">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Disable Two-Factor Authentication</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Disable Two-Factor Authentication</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500">Enter your password to disable 2FA. This will make your account less secure.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">Enter your password to disable 2FA. This will make your account less secure.</p>
|
||||||
</div>
|
</div>
|
||||||
<%= form_with url: totp_path, method: :delete, class: "mt-4" do |form| %>
|
<%= form_with url: totp_path, method: :delete, class: "mt-4" do |form| %>
|
||||||
<div>
|
<div>
|
||||||
@@ -184,14 +184,14 @@
|
|||||||
placeholder: "Enter your password",
|
placeholder: "Enter your password",
|
||||||
autocomplete: "current-password",
|
autocomplete: "current-password",
|
||||||
required: true,
|
required: true,
|
||||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm" %>
|
class: "block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-red-500 focus:ring-red-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex gap-3">
|
<div class="mt-4 flex gap-3">
|
||||||
<%= form.submit "Disable 2FA",
|
<%= form.submit "Disable 2FA",
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->modal#hide"
|
data-action="click->modal#hide"
|
||||||
class="inline-flex justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
class="inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,18 +205,18 @@
|
|||||||
<div id="view-backup-codes-modal"
|
<div id="view-backup-codes-modal"
|
||||||
data-action="click->modal#closeOnBackdrop keyup@window->modal#closeOnEscape"
|
data-action="click->modal#closeOnBackdrop keyup@window->modal#closeOnEscape"
|
||||||
class="hidden fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
class="hidden fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg px-4 pt-5 pb-4 shadow-xl max-w-md w-full">
|
<div class="bg-white dark:bg-gray-800 rounded-lg px-4 pt-5 pb-4 shadow-xl max-w-md w-full">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Generate New Backup Codes</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Generate New Backup Codes</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-500">Due to security improvements, you need to generate new backup codes. Your old codes have been invalidated.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400">Due to security improvements, you need to generate new backup codes. Your old codes have been invalidated.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 p-3 bg-yellow-50 rounded-md">
|
<div class="mt-3 p-3 bg-yellow-50 dark:bg-yellow-900/30 rounded-md">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-yellow-400 mr-2 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-yellow-400 mr-2 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-sm text-yellow-800">
|
<p class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<strong>Important:</strong> Save the new codes immediately after generation. You won't be able to see them again without regenerating.
|
<strong>Important:</strong> Save the new codes immediately after generation. You won't be able to see them again without regenerating.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,14 +227,14 @@
|
|||||||
placeholder: "Enter your password",
|
placeholder: "Enter your password",
|
||||||
autocomplete: "current-password",
|
autocomplete: "current-password",
|
||||||
required: true,
|
required: true,
|
||||||
class: "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
class: "block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex gap-3">
|
<div class="mt-4 flex gap-3">
|
||||||
<%= form.submit "Generate New Codes",
|
<%= form.submit "Generate New Codes",
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->modal#hide"
|
data-action="click->modal#hide"
|
||||||
class="inline-flex justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
class="inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -244,10 +244,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Passkeys (WebAuthn) -->
|
<!-- Passkeys (WebAuthn) -->
|
||||||
<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" data-controller="webauthn" data-webauthn-challenge-url-value="/webauthn/challenge" data-webauthn-create-url-value="/webauthn/create">
|
<div class="px-4 py-5 sm:p-6" data-controller="webauthn" data-webauthn-challenge-url-value="/webauthn/challenge" data-webauthn-create-url-value="/webauthn/create">
|
||||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Passkeys</h3>
|
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Passkeys</h3>
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
<div class="mt-2 max-w-xl text-sm text-gray-500 dark:text-gray-400">
|
||||||
<p>Use your fingerprint, face recognition, or security key to sign in without passwords.</p>
|
<p>Use your fingerprint, face recognition, or security key to sign in without passwords.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -255,20 +255,20 @@
|
|||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div id="add-passkey-form" class="space-y-4">
|
<div id="add-passkey-form" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="passkey-nickname" class="block text-sm font-medium text-gray-700">Passkey Name</label>
|
<label for="passkey-nickname" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Passkey Name</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="passkey-nickname"
|
id="passkey-nickname"
|
||||||
data-webauthn-target="nickname"
|
data-webauthn-target="nickname"
|
||||||
placeholder="e.g., MacBook Touch ID, iPhone Face ID"
|
placeholder="e.g., MacBook Touch ID, iPhone Face ID"
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
class="mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm">
|
||||||
<p class="mt-1 text-sm text-gray-500">Give this passkey a memorable name so you can identify it later.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Give this passkey a memorable name so you can identify it later.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->webauthn#register"
|
data-action="click->webauthn#register"
|
||||||
data-webauthn-target="submitButton"
|
data-webauthn-target="submitButton"
|
||||||
class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2">
|
class="inline-flex items-center rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -284,11 +284,11 @@
|
|||||||
|
|
||||||
<!-- Existing Passkeys List -->
|
<!-- Existing Passkeys List -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h4 class="text-md font-medium text-gray-900 mb-4">Your Passkeys</h4>
|
<h4 class="text-md font-medium text-gray-900 dark:text-gray-100 mb-4">Your Passkeys</h4>
|
||||||
<% if @user.webauthn_credentials.exists? %>
|
<% if @user.webauthn_credentials.exists? %>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<% @user.webauthn_credentials.order(created_at: :desc).each do |credential| %>
|
<% @user.webauthn_credentials.order(created_at: :desc).each do |credential| %>
|
||||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<% if credential.platform_authenticator? %>
|
<% if credential.platform_authenticator? %>
|
||||||
@@ -304,10 +304,10 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm font-medium text-gray-900">
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
<%= credential.nickname %>
|
<%= credential.nickname %>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-500">
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
<%= credential.authenticator_type.humanize %> •
|
<%= credential.authenticator_type.humanize %> •
|
||||||
Last used <%= credential.last_used_ago %>
|
Last used <%= credential.last_used_ago %>
|
||||||
<% if credential.backed_up? %>
|
<% if credential.backed_up? %>
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<% if credential.created_recently? %>
|
<% if credential.created_recently? %>
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-200">
|
||||||
New
|
New
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -338,7 +338,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 p-3 bg-blue-50 rounded-lg">
|
<div class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/30 rounded-lg">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
|
||||||
@@ -346,7 +346,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3">
|
<div class="ml-3">
|
||||||
<p class="text-sm text-blue-800">
|
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||||
<strong>Tip:</strong> Add passkeys on multiple devices for easy access. Platform authenticators (like Touch ID) are synced across your devices if you use iCloud Keychain or Google Password Manager.
|
<strong>Tip:</strong> Add passkeys on multiple devices for easy access. Platform authenticators (like Touch ID) are synced across your devices if you use iCloud Keychain or Google Password Manager.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,11 +354,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="text-center py-8">
|
<div class="text-center py-8">
|
||||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="mt-2 text-sm font-medium text-gray-900">No passkeys</h3>
|
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-gray-100">No passkeys</h3>
|
||||||
<p class="mt-1 text-sm text-gray-500">Get started by adding your first passkey for passwordless sign-in.</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Get started by adding your first passkey for passwordless sign-in.</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<%= form_with url: signin_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
<%= form_with url: signin_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
||||||
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
|
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :email_address, "Email Address", class: "block font-medium text-sm text-gray-700" %>
|
<%= form.label :email_address, "Email Address", class: "block font-medium text-sm text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.email_field :email_address,
|
<%= form.email_field :email_address,
|
||||||
required: true,
|
required: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -14,17 +14,17 @@
|
|||||||
placeholder: "your@email.com",
|
placeholder: "your@email.com",
|
||||||
value: @login_hint || params[:email_address],
|
value: @login_hint || params[:email_address],
|
||||||
data: { action: "blur->webauthn#checkWebAuthnSupport change->webauthn#checkWebAuthnSupport" },
|
data: { action: "blur->webauthn#checkWebAuthnSupport change->webauthn#checkWebAuthnSupport" },
|
||||||
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- WebAuthn section - initially hidden -->
|
<!-- WebAuthn section - initially hidden -->
|
||||||
<div id="webauthn-section" data-login-form-target="webauthnSection" class="my-5 hidden">
|
<div id="webauthn-section" data-login-form-target="webauthnSection" class="my-5 hidden">
|
||||||
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4 dark:bg-green-900/30 dark:border-green-700">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<svg class="w-5 h-5 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="text-sm text-green-800">
|
<p class="text-sm text-green-800 dark:text-green-200">
|
||||||
<strong>Passkey detected!</strong> You can sign in without a password.
|
<strong>Passkey detected!</strong> You can sign in without a password.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,13 +44,13 @@
|
|||||||
<!-- Password section - shown by default, hidden if WebAuthn is required -->
|
<!-- Password section - shown by default, hidden if WebAuthn is required -->
|
||||||
<div id="password-section" data-login-form-target="passwordSection">
|
<div id="password-section" data-login-form-target="passwordSection">
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :password, class: "block font-medium text-sm text-gray-700" %>
|
<%= form.label :password, class: "block font-medium text-sm text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password,
|
<%= form.password_field :password,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "current-password",
|
autocomplete: "current-password",
|
||||||
placeholder: "Enter your password",
|
placeholder: "Enter your password",
|
||||||
maxlength: 72,
|
maxlength: 72,
|
||||||
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
@@ -59,14 +59,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-sm text-gray-600 text-center">
|
<div class="mt-4 text-sm text-gray-600 dark:text-gray-400 text-center">
|
||||||
<%= link_to "Forgot your password?", new_password_path, class: "text-blue-600 hover:text-blue-500 underline" %>
|
<%= link_to "Forgot your password?", new_password_path, class: "text-blue-600 hover:text-blue-500 underline" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<!-- Loading overlay -->
|
<!-- Loading overlay -->
|
||||||
<div id="loading-overlay" data-login-form-target="loadingOverlay" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
|
<div id="loading-overlay" data-login-form-target="loadingOverlay"
|
||||||
<div class="bg-white rounded-lg p-6 flex items-center">
|
data-action="click->login-form#hideLoading"
|
||||||
|
class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 cursor-pointer">
|
||||||
|
<div class="bg-white rounded-lg p-6 flex items-center dark:bg-gray-900">
|
||||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div class="mx-auto max-w-md">
|
<div class="mx-auto max-w-md">
|
||||||
<div class="bg-white py-8 px-6 shadow rounded-lg sm:px-10">
|
<div class="bg-white py-8 px-6 shadow rounded-lg sm:px-10 dark:bg-gray-900">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h2 class="text-2xl font-bold text-gray-900">Two-Factor Authentication</h2>
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Two-Factor Authentication</h2>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Enter the 6-digit code from your authenticator app to complete sign in.
|
Enter the 6-digit code from your authenticator app to complete sign in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
} do |form| %>
|
} do |form| %>
|
||||||
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
|
<%= hidden_field_tag :rd, params[:rd] if params[:rd].present? %>
|
||||||
<div>
|
<div>
|
||||||
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700" %>
|
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= text_field_tag :code,
|
<%= text_field_tag :code,
|
||||||
nil,
|
nil,
|
||||||
placeholder: "000000",
|
placeholder: "000000",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
required: true,
|
required: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autocomplete: "off",
|
autocomplete: "off",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-center text-2xl tracking-widest font-mono sm:text-sm" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-center text-2xl tracking-widest font-mono sm:text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
<p class="mt-2 text-xs text-gray-500">
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
Enter your 6-digit authenticator code or an 8-character backup code
|
Enter your 6-digit authenticator code or an 8-character backup code
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= form.submit "Verify",
|
<%= form.submit "Verify",
|
||||||
data: { form_submit_protection_target: "submit" },
|
data: { form_submit_protection_target: "submit" },
|
||||||
class: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %>
|
class: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="relative my-4">
|
<div class="relative my-4">
|
||||||
<div class="absolute inset-0 flex items-center">
|
<div class="absolute inset-0 flex items-center">
|
||||||
<div class="w-full border-t border-gray-300"></div>
|
<div class="w-full border-t border-gray-300 dark:border-gray-600"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex justify-center text-sm">
|
<div class="relative flex justify-center text-sm">
|
||||||
<span class="px-2 bg-white text-gray-500">Or</span>
|
<span class="px-2 bg-white text-gray-500 dark:bg-gray-900 dark:text-gray-400">Or</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -62,18 +62,18 @@
|
|||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="absolute inset-0 flex items-center">
|
<div class="absolute inset-0 flex items-center">
|
||||||
<div class="w-full border-t border-gray-300"></div>
|
<div class="w-full border-t border-gray-300 dark:border-gray-600"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex justify-center text-sm">
|
<div class="relative flex justify-center text-sm">
|
||||||
<span class="px-2 bg-white text-gray-500">Need help?</span>
|
<span class="px-2 bg-white text-gray-500 dark:bg-gray-900 dark:text-gray-400">Need help?</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-center">
|
<div class="mt-4 text-center">
|
||||||
<p class="text-sm text-gray-600">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
Lost access to your authenticator?
|
Lost access to your authenticator?
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 text-xs text-gray-500">
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
Contact an administrator for assistance.
|
Contact an administrator for assistance.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,34 +8,34 @@
|
|||||||
# Map flash types to styling
|
# Map flash types to styling
|
||||||
case type.to_s
|
case type.to_s
|
||||||
when 'notice'
|
when 'notice'
|
||||||
bg_class = 'bg-green-50'
|
bg_class = 'bg-green-50 dark:bg-green-900/30'
|
||||||
text_class = 'text-green-800'
|
text_class = 'text-green-800 dark:text-green-200'
|
||||||
icon_class = 'text-green-400'
|
icon_class = 'text-green-400 dark:text-green-300'
|
||||||
icon_path = 'M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z'
|
icon_path = 'M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z'
|
||||||
auto_dismiss = true
|
auto_dismiss = true
|
||||||
when 'alert', 'error'
|
when 'alert', 'error'
|
||||||
bg_class = 'bg-red-50'
|
bg_class = 'bg-red-50 dark:bg-red-900/30'
|
||||||
text_class = 'text-red-800'
|
text_class = 'text-red-800 dark:text-red-200'
|
||||||
icon_class = 'text-red-400'
|
icon_class = 'text-red-400 dark:text-red-300'
|
||||||
icon_path = 'M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z'
|
icon_path = 'M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z'
|
||||||
auto_dismiss = false
|
auto_dismiss = false
|
||||||
when 'warning'
|
when 'warning'
|
||||||
bg_class = 'bg-yellow-50'
|
bg_class = 'bg-yellow-50 dark:bg-yellow-900/30'
|
||||||
text_class = 'text-yellow-800'
|
text_class = 'text-yellow-800 dark:text-yellow-200'
|
||||||
icon_class = 'text-yellow-400'
|
icon_class = 'text-yellow-400 dark:text-yellow-300'
|
||||||
icon_path = 'M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z'
|
icon_path = 'M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z'
|
||||||
auto_dismiss = false
|
auto_dismiss = false
|
||||||
when 'info'
|
when 'info'
|
||||||
bg_class = 'bg-blue-50'
|
bg_class = 'bg-blue-50 dark:bg-blue-900/30'
|
||||||
text_class = 'text-blue-800'
|
text_class = 'text-blue-800 dark:text-blue-200'
|
||||||
icon_class = 'text-blue-400'
|
icon_class = 'text-blue-400 dark:text-blue-300'
|
||||||
icon_path = 'M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
|
icon_path = 'M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
|
||||||
auto_dismiss = true
|
auto_dismiss = true
|
||||||
else
|
else
|
||||||
# Default styling for unknown types
|
# Default styling for unknown types
|
||||||
bg_class = 'bg-gray-50'
|
bg_class = 'bg-gray-50 dark:bg-gray-800'
|
||||||
text_class = 'text-gray-800'
|
text_class = 'text-gray-800 dark:text-gray-200'
|
||||||
icon_class = 'text-gray-400'
|
icon_class = 'text-gray-400 dark:text-gray-500'
|
||||||
icon_path = 'M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
|
icon_path = 'M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z'
|
||||||
auto_dismiss = false
|
auto_dismiss = false
|
||||||
end
|
end
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<div class="-mx-1.5 -my-1.5">
|
<div class="-mx-1.5 -my-1.5">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-action="click->flash#dismiss"
|
data-action="click->flash#dismiss"
|
||||||
class="inline-flex rounded-md <%= bg_class %> p-1.5 <%= icon_class %> hover:bg-opacity-70 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-<%= bg_class.gsub('bg-', '') %>"
|
class="inline-flex rounded-md <%= bg_class %> p-1.5 <%= icon_class %> hover:bg-opacity-70 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||||
aria-label="Dismiss">
|
aria-label="Dismiss">
|
||||||
<span class="sr-only">Dismiss</span>
|
<span class="sr-only">Dismiss</span>
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
|||||||
@@ -3,19 +3,19 @@
|
|||||||
|
|
||||||
<% form_object = form.respond_to?(:object) ? form.object : (object || form) %>
|
<% form_object = form.respond_to?(:object) ? form.object : (object || form) %>
|
||||||
<% if form_object&.errors&.any? %>
|
<% if form_object&.errors&.any? %>
|
||||||
<div class="rounded-md bg-red-50 p-4 mb-6 border border-red-200" role="alert" aria-labelledby="form-errors-title" data-form-errors-target="container">
|
<div class="rounded-md bg-red-50 dark:bg-red-900/30 p-4 mb-6 border border-red-200 dark:border-red-700" role="alert" aria-labelledby="form-errors-title" data-form-errors-target="container">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5 text-red-400 dark:text-red-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-3 flex-1">
|
<div class="ml-3 flex-1">
|
||||||
<h3 id="form-errors-title" class="text-sm font-medium text-red-800">
|
<h3 id="form-errors-title" class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||||
<%= pluralize(form_object.errors.count, "error") %> prohibited this <%= form_object.class.name.downcase.gsub(/^admin::/, '') %> from being saved:
|
<%= pluralize(form_object.errors.count, "error") %> prohibited this <%= form_object.class.name.downcase.gsub(/^admin::/, '') %> from being saved:
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<ul class="list-disc space-y-1 pl-5 text-sm text-red-700">
|
<ul class="list-disc space-y-1 pl-5 text-sm text-red-700 dark:text-red-300">
|
||||||
<% form_object.errors.full_messages.each do |message| %>
|
<% form_object.errors.full_messages.each do |message| %>
|
||||||
<li><%= message %></li>
|
<li><%= message %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-auto pl-3">
|
<div class="ml-auto pl-3">
|
||||||
<div class="-mx-1.5 -my-1.5">
|
<div class="-mx-1.5 -my-1.5">
|
||||||
<button type="button" data-action="click->form-errors#dismiss" class="inline-flex rounded-md bg-red-50 p-1.5 text-red-500 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 focus:ring-offset-red-50" aria-label="Dismiss">
|
<button type="button" data-action="click->form-errors#dismiss" class="inline-flex rounded-md bg-red-50 dark:bg-red-900/30 p-1.5 text-red-500 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 focus:ring-offset-red-50 dark:focus:ring-offset-gray-900" aria-label="Dismiss">
|
||||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -33,4 +33,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
|
|
||||||
<!-- Desktop sidebar -->
|
<!-- Desktop sidebar -->
|
||||||
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-64 lg:flex-col">
|
<div class="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-64 lg:flex-col">
|
||||||
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4">
|
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-6 pb-4">
|
||||||
<!-- Branding and User Info -->
|
<!-- Branding and User Info -->
|
||||||
<div class="flex flex-col pt-5 pb-4 border-b border-gray-200">
|
<div class="flex flex-col pt-5 pb-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Clinch</h1>
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Clinch</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-600 truncate"><%= user.email_address %></p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 truncate"><%= user.email_address %></p>
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-800 mt-1">
|
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-0.5 text-xs font-medium text-blue-800 dark:text-blue-200 mt-1">
|
||||||
Administrator
|
Administrator
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<ul role="list" class="-mx-2 space-y-1">
|
<ul role="list" class="-mx-2 space-y-1">
|
||||||
<!-- Dashboard -->
|
<!-- Dashboard -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to root_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to root_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<!-- Admin: Users -->
|
<!-- Admin: Users -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_users_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/users') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to admin_users_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/users') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
|
|
||||||
<!-- Admin: Applications -->
|
<!-- Admin: Applications -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_applications_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/applications') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to admin_applications_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/applications') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
<!-- Admin: Groups -->
|
<!-- Admin: Groups -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_groups_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/groups') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to admin_groups_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/groups') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
<!-- Profile -->
|
<!-- Profile -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to profile_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/profile' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to profile_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/profile' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
<!-- Sessions -->
|
<!-- Sessions -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/active_sessions' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/active_sessions' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -88,9 +88,25 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Dark Mode Toggle -->
|
||||||
|
<li data-controller="dark-mode">
|
||||||
|
<button type="button" data-action="click->dark-mode#toggle" class="group flex w-full gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||||
|
<!-- Moon icon (shown in light mode) -->
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="light" class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
||||||
|
</svg>
|
||||||
|
<!-- Sun icon (shown in dark mode) -->
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="dark" class="hidden h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||||
|
</svg>
|
||||||
|
<span data-dark-mode-target="icon" data-mode="light">Dark Mode</span>
|
||||||
|
<span data-dark-mode-target="icon" data-mode="dark" class="hidden">Light Mode</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Sign Out -->
|
<!-- Sign Out -->
|
||||||
<li>
|
<li>
|
||||||
<%= link_to signout_path, data: { turbo_method: :delete, action: "click->mobile-sidebar#closeSidebar" }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 hover:text-red-700 hover:bg-red-50" do %>
|
<%= link_to signout_path, data: { turbo_method: :delete, action: "click->mobile-sidebar#closeSidebar" }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -124,16 +140,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white px-6 pb-2">
|
<div class="flex grow flex-col gap-y-5 overflow-y-auto bg-white dark:bg-gray-900 px-6 pb-2">
|
||||||
<!-- Branding and User Info -->
|
<!-- Branding and User Info -->
|
||||||
<div class="flex flex-col pt-5 pb-4 border-b border-gray-200">
|
<div class="flex flex-col pt-5 pb-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Clinch</h1>
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Clinch</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-gray-600 truncate"><%= user.email_address %></p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 truncate"><%= user.email_address %></p>
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-800 mt-1">
|
<span class="inline-flex items-center rounded-full bg-blue-100 dark:bg-blue-900/50 px-2 py-0.5 text-xs font-medium text-blue-800 dark:text-blue-200 mt-1">
|
||||||
Administrator
|
Administrator
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -144,7 +160,7 @@
|
|||||||
<!-- Same nav items as desktop -->
|
<!-- Same nav items as desktop -->
|
||||||
<ul role="list" class="-mx-2 space-y-1">
|
<ul role="list" class="-mx-2 space-y-1">
|
||||||
<li>
|
<li>
|
||||||
<%= link_to root_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to root_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -153,7 +169,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% if user.admin? %>
|
<% if user.admin? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_users_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/users') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to admin_users_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/users') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -161,7 +177,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_applications_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/applications') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to admin_applications_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/applications') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -169,7 +185,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to admin_groups_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/groups') ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to admin_groups_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path.start_with?('/admin/groups') ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -178,7 +194,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to profile_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/profile' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to profile_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/profile' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -186,15 +202,30 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/active_sessions' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/active_sessions' ? 'bg-gray-50 dark:bg-gray-800 text-blue-600 dark:text-blue-400' : 'text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800' }", data: { action: "click->mobile-sidebar#closeSidebar" } do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
||||||
</svg>
|
</svg>
|
||||||
Sessions
|
Sessions
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Dark Mode Toggle (mobile) -->
|
||||||
|
<li data-controller="dark-mode">
|
||||||
|
<button type="button" data-action="click->dark-mode#toggle" class="group flex w-full gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="light" class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
||||||
|
</svg>
|
||||||
|
<svg data-dark-mode-target="icon" data-mode="dark" class="hidden h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||||
|
</svg>
|
||||||
|
<span data-dark-mode-target="icon" data-mode="light">Dark Mode</span>
|
||||||
|
<span data-dark-mode-target="icon" data-mode="dark" class="hidden">Light Mode</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<%= link_to signout_path, data: { turbo_method: :delete, action: "click->mobile-sidebar#closeSidebar" }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 hover:text-red-700 hover:bg-red-50" do %>
|
<%= link_to signout_path, data: { turbo_method: :delete, action: "click->mobile-sidebar#closeSidebar" }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20" do %>
|
||||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
<div class="max-w-2xl mx-auto" data-controller="backup-codes" data-backup-codes-codes-value="<%= @backup_codes.to_json %>">
|
<div class="max-w-2xl mx-auto" data-controller="backup-codes" data-backup-codes-codes-value="<%= @backup_codes.to_json %>">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Backup Codes</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Backup Codes</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Save these backup codes in a safe place. Each code can only be used once.
|
Save these backup codes in a safe place. Each code can only be used once.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<div class="rounded-md bg-yellow-50 p-4 mb-6">
|
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/30 p-4 mb-6">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-sm text-yellow-800">
|
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<p class="font-medium">Save these codes now!</p>
|
<p class="font-medium">Save these codes now!</p>
|
||||||
<p class="mt-1">Store them somewhere safe. You won't be able to see them again without re-entering your password.</p>
|
<p class="mt-1">Store them somewhere safe. You won't be able to see them again without re-entering your password.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 p-6 bg-gray-50 rounded-lg font-mono">
|
<div class="grid grid-cols-2 gap-4 p-6 bg-gray-50 dark:bg-gray-700 rounded-lg font-mono">
|
||||||
<% @backup_codes.each do |code| %>
|
<% @backup_codes.each do |code| %>
|
||||||
<div class="text-center text-lg tracking-wider py-2 px-4 bg-white rounded border border-gray-200">
|
<div class="text-center text-lg tracking-wider py-2 px-4 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 dark:text-gray-100">
|
||||||
<%= code %>
|
<%= code %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 flex gap-3">
|
<div class="mt-6 flex gap-3">
|
||||||
<button data-action="click->backup-codes#download" class="inline-flex items-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
<button data-action="click->backup-codes#download" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||||
</svg>
|
</svg>
|
||||||
Download Codes
|
Download Codes
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button data-action="click->backup-codes#print" class="inline-flex items-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
<button data-action="click->backup-codes#print" class="inline-flex items-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
|
||||||
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -47,13 +47,12 @@
|
|||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<% if @auto_signin_pending %>
|
<% if @auto_signin_pending %>
|
||||||
<%= button_to "Continue to Sign In", complete_totp_setup_path, method: :post,
|
<%= button_to "Continue to Sign In", complete_totp_setup_path, method: :post,
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= link_to "Done", profile_path,
|
<%= link_to "Done", profile_path,
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Enable Two-Factor Authentication</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Enable Two-Factor Authentication</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
Scan the QR code below with your authenticator app, then enter the verification code to confirm.
|
Scan the QR code below with your authenticator app, then enter the verification code to confirm.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<!-- Step 1: Scan QR Code -->
|
<!-- Step 1: Scan QR Code -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Step 1: Scan QR Code</h3>
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Step 1: Scan QR Code</h3>
|
||||||
<div class="flex justify-center p-6 bg-gray-50 rounded-lg">
|
<div class="flex justify-center p-6 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||||
<%= @qr_code.as_svg(
|
<%= @qr_code.as_svg(
|
||||||
module_size: 4,
|
module_size: 4,
|
||||||
color: "000",
|
color: "000",
|
||||||
@@ -19,26 +19,26 @@
|
|||||||
standalone: true
|
standalone: true
|
||||||
).html_safe %>
|
).html_safe %>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-4 text-sm text-gray-600 text-center">
|
<p class="mt-4 text-sm text-gray-600 dark:text-gray-400 text-center">
|
||||||
Use an authenticator app like Google Authenticator, Authy, or 1Password to scan this code.
|
Use an authenticator app like Google Authenticator, Authy, or 1Password to scan this code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Manual Entry Option -->
|
<!-- Manual Entry Option -->
|
||||||
<div class="mb-8 p-4 bg-blue-50 rounded-lg">
|
<div class="mb-8 p-4 bg-blue-50 dark:bg-blue-900/30 rounded-lg">
|
||||||
<p class="text-sm font-medium text-blue-900 mb-2">Can't scan the QR code?</p>
|
<p class="text-sm font-medium text-blue-900 dark:text-blue-200 mb-2">Can't scan the QR code?</p>
|
||||||
<p class="text-sm text-blue-800">Enter this key manually in your authenticator app:</p>
|
<p class="text-sm text-blue-800 dark:text-blue-300">Enter this key manually in your authenticator app:</p>
|
||||||
<code class="mt-2 block p-2 bg-white rounded text-sm font-mono break-all"><%= @totp_secret %></code>
|
<code class="mt-2 block p-2 bg-white dark:bg-gray-700 dark:text-gray-200 rounded text-sm font-mono break-all"><%= @totp_secret %></code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: Verify -->
|
<!-- Step 2: Verify -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Step 2: Verify</h3>
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-4">Step 2: Verify</h3>
|
||||||
<%= form_with url: totp_path, method: :post, class: "space-y-4" do |form| %>
|
<%= form_with url: totp_path, method: :post, class: "space-y-4" do |form| %>
|
||||||
<%= hidden_field_tag :totp_secret, @totp_secret %>
|
<%= hidden_field_tag :totp_secret, @totp_secret %>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700" %>
|
<%= label_tag :code, "Verification Code", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<%= text_field_tag :code,
|
<%= text_field_tag :code,
|
||||||
nil,
|
nil,
|
||||||
placeholder: "000000",
|
placeholder: "000000",
|
||||||
@@ -46,27 +46,27 @@
|
|||||||
required: true,
|
required: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autocomplete: "off",
|
autocomplete: "off",
|
||||||
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-center text-2xl tracking-widest font-mono" %>
|
class: "mt-1 block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-center text-2xl tracking-widest font-mono" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Enter the 6-digit code from your authenticator app</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Enter the 6-digit code from your authenticator app</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<%= form.submit "Verify and Enable 2FA",
|
<%= form.submit "Verify and Enable 2FA",
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
<%= link_to "Cancel", profile_path,
|
<%= link_to "Cancel", profile_path,
|
||||||
class: "inline-flex justify-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 p-4 bg-yellow-50 rounded-lg">
|
<div class="mt-6 p-4 bg-yellow-50 dark:bg-yellow-900/30 rounded-lg">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-sm text-yellow-800">
|
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<p class="font-medium">Important: Save your backup codes</p>
|
<p class="font-medium">Important: Save your backup codes</p>
|
||||||
<p class="mt-1">After verifying, you'll be shown backup codes. Save these in a safe place - they can be used to access your account if you lose your device.</p>
|
<p class="mt-1">After verifying, you'll be shown backup codes. Save these in a safe place - they can be used to access your account if you lose your device.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<div class="max-w-2xl mx-auto">
|
<div class="max-w-2xl mx-auto">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Regenerate Backup Codes</h1>
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Regenerate Backup Codes</h1>
|
||||||
<p class="mt-2 text-sm text-gray-600">
|
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
This will invalidate all existing backup codes and generate new ones.
|
This will invalidate all existing backup codes and generate new ones.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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="px-4 py-5 sm:p-6">
|
||||||
<div class="rounded-md bg-yellow-50 p-4 mb-6">
|
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/30 p-4 mb-6">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="h-5 w-5 text-yellow-400 mr-3 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="text-sm text-yellow-800">
|
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<p class="font-medium">Important Security Notice</p>
|
<p class="font-medium">Important Security Notice</p>
|
||||||
<p class="mt-1">All your current backup codes will become invalid after this action. Make sure you're ready to save the new codes.</p>
|
<p class="mt-1">All your current backup codes will become invalid after this action. Make sure you're ready to save the new codes.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,24 +22,24 @@
|
|||||||
|
|
||||||
<%= form_with(url: create_new_backup_codes_totp_path, method: :post, class: "space-y-6") do |form| %>
|
<%= form_with(url: create_new_backup_codes_totp_path, method: :post, class: "space-y-6") do |form| %>
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :password, "Enter your password to confirm", class: "block text-sm font-medium text-gray-700" %>
|
<%= form.label :password, "Enter your password to confirm", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<%= form.password_field :password, required: true,
|
<%= form.password_field :password, required: true,
|
||||||
class: "block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 sm:text-sm" %>
|
class: "block w-full appearance-none rounded-md border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 px-3 py-2 placeholder-gray-400 dark:placeholder-gray-500 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500 sm:text-sm" %>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-gray-500">
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
This is required to verify your identity before regenerating backup codes.
|
This is required to verify your identity before regenerating backup codes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<%= form.submit "Generate New Backup Codes",
|
<%= form.submit "Generate New Backup Codes",
|
||||||
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-transparent bg-blue-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
|
|
||||||
<%= link_to "Cancel", profile_path,
|
<%= link_to "Cancel", profile_path,
|
||||||
class: "inline-flex justify-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" %>
|
class: "inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 py-2 px-4 text-sm font-medium text-gray-700 dark:text-gray-200 shadow-sm hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
<div class="mx-auto md:w-2/3 w-full">
|
<div class="mx-auto md:w-2/3 w-full">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="font-bold text-4xl">Welcome to Clinch</h1>
|
<h1 class="font-bold text-4xl">Welcome to Clinch</h1>
|
||||||
<p class="mt-2 text-gray-600">Create your admin account to get started</p>
|
<p class="mt-2 text-gray-600 dark:text-gray-400">Create your admin account to get started</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= form_with model: @user, url: signup_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
<%= form_with model: @user, url: signup_path, class: "contents", data: { controller: "form-errors" } do |form| %>
|
||||||
<%= render "shared/form_errors", form: form %>
|
<%= render "shared/form_errors", form: form %>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :email_address, class: "block font-medium text-sm text-gray-700" %>
|
<%= form.label :email_address, class: "block font-medium text-sm text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.email_field :email_address,
|
<%= form.email_field :email_address,
|
||||||
required: true,
|
required: true,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
autocomplete: "email",
|
autocomplete: "email",
|
||||||
placeholder: "admin@example.com",
|
placeholder: "admin@example.com",
|
||||||
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :password, class: "block font-medium text-sm text-gray-700" %>
|
<%= form.label :password, class: "block font-medium text-sm text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password,
|
<%= form.password_field :password,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "new-password",
|
autocomplete: "new-password",
|
||||||
placeholder: "Enter a strong password",
|
placeholder: "Enter a strong password",
|
||||||
maxlength: 72,
|
maxlength: 72,
|
||||||
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
<p class="mt-1 text-sm text-gray-500">Must be at least 8 characters</p>
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Must be at least 8 characters</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :password_confirmation, "Confirm Password", class: "block font-medium text-sm text-gray-700" %>
|
<%= form.label :password_confirmation, "Confirm Password", class: "block font-medium text-sm text-gray-700 dark:text-gray-300" %>
|
||||||
<%= form.password_field :password_confirmation,
|
<%= form.password_field :password_confirmation,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "new-password",
|
autocomplete: "new-password",
|
||||||
placeholder: "Re-enter your password",
|
placeholder: "Re-enter your password",
|
||||||
maxlength: 72,
|
maxlength: 72,
|
||||||
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full" %>
|
class: "block shadow-sm rounded-md border border-gray-400 focus:outline-blue-600 px-3 py-2 mt-2 w-full dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
class: "w-full rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white font-medium cursor-pointer" %>
|
class: "w-full rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white font-medium cursor-pointer" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 p-4 bg-blue-50 rounded-lg">
|
<div class="mt-4 p-4 bg-blue-50 rounded-lg dark:bg-blue-900/30">
|
||||||
<p class="text-sm text-blue-900">
|
<p class="text-sm text-blue-900 dark:text-blue-200">
|
||||||
<strong>Note:</strong> This is a first-run setup. You're creating the initial administrator account.
|
<strong>Note:</strong> This is a first-run setup. You're creating the initial administrator account.
|
||||||
After this, you'll be able to invite other users from the admin dashboard.
|
After this, you'll be able to invite other users from the admin dashboard.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user