auth-yes/replace_user_details.py
google-labs-jules[bot] 4c7d8d4af7 feat(ui): decompose admin pages into reusable AdminTable and AdminModal components
Refactored AdminInvitesPage, AdminUserDetailsPage, AdminRolesPage, and AdminAppsPage to use the new pure Hono SSR JSX stateless components.
Fixed missing import definitions in AdminRolesPage.
Moved task file to complete state.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-26 00:34:29 +00:00

386 lines
15 KiB
Python

import re
with open('ui/components/AdminUserDetailsPage.tsx', 'r') as f:
content = f.read()
import_str = """import { AdminLayout } from "./AdminLayout.tsx";"""
content = content.replace(import_str, """import { AdminLayout } from "./AdminLayout.tsx";\nimport { AdminTable } from "./admin/AdminTable.tsx";""")
grants_table = """ <div class="table-container" style="margin-top: 1.25rem;">
<table>
<thead>
<tr>
<th>Application Name</th>
<th>SPIFFE Workload ID</th>
<th>Assigned Role</th>
<th>Granted At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{grants.length === 0
? (
<tr>
<td
colSpan={5}
style="text-align: center; color: var(--danger); padding: 1.5rem;"
>
No application permissions granted (User is blocked from
all subsidiary apps).
</td>
</tr>
)
: (
grants.map((grant) => (
<tr key={grant.id}>
<td>
<strong style="color: var(--text-primary);">
{grant.app_name}
</strong>
</td>
<td>
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.8rem; font-family: monospace;">
{grant.spiffe_id}
</code>
</td>
<td>
<span class="badge badge-info">
{grant.role}
</span>
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(grant.created_at).toLocaleDateString()}
</td>
<td>
<button
type="button"
class="btn-danger"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`revokeGrant('${user.id}', '${grant.app_id}', '${grant.app_name}')`}
>
Revoke Access
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>"""
grants_replacement = """ <div style="margin-top: 1.25rem;">
<AdminTable
id="grantsTable"
headers={[
"Application Name",
"SPIFFE Workload ID",
"Assigned Role",
"Granted At",
"Actions",
]}
isEmpty={grants.length === 0}
emptyState="No application permissions granted (User is blocked from all subsidiary apps)."
desktopRows={grants.map((grant) => (
<tr key={grant.id}>
<td>
<strong style="color: var(--text-primary);">
{grant.app_name}
</strong>
</td>
<td>
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); font-size: 0.8rem; font-family: monospace;">
{grant.spiffe_id}
</code>
</td>
<td>
<span class="badge badge-info">
{grant.role}
</span>
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(grant.created_at).toLocaleDateString()}
</td>
<td>
<button
type="button"
class="btn-danger"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`revokeGrant('${user.id}', '${grant.app_id}', '${grant.app_name}')`}
>
Revoke Access
</button>
</td>
</tr>
))}
mobileCards={grants.map((grant) => (
<div class="card" key={grant.id} style="margin-bottom: 0;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
<strong style="color: var(--text-primary);">{grant.app_name}</strong>
<span class="badge badge-info">{grant.role}</span>
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.75rem; font-family: monospace;">
{grant.spiffe_id}
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.8rem; color: var(--text-secondary);">
{new Date(grant.created_at).toLocaleDateString()}
</span>
<button
type="button"
class="btn-danger"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem;"
onclick={`revokeGrant('${user.id}', '${grant.app_id}', '${grant.app_name}')`}
>
Revoke Access
</button>
</div>
</div>
))}
/>
</div>"""
content = content.replace(grants_table, grants_replacement)
sessions_table = """ <div class="table-container">
<table>
<thead>
<tr>
<th>Session ID</th>
<th>Created</th>
<th>Expires</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{sessions.length === 0
? (
<tr>
<td
colSpan={4}
style="text-align: center; color: var(--text-muted); padding: 1.5rem;"
>
No active sessions.
</td>
</tr>
)
: (
sessions.map((s) => (
<tr key={s.id}>
<td style="font-family: monospace; font-size: 0.85rem;">
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); color: var(--text-primary);">
{s.id.substring(0, 16)}...
</code>
{s.is_agent
? (
<span
class="badge badge-warning"
style="margin-left: 0.75rem;"
>
Agent Session
</span>
)
: null}
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(s.created_at).toLocaleString()}
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(s.expires_at).toLocaleString()}
</td>
<td>
<button
type="button"
class="btn-outline"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`revokeSession('${user.id}', '${s.id}')`}
>
Revoke
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>"""
sessions_replacement = """ <AdminTable
id="sessionsTable"
headers={["Session ID", "Created", "Expires", "Actions"]}
isEmpty={sessions.length === 0}
emptyState="No active sessions."
desktopRows={sessions.map((s) => (
<tr key={s.id}>
<td style="font-family: monospace; font-size: 0.85rem;">
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); color: var(--text-primary);">
{s.id.substring(0, 16)}...
</code>
{s.is_agent
? (
<span
class="badge badge-warning"
style="margin-left: 0.75rem;"
>
Agent Session
</span>
)
: null}
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(s.created_at).toLocaleString()}
</td>
<td style="font-size: 0.85rem; color: var(--text-secondary);">
{new Date(s.expires_at).toLocaleString()}
</td>
<td>
<button
type="button"
class="btn-outline"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`revokeSession('${user.id}', '${s.id}')`}
>
Revoke
</button>
</td>
</tr>
))}
mobileCards={sessions.map((s) => (
<div class="card" key={s.id} style="margin-bottom: 0;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
<code style="font-family: monospace; font-size: 0.85rem; background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm);">
{s.id.substring(0, 16)}...
</code>
{s.is_agent
? (
<span class="badge badge-warning">Agent Session</span>
)
: null}
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.25rem;">
Created: {new Date(s.created_at).toLocaleString()}
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.8rem; color: var(--text-secondary);">
Expires: {new Date(s.expires_at).toLocaleString()}
</span>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem;"
onclick={`revokeSession('${user.id}', '${s.id}')`}
>
Revoke
</button>
</div>
</div>
))}
/>"""
content = content.replace(sessions_table, sessions_replacement)
passkeys_table = """ <div class="table-container">
<table>
<thead>
<tr>
<th>Credential ID</th>
<th>Counter</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{passkeys.length === 0
? (
<tr>
<td
colSpan={3}
style="text-align: center; color: var(--text-muted); padding: 1.5rem;"
>
No registered passkeys.
</td>
</tr>
)
: (
passkeys.map((pk) => (
<tr key={pk.credential_id}>
<td style="font-family: monospace; font-size: 0.85rem; word-break: break-all; max-width: 250px;">
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); color: var(--text-primary);">
{pk.credential_id.substring(0, 32)}...
</code>
</td>
<td>
<span class="badge badge-info">{pk.sign_count}</span>
</td>
<td>
<button
type="button"
class="btn-danger"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`deletePasskey('${user.id}', '${pk.credential_id}')`}
>
Delete
</button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>"""
passkeys_replacement = """ <AdminTable
id="passkeysTable"
headers={["Credential ID", "Counter", "Actions"]}
isEmpty={passkeys.length === 0}
emptyState="No registered passkeys."
desktopRows={passkeys.map((pk) => (
<tr key={pk.credential_id}>
<td style="font-family: monospace; font-size: 0.85rem; word-break: break-all; max-width: 250px;">
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); color: var(--text-primary);">
{pk.credential_id.substring(0, 32)}...
</code>
</td>
<td>
<span class="badge badge-info">{pk.sign_count}</span>
</td>
<td>
<button
type="button"
class="btn-danger"
style="padding: 0.35rem 0.75rem; font-size: 0.8rem; min-height: 32px;"
onclick={`deletePasskey('${user.id}', '${pk.credential_id}')`}
>
Delete
</button>
</td>
</tr>
))}
mobileCards={passkeys.map((pk) => (
<div class="card" key={pk.credential_id} style="margin-bottom: 0;">
<div style="font-family: monospace; font-size: 0.85rem; word-break: break-all; margin-bottom: 0.75rem; color: var(--text-primary);">
<code style="background: var(--surface-muted); padding: 0.2rem 0.4rem; border-radius: var(--radius-sm); color: var(--text-primary);">
{pk.credential_id.substring(0, 32)}...
</code>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span class="badge badge-info">Count: {pk.sign_count}</span>
<button
type="button"
class="btn-danger"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem;"
onclick={`deletePasskey('${user.id}', '${pk.credential_id}')`}
>
Delete
</button>
</div>
</div>
))}
/>"""
content = content.replace(passkeys_table, passkeys_replacement)
with open('ui/components/AdminUserDetailsPage.tsx', 'w') as f:
f.write(content)
print("Replaced User Details tables!")