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>
356 lines
14 KiB
Python
356 lines
14 KiB
Python
import re
|
|
|
|
with open('ui/components/AdminRolesPage.tsx', 'r') as f:
|
|
content = f.read()
|
|
|
|
|
|
roles_table_desktop_mobile = """ {/* Desktop Table View (≥ 768px) */}
|
|
<div class="table-container desktop-only" style="display: none;">
|
|
<table id="rolesTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Role Identifier</th>
|
|
<th>Scope</th>
|
|
<th>Description</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{roles.length === 0
|
|
? (
|
|
<tr>
|
|
<td
|
|
colSpan={5}
|
|
style="text-align: center; color: var(--text-muted); padding: 2rem;"
|
|
>
|
|
No roles found.
|
|
</td>
|
|
</tr>
|
|
)
|
|
: (
|
|
roles.map((r) => {
|
|
const isGlobal = !r.app_id;
|
|
const isCoreAdmin = isGlobal && r.name === "admin";
|
|
|
|
return (
|
|
<tr
|
|
key={r.id}
|
|
class="role-row"
|
|
data-app-id={r.app_id || "global"}
|
|
data-search={`${r.name} ${r.description || ""} ${
|
|
isGlobal ? "global" : r.app_name || ""
|
|
}`.toLowerCase()}
|
|
>
|
|
<td>
|
|
<strong style="font-family: monospace; font-size: 0.95rem; color: var(--text-primary);">
|
|
{r.name}
|
|
</strong>
|
|
</td>
|
|
<td>
|
|
{isGlobal
|
|
? (
|
|
<span class="badge badge-info">
|
|
Global (Shared)
|
|
</span>
|
|
)
|
|
: (
|
|
<span class="badge badge-warning">
|
|
{r.app_name || "App-Specific"}
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td style="color: var(--text-secondary); font-size: 0.85rem;">
|
|
{r.description || "-"}
|
|
</td>
|
|
<td style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
{new Date(r.created_at).toLocaleDateString()}
|
|
</td>
|
|
<td>
|
|
{!isCoreAdmin
|
|
? (
|
|
<div style="display: flex; gap: 0.35rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
style="padding: 0.25rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
|
|
onclick={`openEditRoleDrawer(${
|
|
JSON.stringify(JSON.stringify(r))
|
|
})`}
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger"
|
|
style="padding: 0.25rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
|
|
onclick={`deleteRole('${r.id}', '${r.name}')`}
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
)
|
|
: (
|
|
<span style="font-size: 0.75rem; color: var(--text-muted); font-style: italic;">
|
|
Immutable System Role
|
|
</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Mobile View (< 768px) */}
|
|
<div
|
|
id="rolesMobileDeck"
|
|
class="mobile-only"
|
|
style="display: flex; flex-direction: column; gap: 0.75rem;"
|
|
>
|
|
{roles.length === 0
|
|
? (
|
|
<div class="card" style="text-align: center; padding: 2rem;">
|
|
<p style="color: var(--text-muted); margin: 0;">
|
|
No roles found.
|
|
</p>
|
|
</div>
|
|
)
|
|
: (
|
|
roles.map((r) => {
|
|
const isGlobal = !r.app_id;
|
|
const isCoreAdmin = isGlobal && r.name === "admin";
|
|
|
|
return (
|
|
<div
|
|
class="card role-card"
|
|
key={r.id}
|
|
data-app-id={r.app_id || "global"}
|
|
data-search={`${r.name} ${r.description || ""} ${
|
|
isGlobal ? "global" : r.app_name || ""
|
|
}`.toLowerCase()}
|
|
style="margin-bottom: 0; padding: 1rem;"
|
|
>
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
|
|
<strong style="font-family: monospace; font-size: 1.05rem; color: var(--text-primary);">
|
|
{r.name}
|
|
</strong>
|
|
{isGlobal
|
|
? (
|
|
<span class="badge badge-info">
|
|
Global (Shared)
|
|
</span>
|
|
)
|
|
: (
|
|
<span class="badge badge-warning">
|
|
{r.app_name || "App-Specific"}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 1rem; line-height: 1.5;">
|
|
<p style="margin: 0 0 0.5rem 0;">
|
|
{r.description || "No description provided."}
|
|
</p>
|
|
<div style="font-size: 0.8rem;">
|
|
Created: {new Date(r.created_at).toLocaleDateString()}
|
|
</div>
|
|
</div>
|
|
|
|
{!isCoreAdmin
|
|
? (
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
style="flex: 1; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`openEditRoleDrawer(${
|
|
JSON.stringify(JSON.stringify(r))
|
|
})`}
|
|
>
|
|
Edit Role
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger"
|
|
style="flex: 1; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`deleteRole('${r.id}', '${r.name}')`}
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
)
|
|
: (
|
|
<div style="text-align: center; padding: 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-size: 0.8rem; color: var(--text-muted); font-style: italic;">
|
|
Immutable System Role
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>"""
|
|
|
|
roles_table_replacement = """ <AdminTable
|
|
id="rolesTable"
|
|
headers={[
|
|
"Role Identifier",
|
|
"Scope",
|
|
"Description",
|
|
"Created",
|
|
"Actions",
|
|
]}
|
|
isEmpty={roles.length === 0}
|
|
emptyState="No roles found."
|
|
desktopRows={roles.map((r) => {
|
|
const isGlobal = !r.app_id;
|
|
const isCoreAdmin = isGlobal && r.name === "admin";
|
|
|
|
return (
|
|
<tr
|
|
key={r.id}
|
|
class="role-row"
|
|
data-app-id={r.app_id || "global"}
|
|
data-search={`${r.name} ${r.description || ""} ${
|
|
isGlobal ? "global" : r.app_name || ""
|
|
}`.toLowerCase()}
|
|
>
|
|
<td>
|
|
<strong style="font-family: monospace; font-size: 0.95rem; color: var(--text-primary);">
|
|
{r.name}
|
|
</strong>
|
|
</td>
|
|
<td>
|
|
{isGlobal
|
|
? (
|
|
<span class="badge badge-info">
|
|
Global (Shared)
|
|
</span>
|
|
)
|
|
: (
|
|
<span class="badge badge-warning">
|
|
{r.app_name || "App-Specific"}
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td style="color: var(--text-secondary); font-size: 0.85rem;">
|
|
{r.description || "-"}
|
|
</td>
|
|
<td style="font-size: 0.85rem; color: var(--text-secondary);">
|
|
{new Date(r.created_at).toLocaleDateString()}
|
|
</td>
|
|
<td>
|
|
{!isCoreAdmin
|
|
? (
|
|
<div style="display: flex; gap: 0.35rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
style="padding: 0.25rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
|
|
onclick={`openEditRoleDrawer(${
|
|
JSON.stringify(JSON.stringify(r))
|
|
})`}
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger"
|
|
style="padding: 0.25rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
|
|
onclick={`deleteRole('${r.id}', '${r.name}')`}
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
)
|
|
: (
|
|
<span style="font-size: 0.75rem; color: var(--text-muted); font-style: italic;">
|
|
Immutable System Role
|
|
</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
mobileCards={roles.map((r) => {
|
|
const isGlobal = !r.app_id;
|
|
const isCoreAdmin = isGlobal && r.name === "admin";
|
|
|
|
return (
|
|
<div
|
|
class="card role-card"
|
|
key={r.id}
|
|
data-app-id={r.app_id || "global"}
|
|
data-search={`${r.name} ${r.description || ""} ${
|
|
isGlobal ? "global" : r.app_name || ""
|
|
}`.toLowerCase()}
|
|
style="margin-bottom: 0; padding: 1rem;"
|
|
>
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
|
|
<strong style="font-family: monospace; font-size: 1.05rem; color: var(--text-primary);">
|
|
{r.name}
|
|
</strong>
|
|
{isGlobal
|
|
? (
|
|
<span class="badge badge-info">
|
|
Global (Shared)
|
|
</span>
|
|
)
|
|
: (
|
|
<span class="badge badge-warning">
|
|
{r.app_name || "App-Specific"}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 1rem; line-height: 1.5;">
|
|
<p style="margin: 0 0 0.5rem 0;">
|
|
{r.description || "No description provided."}
|
|
</p>
|
|
<div style="font-size: 0.8rem;">
|
|
Created: {new Date(r.created_at).toLocaleDateString()}
|
|
</div>
|
|
</div>
|
|
|
|
{!isCoreAdmin
|
|
? (
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
style="flex: 1; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`openEditRoleDrawer(${
|
|
JSON.stringify(JSON.stringify(r))
|
|
})`}
|
|
>
|
|
Edit Role
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger"
|
|
style="flex: 1; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`deleteRole('${r.id}', '${r.name}')`}
|
|
>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
)
|
|
: (
|
|
<div style="text-align: center; padding: 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-size: 0.8rem; color: var(--text-muted); font-style: italic;">
|
|
Immutable System Role
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
/>"""
|
|
|
|
content = content.replace(roles_table_desktop_mobile, roles_table_replacement)
|
|
|
|
|
|
with open('ui/components/AdminRolesPage.tsx', 'w') as f:
|
|
f.write(content)
|
|
print("Replaced Roles table!")
|