Merge pull request #129 from theta42/feat/super-admin-overview-rename
Add cross-app super admin group; rename Executive page to Overview
This commit is contained in:
@@ -46,7 +46,7 @@ on, just like anyone else's.
|
|||||||
|
|
||||||
A **group** is just a named list of accounts, used to control access. This
|
A **group** is just a named list of accounts, used to control access. This
|
||||||
app has a handful of built-in groups that grant admin powers (e.g. only
|
app has a handful of built-in groups that grant admin powers (e.g. only
|
||||||
people in the `app_sso_admin` group can see the Users/Groups/Directory/Executive
|
people in the `app_sso_admin` group can see the Users/Groups/Directory/Overview
|
||||||
pages at all), but you can also make your own groups for any app you
|
pages at all), but you can also make your own groups for any app you
|
||||||
connect — say, a group listing everyone who should be allowed into your
|
connect — say, a group listing everyone who should be allowed into your
|
||||||
photo server. Once a group exists, add or remove members from the
|
photo server. Once a group exists, add or remove members from the
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ one command).
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<a href="images/dashboard.png" target="_blank"><img src="images/dashboard.png" alt="Executive dashboard" width="49%"></a>
|
<a href="images/dashboard.png" target="_blank"><img src="images/dashboard.png" alt="Overview dashboard" width="49%"></a>
|
||||||
<a href="images/users.png" target="_blank"><img src="images/users.png" alt="User list" width="49%"></a>
|
<a href="images/users.png" target="_blank"><img src="images/users.png" alt="User list" width="49%"></a>
|
||||||
<a href="images/groups.png" target="_blank"><img src="images/groups.png" alt="Groups" width="49%"></a>
|
<a href="images/groups.png" target="_blank"><img src="images/groups.png" alt="Groups" width="49%"></a>
|
||||||
<a href="images/directory.png" target="_blank"><img src="images/directory.png" alt="Directory & inventory" width="49%"></a>
|
<a href="images/directory.png" target="_blank"><img src="images/directory.png" alt="Directory & inventory" width="49%"></a>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ const router = require('express').Router();
|
|||||||
const permission = require('../utils/permission');
|
const permission = require('../utils/permission');
|
||||||
const metrics = require('../utils/metrics');
|
const metrics = require('../utils/metrics');
|
||||||
|
|
||||||
// /api/metrics/executive
|
// /api/metrics/overview
|
||||||
router.get('/executive', async (req, res, next) => {
|
router.get('/overview', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
await permission.byGroup(req.user, ['app_sso_admin']);
|
await permission.byGroup(req.user, ['app_sso_admin']);
|
||||||
|
|
||||||
|
|||||||
@@ -48,13 +48,14 @@ router.get('/tos', async function(req, res, next) {
|
|||||||
|
|
||||||
// Admin dashboard (stats + recent/inactive users) and Notifications
|
// Admin dashboard (stats + recent/inactive users) and Notifications
|
||||||
// (broadcast + history) merged into one page.
|
// (broadcast + history) merged into one page.
|
||||||
router.get('/executive', function(req, res) {
|
router.get('/overview', function(req, res) {
|
||||||
res.render('executive', {...values});
|
res.render('overview', {...values});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/admin', (req, res) => res.redirect(301, '/executive'));
|
router.get('/admin', (req, res) => res.redirect(301, '/overview'));
|
||||||
router.get('/notifications', (req, res) => res.redirect(301, '/executive'));
|
router.get('/notifications', (req, res) => res.redirect(301, '/overview'));
|
||||||
router.get('/dashboard', (req, res) => res.redirect(301, '/executive'));
|
router.get('/dashboard', (req, res) => res.redirect(301, '/overview'));
|
||||||
|
router.get('/executive', (req, res) => res.redirect(301, '/overview'));
|
||||||
|
|
||||||
router.get('/directory', function(req, res) {
|
router.get('/directory', function(req, res) {
|
||||||
res.render('directory', {...values});
|
res.render('directory', {...values});
|
||||||
|
|||||||
@@ -78,11 +78,12 @@ router.get('/me', async function(req, res, next){
|
|||||||
|
|
||||||
// The shared client framework gates the UI on a single effective-rights
|
// The shared client framework gates the UI on a single effective-rights
|
||||||
// flag (the OIDC-client apps send the same key). Here "admin" means
|
// flag (the OIDC-client apps send the same key). Here "admin" means
|
||||||
// membership in app_sso_admin; group-level gating still reads memberOf.
|
// membership in app_sso_admin or the cross-app app_super_admin group;
|
||||||
|
// group-level gating still reads memberOf.
|
||||||
const groups = (user.memberOf || []).map(function(dn){
|
const groups = (user.memberOf || []).map(function(dn){
|
||||||
return String(dn).split(',')[0].replace(/^cn=/i, '');
|
return String(dn).split(',')[0].replace(/^cn=/i, '');
|
||||||
});
|
});
|
||||||
user.isAdmin = groups.includes('app_sso_admin');
|
user.isAdmin = groups.includes('app_sso_admin') || groups.includes(permission.SUPER_ADMIN_GROUP);
|
||||||
|
|
||||||
return res.json(user);
|
return res.json(user);
|
||||||
}catch(error){
|
}catch(error){
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
const {Group} = require('../models/group_ldap');
|
const {Group} = require('../models/group_ldap');
|
||||||
|
|
||||||
|
const SUPER_ADMIN_GROUP = 'app_super_admin';
|
||||||
|
|
||||||
let byGroup = async function(user, groups, ownerOf){
|
let byGroup = async function(user, groups, ownerOf){
|
||||||
|
try{
|
||||||
|
let superAdmin = await Group.get(SUPER_ADMIN_GROUP);
|
||||||
|
if(superAdmin.member.includes(user.dn)) return true
|
||||||
|
}catch(error){
|
||||||
|
// group not found, continue checking
|
||||||
|
}
|
||||||
|
|
||||||
for(let group of groups){
|
for(let group of groups){
|
||||||
try{
|
try{
|
||||||
group = await Group.get(group);
|
group = await Group.get(group);
|
||||||
@@ -28,4 +37,4 @@ let byGroup = async function(user, groups, ownerOf){
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {byGroup};
|
module.exports = {byGroup, SUPER_ADMIN_GROUP};
|
||||||
|
|||||||
+4
-4
@@ -38,9 +38,9 @@ module.exports = {
|
|||||||
// app-base.js, which reveals .group-required-<cn> for each group the user is
|
// app-base.js, which reveals .group-required-<cn> for each group the user is
|
||||||
// in (plus the synthetic `admin` group when user/me reports isAdmin).
|
// in (plus the synthetic `admin` group when user/me reports isAdmin).
|
||||||
nav: [
|
nav: [
|
||||||
{href: '/users', icon: 'fa-solid fa-users', label: 'Users', groups: ['app_sso_admin']},
|
{href: '/users', icon: 'fa-solid fa-users', label: 'Users', groups: ['app_sso_admin', 'admin']},
|
||||||
{href: '/groups', icon: 'fa-solid fa-users-viewfinder', label: 'Groups', groups: ['app_sso_admin']},
|
{href: '/groups', icon: 'fa-solid fa-users-viewfinder', label: 'Groups', groups: ['app_sso_admin', 'admin']},
|
||||||
{href: '/directory', icon: 'fa-solid fa-server', label: 'Directory', groups: ['app_sso_admin', 'app_sso_directory_admin']},
|
{href: '/directory', icon: 'fa-solid fa-server', label: 'Directory', groups: ['app_sso_admin', 'app_sso_directory_admin', 'admin']},
|
||||||
{href: '/executive', icon: 'fa-solid fa-gauge-high', label: 'Executive', groups: ['app_sso_admin']},
|
{href: '/overview', icon: 'fa-solid fa-gauge-high', label: 'Overview', groups: ['app_sso_admin', 'admin']},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
app.auth.forceLogin(['app_sso_admin', 'app_sso_directory_admin']);
|
app.auth.forceLogin(['app_sso_admin', 'app_sso_directory_admin', 'admin']);
|
||||||
|
|
||||||
// --- Resource modal tab content, built once. Populated via .val() in
|
// --- Resource modal tab content, built once. Populated via .val() in
|
||||||
// openAddModal/openEditModal AFTER app.modal.open() has (re)built the
|
// openAddModal/openEditModal AFTER app.modal.open() has (re)built the
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.auth.forceLogin('app_sso_admin');
|
app.auth.forceLogin(['app_sso_admin', 'admin']);
|
||||||
|
|
||||||
$(document).ready(async function(){
|
$(document).ready(async function(){
|
||||||
userlist = (await app.user.list()).results;
|
userlist = (await app.user.list()).results;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<%- include('top') %>
|
<%- include('top') %>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
app.auth.forceLogin('app_sso_admin');
|
app.auth.forceLogin(['app_sso_admin', 'admin']);
|
||||||
|
|
||||||
// ── Overview (stats, recent signups, inactive users) ────────────────────
|
// ── Overview (stats, recent signups, inactive users) ────────────────────
|
||||||
async function loadDashboard() {
|
async function loadDashboard() {
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
async function loadMetrics() {
|
async function loadMetrics() {
|
||||||
try {
|
try {
|
||||||
const data = await app.api.get('metrics/executive');
|
const data = await app.api.get('metrics/overview');
|
||||||
if (data && data.results) {
|
if (data && data.results) {
|
||||||
const renderList = (items, id) => {
|
const renderList = (items, id) => {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h4 class="mb-0"><i class="fa-solid fa-gauge-high"></i> Executive Dashboard</h4>
|
<h4 class="mb-0"><i class="fa-solid fa-gauge-high"></i> Overview</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
(async function(){
|
(async function(){
|
||||||
await app.auth.forceLogin('app_sso_admin');
|
await app.auth.forceLogin(['app_sso_admin', 'admin']);
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
renderUsers();
|
renderUsers();
|
||||||
|
|||||||
Reference in New Issue
Block a user