diff --git a/dashboard/src/lib/ActionMenu.svelte b/dashboard/src/lib/ActionMenu.svelte
new file mode 100644
index 0000000..ea8058e
--- /dev/null
+++ b/dashboard/src/lib/ActionMenu.svelte
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+
+
+ {#if open}
+
+ {/if}
+
+
+
diff --git a/dashboard/src/routes/users/+page.svelte b/dashboard/src/routes/users/+page.svelte
index 88fbbeb..e049dbb 100644
--- a/dashboard/src/routes/users/+page.svelte
+++ b/dashboard/src/routes/users/+page.svelte
@@ -16,6 +16,7 @@
import { currentUser } from '$lib/auth';
import RoleChip from '$lib/RoleChip.svelte';
import ConfirmModal from '$lib/ConfirmModal.svelte';
+ import ActionMenu from '$lib/ActionMenu.svelte';
import { generatePassword } from '$lib/password-gen';
const me = $derived($currentUser);
@@ -70,8 +71,7 @@
username: string;
email: string;
instance_role: InstanceRole;
- is_active: boolean;
- }>({ username: '', email: '', instance_role: 'admin', is_active: true });
+ }>({ username: '', email: '', instance_role: 'admin' });
let editPending = $state(false);
let editError = $state(null);
@@ -161,8 +161,7 @@
editForm = {
username: row.username,
email: row.email ?? '',
- instance_role: row.instance_role,
- is_active: row.is_active
+ instance_role: row.instance_role
};
editError = null;
}
@@ -176,7 +175,6 @@
username?: string;
email?: string | null;
instance_role?: InstanceRole;
- is_active?: boolean;
} = {};
if (editForm.username !== editTarget.username) patch.username = editForm.username;
if ((editTarget.email ?? '') !== editForm.email.trim()) {
@@ -185,7 +183,6 @@
if (editForm.instance_role !== editTarget.instance_role) {
patch.instance_role = editForm.instance_role;
}
- if (editForm.is_active !== editTarget.is_active) patch.is_active = editForm.is_active;
try {
const updated = await api.admins.update(editTarget.id, patch);
admins = admins
@@ -350,19 +347,22 @@
{shortDate(row.created_at)}
{relative(row.last_login_at)}
-
-
- {#if canDelete(row)}
-
- {/if}
+
openEdit(row) },
+ {
+ label: row.is_active ? 'Deactivate' : 'Reactivate',
+ onClick: () => toggleActive(row)
+ },
+ {
+ label: 'Delete',
+ danger: true,
+ disabled: !canDelete(row),
+ onClick: () => openDelete(row)
+ }
+ ]}
+ />
{/each}
@@ -514,11 +514,6 @@
{/if}
-
{#if editError}
{editError}
{/if}
@@ -682,7 +677,7 @@
}
.row {
display: grid;
- grid-template-columns: 1.3fr 0.7fr 1.5fr 0.9fr 0.8fr 0.9fr 1.6fr;
+ grid-template-columns: 1.3fr 0.7fr 1.5fr 0.9fr 0.8fr 0.9fr 2.5rem;
align-items: center;
gap: 0.75rem;
padding: 0.7rem 1rem;
@@ -737,29 +732,6 @@
.actions-col {
display: flex;
justify-content: flex-end;
- gap: 0.25rem;
- flex-wrap: wrap;
- }
- .row-action {
- background: transparent;
- color: #cbd5e1;
- border: 1px solid #334155;
- padding: 0.25rem 0.55rem;
- border-radius: 0.25rem;
- font-size: 0.75rem;
- cursor: pointer;
- }
- .row-action:hover {
- background: #1e293b;
- color: #e2e8f0;
- }
- .danger-link {
- color: #fca5a5;
- border-color: #7f1d1d;
- }
- .danger-link:hover {
- background: #450a0a;
- color: #fecaca;
}
button.primary {
@@ -923,21 +895,6 @@
color: #cbd5e1;
}
- .toggle {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
- font-size: 0.85rem;
- color: #cbd5e1;
- }
- .toggle :global(input[type='checkbox']) {
- margin-right: 0.4rem;
- }
- .toggle small {
- color: #64748b;
- font-size: 0.72rem;
- margin-left: 1.3rem;
- }
.token-row {
display: flex;