diff --git a/frontend/src/admin/AdminApp.vue b/frontend/src/admin/AdminApp.vue
index 51b56e7..29882ed 100644
--- a/frontend/src/admin/AdminApp.vue
+++ b/frontend/src/admin/AdminApp.vue
@@ -5,6 +5,10 @@ import CredentialList from '@/components/CredentialList.vue'
import UserBasicInfo from '@/components/UserBasicInfo.vue'
import RegistrationLinkModal from '@/components/RegistrationLinkModal.vue'
import StatusMessage from '@/components/StatusMessage.vue'
+import AdminOverview from './AdminOverview.vue'
+import AdminOrgDetail from './AdminOrgDetail.vue'
+import AdminUserDetail from './AdminUserDetail.vue'
+import AdminDialogs from './AdminDialogs.vue'
import { useAuthStore } from '@/stores/auth'
const info = ref(null)
@@ -467,221 +471,51 @@ async function submitDialog() {
Insufficient permissions.
-
-
Organizations
-
-
-
-
-
-
- | Name |
- Roles |
- Members |
- Actions |
-
-
-
-
- |
- {{ o.display_name }}
-
- |
- {{ o.roles.length }} |
- {{ o.roles.reduce((acc,r)=>acc + r.users.length,0) }} |
-
-
- |
-
-
-
-
+
-
-
-
{{ userDetail.error }}
-
- Registered Passkeys
-
-
-
-
-
-
-
-
Use the token dialog to register a new credential for the member.
-
-
-
-
- {{ selectedOrg.display_name }}
-
-
-
+
+
-
-
-
Toggle which permissions each role grants.
-
-
-
onRoleDrop(e, selectedOrg, r)"
- >
-
-
-
- - onUserDragStart(e, u, selectedOrg.uuid)"
- @click="openUser(u)"
- :title="u.uuid"
- >
- {{ u.display_name }}
- {{ u.last_seen ? new Date(u.last_seen).toLocaleDateString() : 'â' }}
-
-
-
-
-
-
-
-
-
-
Permissions
-
-
-
Toggle which permissions each organization can grant to its members.
-
-
-
-
-
-
-
- | Permission |
- Members |
- Actions |
-
-
-
-
- |
-
- {{ p.display_name }}
-
-
-
- {{ p.id }}
-
-
- |
- {{ permissionSummary[p.id]?.userCount || 0 }} |
-
-
- |
-
-
-
-
@@ -689,70 +523,12 @@ async function submitDialog() {
-
-
-
- Create Organization
- Rename Organization
- Create Role
- Edit Role
- Add User To Role
- Create Permission
- Edit Permission Display
- Confirm
-
-
-
-
+
@@ -762,134 +538,4 @@ async function submitDialog() {
.admin-section { margin-top: var(--space-xl); }
.admin-section-body { display: flex; flex-direction: column; gap: var(--space-xl); }
.admin-panels { display: flex; flex-direction: column; gap: var(--space-xl); }
-.permissions-section { margin-bottom: var(--space-xl); }
-.permissions-section h2 { margin-bottom: var(--space-md); }
-.actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: center; }
-.actions button { width: auto; }
-.org-table a { text-decoration: none; color: var(--color-link); }
-.org-table a:hover { text-decoration: underline; }
-.perm-name-cell { display: flex; flex-direction: column; gap: 0.3rem; }
-.perm-title { font-weight: 600; color: var(--color-heading); }
-.perm-id-info { font-size: 0.8rem; color: var(--color-text-muted); }
-.plus-btn { background: var(--color-accent-soft); color: var(--color-accent); border: none; border-radius: var(--radius-sm); padding: 0.25rem 0.45rem; font-size: 1.1rem; cursor: pointer; }
-.plus-btn:hover { background: rgba(37, 99, 235, 0.18); }
-.user-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-xs); }
-.user-chip { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 0.45rem 0.6rem; display: flex; justify-content: space-between; gap: var(--space-sm); cursor: grab; }
-.user-chip .meta { font-size: 0.7rem; color: var(--color-text-muted); }
-.empty-role { border: 1px dashed var(--color-border-strong); border-radius: var(--radius-md); padding: var(--space-sm); display: flex; flex-direction: column; gap: var(--space-xs); align-items: flex-start; }
-.icon-btn { background: none; border: none; color: var(--color-text-muted); padding: 0.2rem; border-radius: var(--radius-sm); cursor: pointer; transition: background 0.2s ease, color 0.2s ease; }
-.icon-btn:hover { color: var(--color-heading); background: var(--color-surface-muted); }
-.delete-icon { color: var(--color-danger); }
-.delete-icon:hover { background: var(--color-danger-bg); color: var(--color-danger-text); }
-.matrix-wrapper { margin: var(--space-md) 0; padding: var(--space-lg); }
-.matrix-scroll { overflow-x: auto; }
-.matrix-hint { font-size: 0.8rem; color: var(--color-text-muted); }
-.perm-matrix-grid { display: inline-grid; gap: 0.25rem; align-items: stretch; }
-.perm-matrix-grid > * { padding: 0.35rem 0.45rem; font-size: 0.75rem; }
-.perm-matrix-grid .grid-head { color: var(--color-text-muted); text-transform: uppercase; font-weight: 600; letter-spacing: 0.05em; }
-.perm-matrix-grid .perm-head { display: flex; align-items: flex-end; justify-content: flex-start; padding: 0.35rem 0.45rem; font-size: 0.75rem; }
-.perm-matrix-grid .role-head { display: flex; align-items: flex-end; justify-content: center; }
-.perm-matrix-grid .role-head span { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 0.65rem; }
-.perm-matrix-grid .org-head { display: flex; align-items: flex-end; justify-content: center; }
-.perm-matrix-grid .org-head span { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 0.65rem; }
-.perm-matrix-grid .add-role-head,
-.perm-matrix-grid .add-permission-head { cursor: pointer; }
-.perm-name { font-weight: 600; color: var(--color-heading); padding: 0.35rem 0.45rem; font-size: 0.75rem; }
-.perm-orgs { gap: 0.5rem; }
-.perm-orgs-list { display: flex; flex-wrap: wrap; gap: 0.4rem; }
-.org-pill { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.2rem 0.55rem; border-radius: 999px; background: var(--color-surface-muted); border: 1px solid var(--color-border); font-size: 0.75rem; }
-.pill-x { background: none; border: none; color: var(--color-danger); cursor: pointer; }
-.pill-x:hover { color: var(--color-danger-text); }
-.org-add-wrapper { display: inline-flex; align-items: center; gap: var(--space-xs); position: relative; }
-.add-org-btn { background: var(--color-accent-soft); color: var(--color-accent); border: none; border-radius: var(--radius-sm); padding: 0.2rem 0.4rem; cursor: pointer; }
-.add-org-btn:hover { background: rgba(37, 99, 235, 0.18); }
-.org-add-menu { position: absolute; top: calc(100% + var(--space-xs)); right: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); padding: var(--space-xs); min-width: 220px; z-index: 20; }
-.org-add-list { display: flex; flex-direction: column; gap: var(--space-xs); max-height: 240px; overflow-y: auto; }
-.org-add-item { background: none; border: 1px solid transparent; border-radius: var(--radius-sm); padding: 0.45rem 0.6rem; text-align: left; cursor: pointer; }
-.org-add-item:hover { background: var(--color-surface-muted); border-color: var(--color-border-strong); }
-.org-add-footer { display: flex; justify-content: flex-end; margin-top: var(--space-xs); }
-.org-add-cancel { background: none; border: none; color: var(--color-text-muted); cursor: pointer; }
-.display-text { margin-right: var(--space-xs); }
-.edit-display-btn { padding: 0.1rem 0.2rem; font-size: 0.8rem; }
-.edit-org-btn { padding: 0.1rem 0.2rem; font-size: 0.8rem; margin-left: var(--space-xs); }
-.perm-actions { text-align: center; }
-.small { font-size: 0.9rem; }
-.muted { color: var(--color-text-muted); }
-.error { color: var(--color-danger-text); }
-
-.modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- backdrop-filter: blur(.1rem);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
-}
-
-.modal {
- background: var(--color-surface);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-lg);
- box-shadow: var(--shadow-xl);
- padding: var(--space-lg);
- max-width: 500px;
- width: 90%;
- max-height: 90vh;
- overflow-y: auto;
-}
-
-.modal-title {
- margin: 0 0 var(--space-md) 0;
- font-size: 1.25rem;
- font-weight: 600;
- color: var(--color-heading);
-}
-
-.modal-form {
- display: flex;
- flex-direction: column;
- gap: var(--space-md);
-}
-
-.modal-form label {
- display: flex;
- flex-direction: column;
- gap: var(--space-xs);
- font-weight: 500;
-}
-
-.modal-form input,
-.modal-form textarea {
- padding: var(--space-sm);
- border: 1px solid var(--color-border);
- border-radius: var(--radius-sm);
- background: var(--color-surface);
- color: var(--color-text);
-}
-
-.modal-form input:focus,
-.modal-form textarea:focus {
- outline: none;
- border-color: var(--color-accent);
- box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
-}
-
-.modal-actions {
- display: flex;
- justify-content: flex-end;
- gap: var(--space-sm);
- margin-top: var(--space-lg);
-}
-
-@media (max-width: 720px) {
- .card.surface { padding: var(--space-md); }
- .actions { flex-direction: column; align-items: flex-start; }
- .roles-grid { flex-direction: column; }
- .org-add-menu { left: 0; right: auto; }
-}
diff --git a/frontend/src/admin/AdminDialogs.vue b/frontend/src/admin/AdminDialogs.vue
new file mode 100644
index 0000000..194b9ae
--- /dev/null
+++ b/frontend/src/admin/AdminDialogs.vue
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/admin/AdminOrgDetail.vue b/frontend/src/admin/AdminOrgDetail.vue
new file mode 100644
index 0000000..31001a9
--- /dev/null
+++ b/frontend/src/admin/AdminOrgDetail.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+ {{ selectedOrg.display_name }}
+
+
+
+
+
+
+
Toggle which permissions each role grants.
+
+
+
$emit('onRoleDrop', e, selectedOrg, r)"
+ >
+
+
+
+ - $emit('onUserDragStart', e, u, selectedOrg.uuid)"
+ @click="$emit('openUser', u)"
+ :title="u.uuid"
+ >
+ {{ u.display_name }}
+ {{ u.last_seen ? new Date(u.last_seen).toLocaleDateString() : 'â' }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/admin/AdminOverview.vue b/frontend/src/admin/AdminOverview.vue
new file mode 100644
index 0000000..89ef123
--- /dev/null
+++ b/frontend/src/admin/AdminOverview.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
Organizations
+
+
+
+
+
+
+ | Name |
+ Roles |
+ Members |
+ Actions |
+
+
+
+
+ |
+ {{ o.display_name }}
+
+ |
+ {{ o.roles.length }} |
+ {{ o.roles.reduce((acc,r)=>acc + r.users.length,0) }} |
+
+
+ |
+
+
+
+
+
+
+
Permissions
+
+
+
Toggle which permissions each organization can grant to its members.
+
+
+
+
+
+
+
+ | Permission |
+ Members |
+ Actions |
+
+
+
+
+ |
+
+ {{ p.display_name }}
+
+
+
+ {{ p.id }}
+
+
+ |
+ {{ permissionSummary[p.id]?.userCount || 0 }} |
+
+
+ |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/admin/AdminUserDetail.vue b/frontend/src/admin/AdminUserDetail.vue
new file mode 100644
index 0000000..0ce484c
--- /dev/null
+++ b/frontend/src/admin/AdminUserDetail.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
{{ userDetail.error }}
+
+ Registered Passkeys
+
+
+
+
+
+
+
+
Use the token dialog to register a new credential for the member.
+
+
+
+
+
\ No newline at end of file