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>
386 lines
15 KiB
Python
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!")
|