Skip to content

Group view #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 23, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -9,9 +9,9 @@
* increase size of input field in users page
* add a readonly input field under email for showing the email domain*
* Add "custom_users" key to config file, to be used with various scripts
* Separate global overview from individual group view in admin group management
* Allow accents in user's first and last names


## 1.4.31 (2024-09-27)

* Fix 'Admin' button in 'My projects' page for administrators
1 change: 1 addition & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -298,6 +298,7 @@ app.post('/group/:id', groups);
app.put('/group/:id', groups);
app.delete('/group/:id', groups);
app.get('/group/:id', groups);
app.get('/group/:id/users', groups);
app.get('/user', users);
app.get('/database', database);
app.get('/pending/database', database);
144 changes: 144 additions & 0 deletions manager2/src/app/admin/group/group.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<div class="col-sm-6">
<a routerLink="/admin/group" class="btn btn-primary">Back to groups</a>
</div>

<div class="alert alert-danger" *ngIf="del_err_msg">{{ del_err_msg }}</div>

<div class="card bg-light">
<div class="card bg-light card-header">
<div class="row">
<div class="col-sm-9">
<h4>
Group <strong>{{ group.name }}</strong>
</h4>
</div>
<div class="col-sm-3">
<app-my-delete-confirm
[onConfirm]="deleteGroup"
></app-my-delete-confirm>
</div>
</div>
</div>
<div class="card-body">
<form role="form" class="user-form form-horizontal form-register">
<div class="form-group row">
<div class="col-sm-3">
<label for="group_id" class="col-form-label">Owner</label>
<input
type="text"
placeholder="Owner"
id="group_id"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="group.owner"
class="form-control"
/>
</div>
<div class="col-sm-9">
<label for="group_desc" class="col-form-label">Description</label>
<input
type="text"
placeholder="Description"
id="group_desc"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="group.description"
class="form-control"
/>
</div>
</div>
<button
type="button"
class="p-button p-button-sm p-button-primary"
(click)="updateGroup()"
>
Update
</button>
<div class="form-group"></div>
</form>
</div>
</div>

<div class="card bg-light">
<div class="card-header">
<h3>Group tags</h3>
</div>
<div class="card-body">
<app-tag [tag]="group.tags" [user]="group.name" kind="group"></app-tag>
</div>
</div>

<div class="card bg-light">
<div class="card-header">
<h3>Projects associated with {{ group.name }} :</h3>
</div>
<div class="card-body">
<div class="table">
<table class="table table-striped">
<thead>
<th>Project</th>
<th>Owner</th>
</thead>
<tbody>
<tr *ngFor="let project of projects">
<td>
<a routerLink="/admin/project/{{ project.id }}">
<span class="p-button p-button-sm p-button-primary">
{{ project.id }}
</span>
</a>
</td>
<td>
<a routerLink="/user/{{ project.owner }}">
<span class="p-button p-button-sm p-button-primary">
{{ project.owner }}
</span>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

<div class="card bg-light">
<div class="card-header">
<h3>Users in {{ group.name }} :</h3>
</div>
<div class="card-body">
<div class="alert alert-info" *ngIf="msg">{{ msg }}</div>
<div class="alert alert-danger" *ngIf="err_msg">{{ err_msg }}</div>
<div class="table-responsive table-striped">
<p-table
#dtu
[value]="users"
[paginator]="true"
[rows]="10"
[showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]"
>
<ng-template pTemplate="header">
<tr>
<th>User</th>
<th>Email</th>
<th>In associated project?</th>
<th>Main group?</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr *ngFor="let user of users">
<td>
<a routerLink="/user/{{ user.uid }}">
<span class="p-button p-button-sm p-button-primary">
{{ user.uid }}
</span>
</a>
</td>
<td>{{ user.email }}</td>
<td><span *ngIf="user.temp?.authorized">x</span></td>
<td><span *ngIf="user.group == group.name">x</span></td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>
93 changes: 93 additions & 0 deletions manager2/src/app/admin/group/group.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Component, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { Group, GroupsService } from "../groups/groups.service";
import { Project, ProjectsService } from "../projects/projects.service";
import { User } from "../../user/user.service";
import { Table } from "primeng/table";

@Component({
selector: "app-groups",
templateUrl: "./group.component.html"
// styleUrls: ['./group.component.css']
})
export class GroupComponent implements OnInit {
@ViewChild("dtg") tableGroups: Table;
@ViewChild("dtu") tableUsers: Table;

msg: string;
err_msg: string;
del_err_msg: string;
group: Group;
projects: Project[];
users: User[];

constructor(
private route: ActivatedRoute,
private router: Router,
private groupsService: GroupsService,
private projectsService: ProjectsService
) {
this.group = new Group();
this.projects = null;
this.users = null;
}

ngOnDestroy(): void {}

ngAfterViewInit(): void {}

ngOnInit() {
this.deleteGroup = this.deleteGroup.bind(this);
this.route.params.subscribe((params) => {
let group_name = params.id;
this.groupsService.get(group_name).subscribe(
(group) => (this.group = group),
(err) => console.log("failed to get group")
);
this.projectsService.getProjectsInGroup(group_name).subscribe(
(project_list) => (this.projects = project_list),
(err) => console.log("failed to get projects in group")
);
this.groupsService.getUsers(group_name).subscribe(
(user_list) => {
this.users = user_list;
for (var i = 0; i < user_list.length; i++) {
var is_authorized = false;
if (user_list[i].projects) {
for (var j = 0; j < this.projects.length; j++) {
if (user_list[i].projects.indexOf(this.projects[j].id) >= 0) {
is_authorized = true;
break;
}
}
}
this.users[i].temp = {
...this.users[i].temp,
authorized: is_authorized
};
}
},
(err) => console.log("failed to get group's users")
);
});
}

deleteGroup() {
this.groupsService.delete(this.group.name).subscribe(
(resp) =>
this.router.navigate(["/admin/group"], {
queryParams: { deleted: "ok" }
}),
(err) => (this.del_err_msg = err.error.message)
);
}

updateGroup() {
this.msg = "";
this.err_msg = "";
this.groupsService.update(this.group).subscribe(
(resp) => (this.msg = "Group updated"),
(err) => (this.err_msg = err.error.message)
);
}
}
85 changes: 4 additions & 81 deletions manager2/src/app/admin/groups/groups.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<div *ngIf="notification" class="alert alert-success">{{notification}}</div>

<div class="card bg-light">
<div class="card-header">
<h3 class="panel-title">Group creation</h3>
@@ -23,85 +25,6 @@ <h3 class="panel-title">Group creation</h3>
</div>
</div>


<div class="row">
<div class="alert alert-success" *ngIf="rm_grp_msg_ok">{{rm_grp_msg_ok}}</div>
</div>
<div *ngIf="selectedGroup">
<div class="card bg-light">
<div class="card-header">
<h3>Group tags</h3>
</div>
<div class="card-body">
<app-tag [tag]="selectedGroup.tags" [user]="selectedGroup.name" kind="group"></app-tag>
</div>
</div>

<div class="card bg-light">
<div class="card-header">
<h3>Users in group {{selectedGroup.name}}</h3>
</div>
<div class="card-body">
<div class="alert alert-danger" *ngIf="rm_grp_err_msg">{{rm_grp_err_msg}}</div>
<div class="alert alert-info" *ngIf="msg">{{msg}}</div>
<form role="form" class="user-form form-horizontal form-register">
<div class="form-group">
<label for="group_id" class="col-sm-2 col-form-label">Owner</label>
<div class="col-sm-3">
<input placeholder="Owner" id="group_id" type="text" [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedGroup.owner" class="form-control"/>
</div>
<label for="group_desc" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-6">
<input placeholder="Description" id="group_desc" type="text" [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedGroup.description" class="form-control"/>
</div>
<button type="button" class="p-button p-button-sm p-button-secondary" (click)="updateGroup()">Update</button>
</div>
<div class="form-group">

</div>
</form>
<h5>Projects associated with this group :</h5>
<div class="table">
<table class="table table-striped">
<thead>
<th>Project</th><th>Owner</th>
</thead>
<tbody>
<tr *ngFor="let project of projects">
<td>{{project.id}}</td>
<td>{{project.owner}}</td>
</tr>
</tbody>
</table>
</div>
<br>
<h5>Group members</h5>
<div class="table-responsive table-striped">
<p-table #dtu [value]="users"
[paginator]="true"
[rows]="10"
[showCurrentPageReport]="true"
[rowsPerPageOptions]="[10,25,50]"
>
<ng-template pTemplate="header">
<tr><th>User</th><th>Email</th><th>In associated project?</th><th>Main group?</th></tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr>
<td><a routerLink="/user/{{user.uid}}"><span class="p-button p-button-sm p-button-primary">{{user.uid}}</span></a></td>
<td>{{user.email}}</td>
<td><span *ngIf="user.temp?.authorized">x</span></td>
<td><span *ngIf="user.group == selectedGroup.name">x</span></td>
</tr>
</ng-template>
</p-table>
</div>
<button class="p-button p-button-sm p-button-danger" (click)="delete_group()">Delete</button>
</div>
</div>
</div>


<div class="card bg-light">
<div class="card-header">
<h3>Groups</h3>
@@ -136,9 +59,9 @@ <h3>Groups</h3>
</ng-template>
<ng-template pTemplate="body" let-group>
<tr>
<td (click)="show_group_users(group)" style="cursor: pointer;"><span class="p-button p-button-sm p-button-primary">{{group.name}}</span></td>
<td><a routerLink="/admin/group/{{group.name}}"><span class="p-button p-button-sm p-button-primary">{{group.name}}</span></a></td>
<td>{{group.gid}}</td>
<td>{{group.owner}}</td>
<td><a *ngIf="group.owner" routerLink="/user/{{group.owner}}"><span class="p-button p-button-sm p-button-primary">{{group?.owner}}</span></a></td>
<td>{{group.description}}</td>
<td>{{group.tags ? group.tags.join(", ") : ""}}</td>
</tr>
118 changes: 20 additions & 98 deletions manager2/src/app/admin/groups/groups.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Group, GroupsService } from './groups.service';
import { Project, ProjectsService } from 'src/app/admin/projects/projects.service';
import { ActivatedRoute } from '@angular/router';
import { User } from '../../user/user.service';

import { Table } from 'primeng/table';

@Component({
@@ -14,26 +13,19 @@ export class GroupsComponent implements OnInit {
@ViewChild('dtg') tableGroups: Table;
@ViewChild('dtu') tableUsers: Table;

notification: string
success_msg: string
err_msg: string
rm_grp_msg_ok: string
rm_grp_err_msg: string
msg: string

selectedGroup: Group
new_group: Group

projects: Project[]
groups: Group[]
users: User[]

constructor(
private groupsService: GroupsService,
private projectsService: ProjectsService
private route: ActivatedRoute,
private groupsService: GroupsService
) {
this.selectedGroup = null;
this.new_group = new Group();
this.projects = [];
this.groups = [];
this.users = [];
}
@@ -47,103 +39,33 @@ export class GroupsComponent implements OnInit {
}

ngOnInit() {
this.groupsService.list().subscribe(
resp => {
this.groups = resp;
},
err => console.log('failed to get groups')
)
this.route.queryParams
.subscribe(params => {
if (params.deleted == 'ok') {
this.notification = 'Group was deleted successfully';
};
});
this.listGroups();
}

addGroup(){
if (this.new_group.name === '') {
return;
}
addGroup() {
this.notification = '';
if (this.new_group.name === '') { return; }
this.err_msg = '';
this.success_msg = '';
this.groupsService.add(this.new_group).subscribe(
resp => {
this.success_msg = 'Group was created';
this.groupsService.list().subscribe(
resp => {
this.groups = resp;
},
err => console.log('failed to get groups')
)
this.listGroups();
},
err => {
this.success_msg = '';
this.err_msg = err.error.message;
}
err => this.err_msg = err.error.message
)
}

delete_group(){
this.groupsService.delete(this.selectedGroup.name).subscribe(
resp => {
this.groupsService.list().subscribe(
resp => {
this.groups = resp;
},
err => console.log('failed to get groups')
);
this.selectedGroup = null;
},
err => {
this.rm_grp_err_msg = err.error.message;
}
)
}

updateGroup(){
this.groupsService.update(this.selectedGroup).subscribe(
resp => {
this.msg = 'Group updated';
this.rm_grp_err_msg = '';
this.groupsService.list().subscribe(
resp => {
this.groups = resp;
},
err => console.log('failed to get groups')
)
},
err => {
this.msg = '';
this.rm_grp_err_msg = err.error.message;
}
)
}

show_group_users(input_group: any) {
const group: Group = this.groupsService.mapToGroup(input_group);
this.msg = '';
this.rm_grp_err_msg = '';
this.rm_grp_msg_ok = '';
this.selectedGroup = group;
this.projectsService.getProjectsInGroup(group.name).subscribe(
resp => {
this.projects = resp;
this.groupsService.get(group.name).subscribe(
user_list => {
this.users = user_list;
for(var i = 0; i < user_list.length; i++){
var is_authorized = false;
if(user_list[i].projects){
for(var j = 0; j < this.projects.length; j++){
if(user_list[i].projects.indexOf(this.projects[j].id) >= 0){
is_authorized = true;
break;
}
}
}
this.users[i].temp = { ...this.users[i].temp, 'authorized': is_authorized };
}
},
err => console.log('failed to get users in group', group)
)
},
err => console.log('failed to get projects in group')
listGroups() {
this.groupsService.list().subscribe(
resp => (this.groups = resp),
err => console.log('failed to get groups')
)
}

}
20 changes: 18 additions & 2 deletions manager2/src/app/admin/groups/groups.service.ts
Original file line number Diff line number Diff line change
@@ -63,15 +63,15 @@ export class GroupsService {
}));
}

get(groupId: string): Observable<User[]> {
getUsers(group_name: string): Observable<User[]> {
// let user = this.authService.profile;
let httpOptions = {
//headers: new HttpHeaders({
// 'x-api-key': user.apikey
//}),
};
return this.http.get(
environment.apiUrl + '/group/' + groupId,
environment.apiUrl + '/group/' + group_name + '/users',
httpOptions
).pipe(map((response: any[]) => {
return response.map(item => {
@@ -80,6 +80,22 @@ export class GroupsService {
}));
}

get(group_name: string): Observable<Group> {
// let user = this.authService.profile;
let httpOptions = {
//headers: new HttpHeaders({
// 'x-api-key': user.apikey
//}),
};
return this.http
.get(environment.apiUrl + '/group/' + group_name, httpOptions)
.pipe(
map((response) => {
return this.mapToGroup(response);
})
);
}

update(group: Group) {
// let user = this.authService.profile;
let httpOptions = {
12 changes: 10 additions & 2 deletions manager2/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -9,8 +9,9 @@ import { RegisterComponent } from './auth/register/register.component';
import { LogoutComponent } from './auth/logout/logout.component';
import { TpsComponent } from './tps/tps.component';
import { ProjectComponent } from './project/project.component';
import { DatabasesComponent as AdminDatabaseComponent} from './admin/databases/databases.component';
import { GroupsComponent as AdminGroupComponent} from './admin/groups/groups.component';
import { DatabasesComponent as AdminDatabaseComponent } from './admin/databases/databases.component';
import { GroupsComponent as AdminGroupsComponent } from './admin/groups/groups.component';
import { GroupComponent as AdminGroupComponent } from './admin/group/group.component';
import { LogsComponent as AdminLogComponent} from './admin/logs/logs.component';
import { MessagesComponent as AdminMessageComponent} from './admin/messages/messages.component';
import { ProjectsComponent as AdminProjectsComponent} from './admin/projects/projects.component';
@@ -66,6 +67,13 @@ const routes: Routes = [
},
{
path: 'admin/group',
component: AdminGroupsComponent,
canActivate: [
AdminAuthGuard
]
},
{
path: 'admin/group/:id',
component: AdminGroupComponent,
canActivate: [
AdminAuthGuard
2 changes: 2 additions & 0 deletions manager2/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import { ProjectComponent } from './project/project.component';
import { UsersComponent, MyStatusFilterPipe } from './admin/users/users.component';
import { AdminStatComponent} from './admin/stats/stats.component';
import { GroupsComponent } from './admin/groups/groups.component';
import { GroupComponent } from './admin/group/group.component';
import { ProjectsComponent as AdminProjectsComponent} from './admin/projects/projects.component';
import { ProjectComponent as AdminProjectComponent} from './admin/project/project.component';
import { MessagesComponent } from './admin/messages/messages.component';
@@ -84,6 +85,7 @@ export class SentryErrorHandler implements ErrorHandler {
UsersComponent,
AdminStatComponent,
GroupsComponent,
GroupComponent,
AdminProjectsComponent,
AdminProjectComponent,
UserProjectsComponent,
10 changes: 4 additions & 6 deletions manager2/src/app/utils/tag/tag.component.html
Original file line number Diff line number Diff line change
@@ -11,11 +11,9 @@
<button class="p-button p-button-sm p-button-primary" (click)="addTag()" >Add</button>
</div>
</div>
<div class="row">
<span *ngFor="let tag of tags" class="tag label label-primary">{{tag}} <small class="oi oi-delete" title="remove" (click)="deleteTag(tag)"></small></span>
</div>
<div class="row">
<span *ngFor="let tag of tags" class="tag label label-primary">{{tag}} <small class="oi oi-delete" title="remove" (click)="deleteTag(tag)"></small></span>
</div>

<div class="row">
<button class="p-button p-button-sm p-button-primary" (click)="updateTags()" >Update</button>
<button class="p-button p-button-sm p-button-primary" (click)="updateTags()">Update</button>
</div>
</div>
39 changes: 38 additions & 1 deletion routes/groups.js
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ const sansrv = require('../core/sanitize.service.js');
const grpsrv = require('../core/group.service.js');
const rolsrv = require('../core/role.service.js');

router.get('/group/:id', async function(req, res){
router.get('/group/:id/users', async function(req, res) {
if(! req.locals.logInfo.is_logged) {
res.status(401).send({message: 'Not authorized'});
return;
@@ -52,6 +52,43 @@ router.get('/group/:id', async function(req, res){
res.end();
});

router.get('/group/:id', async function (req, res) {
if (!req.locals.logInfo.is_logged) {
res.status(401).send({ message: 'Not authorized' });
return;
}
if (!sansrv.sanitizeAll([req.params.id])) {
res.status(403).send({ message: 'Invalid parameters' });
return;
}
let user = null;
let isadmin = false;
try {
user = await dbsrv.mongo_users().findOne({
_id: req.locals.logInfo.id,
});
isadmin = await rolsrv.is_admin(user);
} catch (e) {
logger.error(e);
res.status(404).send({ message: 'User session not found' });
return;
}
if (!user) {
res.status(404).send({ message: 'User not found' });
return;
}
if (!isadmin) {
res.status(401).send({ message: 'Not authorized' });
return;
}
const group = await dbsrv.mongo_groups().findOne({ 'name': req.params.id });
if (!group) {
res.status(404).send({ message: 'Group ' + req.params.id + ' not found' });
return;
}
res.send(group);
});

router.delete('/group/:id', async function(req, res){
if(! req.locals.logInfo.is_logged) {
res.status(401).send({message: 'Not authorized'});