diff --git a/artifacts/api-server/src/routes/roles.ts b/artifacts/api-server/src/routes/roles.ts
index 4992cdc1..da1a354f 100644
--- a/artifacts/api-server/src/routes/roles.ts
+++ b/artifacts/api-server/src/routes/roles.ts
@@ -54,7 +54,41 @@ function serializeRole(r: typeof rolesTable.$inferSelect) {
router.get("/roles", requireAdmin, async (_req, res): Promise => {
const rows = await db.select().from(rolesTable).orderBy(rolesTable.name);
- res.json(rows.map(serializeRole));
+ if (rows.length === 0) {
+ res.json([]);
+ return;
+ }
+ // Compute dependency counts so the admin list can show the same inline
+ // "5 users · 2 groups" hint as the Apps/Services/Users/Groups panels
+ // (mirrors the GET /apps and GET /groups behavior). The single-role
+ // /roles/:id/usage endpoint remains the source of truth for the delete
+ // dialog and edit dialog.
+ const roleIds = rows.map((r) => r.id);
+ const userCountRows = await db
+ .select({
+ roleId: userRolesTable.roleId,
+ count: sql`count(*)::int`,
+ })
+ .from(userRolesTable)
+ .where(inArray(userRolesTable.roleId, roleIds))
+ .groupBy(userRolesTable.roleId);
+ const groupCountRows = await db
+ .select({
+ roleId: groupRolesTable.roleId,
+ count: sql`count(*)::int`,
+ })
+ .from(groupRolesTable)
+ .where(inArray(groupRolesTable.roleId, roleIds))
+ .groupBy(groupRolesTable.roleId);
+ const userMap = new Map(userCountRows.map((r) => [r.roleId, r.count]));
+ const groupMap = new Map(groupCountRows.map((r) => [r.roleId, r.count]));
+ res.json(
+ rows.map((r) => ({
+ ...serializeRole(r),
+ userCount: userMap.get(r.id) ?? 0,
+ groupCount: groupMap.get(r.id) ?? 0,
+ })),
+ );
});
router.get("/permissions", requireAdmin, async (_req, res): Promise => {
diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx
index 256d5be5..3753f3b2 100644
--- a/artifacts/tx-os/src/pages/admin.tsx
+++ b/artifacts/tx-os/src/pages/admin.tsx
@@ -5246,6 +5246,27 @@ function RolesPanel() {
{r.descriptionAr}
)}
+ {(() => {
+ const userCount = r.userCount ?? 0;
+ const groupCount = r.groupCount ?? 0;
+ const parts: string[] = [];
+ if (userCount > 0) parts.push(t("admin.roles.usersCount", { count: userCount }));
+ if (groupCount > 0) parts.push(t("admin.roles.groupsCount", { count: groupCount }));
+ if (parts.length === 0) return null;
+ return (
+
+ {parts.map((p, i) => (
+
+ {i > 0 && •}
+ {p}
+
+ ))}
+
+ );
+ })()}