Added user management and also fixed the bug that Gabe pointed out with the availability on tv shows

This commit is contained in:
tidusjar 2019-03-22 21:22:09 +00:00
commit 9fcc3120bd
8 changed files with 148 additions and 178 deletions

View file

@ -1,4 +1,4 @@
version: 3.0.{build} version: 4.0.{build}
configuration: Release configuration: Release
os: Visual Studio 2017 os: Visual Studio 2017
environment: environment:

View file

@ -140,7 +140,7 @@
<button mat-raised-button class="btn-green btn-spacing" *ngIf="tv.available"> {{ <button mat-raised-button class="btn-green btn-spacing" *ngIf="tv.available"> {{
'Common.Available' | translate }}</button> 'Common.Available' | translate }}</button>
<button mat-raised-button class="btn-orange btn-spacing" *ngIf="tv.partlyAvailable"> {{ <button mat-raised-button class="btn-orange btn-spacing" *ngIf="tv.partlyAvailable && !tv.available"> {{
'Common.PartlyAvailable' | translate }}</button> 'Common.PartlyAvailable' | translate }}</button>
<span *ngIf="tv.available"> <span *ngIf="tv.available">

View file

@ -21,7 +21,6 @@ export interface IUser {
episodeRequestQuota: IRemainingRequests | null; episodeRequestQuota: IRemainingRequests | null;
movieRequestQuota: IRemainingRequests | null; movieRequestQuota: IRemainingRequests | null;
musicRequestQuota: IRemainingRequests | null; musicRequestQuota: IRemainingRequests | null;
checked: boolean;
} }
export interface IUserQualityProfiles { export interface IUserQualityProfiles {

View file

@ -29,6 +29,7 @@ export class MyNavComponent {
public navItems: INavBar[] = [ public navItems: INavBar[] = [
{ name: "NavigationBar.Discover", icon: "find_replace", link: "/discover" }, { name: "NavigationBar.Discover", icon: "find_replace", link: "/discover" },
{ name: "NavigationBar.Requests", icon: "list", link: "/requests" }, { name: "NavigationBar.Requests", icon: "list", link: "/requests" },
{ name: "NavigationBar.UserManagement", icon: "account_circle", link: "/usermanagement" },
{ name: "NavigationBar.Settings", icon: "settings", link: "/Settings/About" }, { name: "NavigationBar.Settings", icon: "settings", link: "/Settings/About" },
] ]

View file

@ -5,7 +5,7 @@
<button mat-menu-item [routerLink]="['/Settings/Customization']">Customization</button> <button mat-menu-item [routerLink]="['/Settings/Customization']">Customization</button>
<button mat-menu-item [routerLink]="['/Settings/LandingPage']">Landing Page</button> <button mat-menu-item [routerLink]="['/Settings/LandingPage']">Landing Page</button>
<button mat-menu-item [routerLink]="['/Settings/Issues']">Issues</button> <button mat-menu-item [routerLink]="['/Settings/Issues']">Issues</button>
<button mat-menu-item [routerLink]="['/Settings/Usermanagement']">User Management</button> <button mat-menu-item [routerLink]="['/Settings/UserManagement']">User Management</button>
<button mat-menu-item [routerLink]="['/Settings/Authentication']">Authentication</button> <button mat-menu-item [routerLink]="['/Settings/Authentication']">Authentication</button>
<button mat-menu-item [routerLink]="['/Settings/Vote']">Vote</button> <button mat-menu-item [routerLink]="['/Settings/Vote']">Vote</button>
</mat-menu> </mat-menu>

View file

@ -66,7 +66,6 @@ export class UserManagementUserComponent implements OnInit {
password: "", password: "",
userName: "", userName: "",
userType: UserType.LocalUser, userType: UserType.LocalUser,
checked: false,
hasLoggedIn: false, hasLoggedIn: false,
lastLoggedIn: new Date(), lastLoggedIn: new Date(),
episodeRequestLimit: 0, episodeRequestLimit: 0,

View file

@ -2,139 +2,121 @@
<button type="button" class="btn btn-success-outline" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</button> <button type="button" mat-raised-button color="primary" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</button>
<button type="button" style="float:right;" class="btn btn-primary-outline"(click)="showBulkEdit = !showBulkEdit" [disabled]="!hasChecked()">Bulk Edit</button> <button type="button" style="float:right;" mat-raised-button color="primary" (click)="showBulkEdit = !showBulkEdit" [disabled]="this.selection.selected.length <= 0">Bulk Edit</button>
<!-- Table --> <table *ngIf="dataSource" mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<table class="table table-striped table-hover table-responsive table-condensed table-usermanagement">
<thead> <ng-container matColumnDef="select">
<tr> <th mat-header-cell *matHeaderCellDef>
<th style="width:1%"> <mat-checkbox (change)="$event ? masterToggle() : null"
<a> [checked]="selection.hasValue() && isAllSelected()"
<td class="checkbox" data-label="Select:"> [indeterminate]="selection.hasValue() && !isAllSelected()"
<input id="all" type="checkbox" ng-checked="checkAll" (change)="checkAllBoxes()"> [aria-label]="checkboxLabel()">
<label for="all"></label> </mat-checkbox>
</td> </th>
</a> <td mat-cell *matCellDef="let row">
</th> <mat-checkbox (click)="$event.stopPropagation()"
<th class="active" (click)="setOrder('userName', $event)"> (change)="$event ? selection.toggle(row) : null"
<a> [checked]="selection.isSelected(row)"
Username [aria-label]="checkboxLabel(row)">
</a> </mat-checkbox>
<span *ngIf="order === 'userName'"> </td>
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> </ng-container>
</span>
</th> <ng-container matColumnDef="username">
<th (click)="setOrder('alias', $event)"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Username </th>
<a> <td mat-cell *matCellDef="let element"> {{element.userName}} </td>
Alias </ng-container>
</a>
<span *ngIf="order === 'alias'"> <ng-container matColumnDef="alias">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
</span> <td mat-cell *matCellDef="let element"> {{element.alias}} </td>
</th> </ng-container>
<th (click)="setOrder('emailAddress', $event)">
<a> <ng-container matColumnDef="email">
Email <th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th>
</a> <td mat-cell *matCellDef="let element"> {{element.emailAddress}} </td>
<span *ngIf="order === 'emailAddress'"> </ng-container>
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>
<span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <ng-container matColumnDef="remainingRequests">
</span> <th mat-header-cell *matHeaderCellDef> Requests Remaining </th>
</th> <td mat-cell *matCellDef="let u">
<th> <div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.hasLimit">
Roles {{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }}
</th> </div>
<th> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit">
Requests Remaining {{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }}
</th> </div>
<th> <div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.hasLimit">
Next Request Due {{'UserManagment.MusicRemaining' | translate: {remaining: u.musicRequestQuota.remaining, total: u.musicRequestLimit} }}
</th> </div>
<th (click)="setOrder('lastLoggedIn', $event)"> </td>
<a> Last Logged In</a> </ng-container>
<span *ngIf="order === 'lastLoggedIn'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <ng-container matColumnDef="nextRequestDue">
</span> <th mat-header-cell *matHeaderCellDef> Next Request Due </th>
</th> <td mat-cell *matCellDef="let u">
<th (click)="setOrder('userType', $event)"> <div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit">
<a> {{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
User Type </div>
</a> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit">
<span *ngIf="order === 'userType'"> {{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> </div>
</span> <div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.remaining != u.musicRequestLimit">
</th> {{'UserManagment.MusicDue' | translate: {date: (u.musicRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
</tr> </div>
</thead> </td>
<tbody> </ng-container>
<tr *ngFor="let u of users | orderBy: order : reverse : 'case-insensitive'"> <ng-container matColumnDef="lastLoggedIn">
<td class="checkbox" data-label="Select:"> <th mat-header-cell *matHeaderCellDef mat-sort-header> Last Logged In </th>
<input id="{{u.id}}" type="checkbox" [(ngModel)]="u.checked"> <td mat-cell *matCellDef="let u">
<label for="{{u.id}}"></label> <span *ngIf="u.lastLoggedIn">
</td> {{u.lastLoggedIn | amLocal | amDateFormat: 'l LT'}}
<td class="td-labelled" data-label="Username:"> </span>
{{u.userName}} <span *ngIf="!u.lastLoggedIn">
</td> Not logged in yet!
<td class="td-labelled" data-label="Alias:"> </span> </td>
{{u.alias}} </ng-container>
</td>
<td class="td-labelled" data-label="Email:"> <ng-container matColumnDef="userType">
{{u.emailAddress}} <th mat-header-cell *matHeaderCellDef mat-sort-header> User Type </th>
</td> <td mat-cell *matCellDef="let u">
<td class="td-labelled" data-label="Roles:"> <span *ngIf="u.userType === 1">Local User</span>
<div *ngFor="let claim of u.claims"> <span *ngIf="u.userType === 2">Plex User</span>
<span *ngIf="u.userType === 3">Emby User</span> </td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef> Roles </th>
<td mat-cell *matCellDef="let element">
<div *ngFor="let claim of element.claims">
<span *ngIf="claim.enabled">{{claim.value}}</span> <span *ngIf="claim.enabled">{{claim.value}}</span>
</div> </div>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let u">
<button mat-raised-button color="accent" [routerLink]="['/usermanagement/user/' + u.id]">Details/Edit</button>
</td> </td>
<td class="td-labelled" data-label="Requests Remaining"> </ng-container>
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.hasLimit">
{{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }} <ng-container matColumnDef="welcome">
</div> <th mat-header-cell *matHeaderCellDef> </th>
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit"> <td mat-cell *matCellDef="let u">
{{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }} <button *ngIf="!u.hasLoggedIn" mat-raised-button color="accent" (click)="welcomeEmail(u)" [disabled]="!customizationSettings.applicationUrl">Send Welcome Email</button>
</div> </td>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.hasLimit"> </ng-container>
{{'UserManagment.MusicRemaining' | translate: {remaining: u.musicRequestQuota.remaining, total: u.musicRequestLimit} }}
</div> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
</td> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<td class="td-labelled" data-label="Request Due"> </table>
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit">
{{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }} <!-- Table -->
</div>
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit">
{{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
</div>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.remaining != u.musicRequestLimit">
{{'UserManagment.MusicDue' | translate: {date: (u.musicRequestQuota.nextRequest | amLocal | amDateFormat: 'l LT')} }}
</div>
</td>
<td class="td-labelled" data-label="Last Logged In:">
<!-- {{u.lastLoggedIn | date: 'short'}} -->
<span *ngIf="u.lastLoggedIn">
{{u.lastLoggedIn | amLocal | amDateFormat: 'l LT'}}
</span>
<span *ngIf="!u.lastLoggedIn">
Not logged in yet!
</span>
</td>
<td class="td-labelled" data-label="User Type:">
<span *ngIf="u.userType === 1">Local User</span>
<span *ngIf="u.userType === 2">Plex User</span>
<span *ngIf="u.userType === 3">Emby User</span>
</td>
<td>
<a id="edit{{u.userName}}" [routerLink]="['/usermanagement/user/' + u.id]" class="btn btn-sm btn-info-outline">Details/Edit</a>
</td>
<td *ngIf="customizationSettings">
<button *ngIf="!u.hasLoggedIn" (click)="welcomeEmail(u)" [disabled]="!customizationSettings.applicationUrl" class="btn btn-sm btn-info-outline">Send Welcome Email</button>
</td>
</tr>
</tbody>
</table>
<p-sidebar [(visible)]="showBulkEdit" position="right" styleClass="ui-sidebar-md side-back"> <p-sidebar [(visible)]="showBulkEdit" position="right" styleClass="ui-sidebar-md side-back">

View file

@ -1,21 +1,25 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit, ViewChild } from "@angular/core";
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces";
import { IdentityService, NotificationService, SettingsService } from "../services"; import { IdentityService, NotificationService, SettingsService } from "../services";
import { MatSort, MatTableDataSource } from "@angular/material";
import { SelectionModel } from "@angular/cdk/collections";
@Component({ @Component({
templateUrl: "./usermanagement.component.html", templateUrl: "./usermanagement.component.html",
}) })
export class UserManagementComponent implements OnInit { export class UserManagementComponent implements OnInit {
public displayedColumns: string[] = ['select', 'username', 'alias', 'email', 'roles', 'remainingRequests',
'nextRequestDue', 'lastLoggedIn', 'userType', 'actions', 'welcome'];
public dataSource: MatTableDataSource<IUser>;
public selection = new SelectionModel<IUser>(true, []);
@ViewChild(MatSort) public sort: MatSort;
public users: IUser[]; public users: IUser[];
public checkAll = false; public checkAll = false;
public emailSettings: IEmailNotificationSettings; public emailSettings: IEmailNotificationSettings;
public customizationSettings: ICustomizationSettings; public customizationSettings: ICustomizationSettings;
public order: string = "userName";
public reverse = false;
public showBulkEdit = false; public showBulkEdit = false;
public availableClaims: ICheckbox[]; public availableClaims: ICheckbox[];
public bulkMovieLimit?: number; public bulkMovieLimit?: number;
@ -23,15 +27,15 @@ export class UserManagementComponent implements OnInit {
public plexEnabled: boolean; public plexEnabled: boolean;
constructor(private identityService: IdentityService, constructor(private identityService: IdentityService,
private settingsService: SettingsService, private settingsService: SettingsService,
private notificationService: NotificationService, private notificationService: NotificationService,
private plexSettings: SettingsService) { } private plexSettings: SettingsService) { }
public ngOnInit() { public async ngOnInit() {
this.users = []; this.users = await this.identityService.getUsers().toPromise();
this.identityService.getUsers().subscribe(x => {
this.users = x; this.dataSource = new MatTableDataSource(this.users);
}); this.dataSource.sort = this.sort;
this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable); this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable);
@ -59,28 +63,12 @@ export class UserManagementComponent implements OnInit {
this.notificationService.success(`Sent a welcome email to ${user.emailAddress}`); this.notificationService.success(`Sent a welcome email to ${user.emailAddress}`);
} }
public checkAllBoxes() {
this.checkAll = !this.checkAll;
this.users.forEach(user => {
user.checked = this.checkAll;
});
}
public hasChecked(): boolean {
return this.users.some(x => {
return x.checked;
});
}
public bulkUpdate() { public bulkUpdate() {
const anyRoles = this.availableClaims.some(x => { const anyRoles = this.availableClaims.some(x => {
return x.enabled; return x.enabled;
}); });
this.users.forEach(x => { this.selection.selected.forEach(x => {
if (!x.checked) {
return;
}
if (anyRoles) { if (anyRoles) {
x.claims = this.availableClaims; x.claims = this.availableClaims;
} }
@ -103,23 +91,24 @@ export class UserManagementComponent implements OnInit {
this.bulkEpisodeLimit = undefined; this.bulkEpisodeLimit = undefined;
} }
public setOrder(value: string, el: any) { public isAllSelected() {
el = el.toElement || el.relatedTarget || el.target || el.srcElement; const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
if (el.nodeName === "A") {
el = el.parentElement; public masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
/** The label for the checkbox on the passed row */
public checkboxLabel(row?: IUser): string {
if (!row) {
return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
} }
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.id + 1}`;
const parent = el.parentElement;
const previousFilter = parent.querySelector(".active");
if (this.order === value) {
this.reverse = !this.reverse;
} else {
previousFilter.className = "";
el.className = "active";
}
this.order = value;
} }
} }