mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-12 08:16:05 -07:00
Started implimenting the Email Token functionality so we can send the user when creating a email with a token they can click on to set their username and password.
This commit is contained in:
parent
ffab4b7981
commit
acd9ebde33
26 changed files with 298 additions and 90 deletions
|
@ -1,4 +1,5 @@
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using Hangfire;
|
||||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||||
using Ombi.Core.Models;
|
using Ombi.Core.Models;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
@ -13,14 +14,16 @@ namespace Ombi.Core.IdentityResolver
|
||||||
{
|
{
|
||||||
public class UserIdentityManager : IUserIdentityManager
|
public class UserIdentityManager : IUserIdentityManager
|
||||||
{
|
{
|
||||||
public UserIdentityManager(IUserRepository userRepository, IMapper mapper)
|
public UserIdentityManager(IUserRepository userRepository, IMapper mapper, ITokenRepository token)
|
||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
Mapper = mapper;
|
Mapper = mapper;
|
||||||
|
TokenRepository = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMapper Mapper { get; }
|
private IMapper Mapper { get; }
|
||||||
private IUserRepository UserRepository { get; }
|
private IUserRepository UserRepository { get; }
|
||||||
|
private ITokenRepository TokenRepository { get; }
|
||||||
|
|
||||||
public async Task<bool> CredentialsValid(string username, string password)
|
public async Task<bool> CredentialsValid(string username, string password)
|
||||||
{
|
{
|
||||||
|
@ -50,11 +53,19 @@ namespace Ombi.Core.IdentityResolver
|
||||||
{
|
{
|
||||||
var user = Mapper.Map<User>(userDto);
|
var user = Mapper.Map<User>(userDto);
|
||||||
user.Claims.RemoveAll(x => x.Type == ClaimTypes.Country); // This is a hack around the Mapping Profile
|
user.Claims.RemoveAll(x => x.Type == ClaimTypes.Country); // This is a hack around the Mapping Profile
|
||||||
var result = HashPassword(user.Password);
|
var result = HashPassword(Guid.NewGuid().ToString("N")); // Since we do not allow the admin to set up the password. We send an email to the user
|
||||||
user.Password = result.HashedPass;
|
user.Password = result.HashedPass;
|
||||||
user.Salt = result.Salt;
|
user.Salt = result.Salt;
|
||||||
await UserRepository.CreateUser(user);
|
await UserRepository.CreateUser(user);
|
||||||
|
|
||||||
|
await TokenRepository.CreateToken(new EmailTokens
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
ValidUntil = DateTime.UtcNow.AddDays(7),
|
||||||
|
});
|
||||||
|
|
||||||
|
//BackgroundJob.Enqueue(() => );
|
||||||
|
|
||||||
return Mapper.Map<UserDto>(user);
|
return Mapper.Map<UserDto>(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ namespace Ombi.DependencyInjection
|
||||||
|
|
||||||
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
|
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
|
||||||
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
|
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
|
||||||
|
services.AddTransient<ITokenRepository, TokenRepository>();
|
||||||
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
|
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
|
||||||
}
|
}
|
||||||
public static void RegisterServices(this IServiceCollection services)
|
public static void RegisterServices(this IServiceCollection services)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
public bool CollectAnalyticData { get; set; }
|
public bool CollectAnalyticData { get; set; }
|
||||||
public bool Wizard { get; set; }
|
public bool Wizard { get; set; }
|
||||||
|
|
||||||
|
public string ExternalUrl { get; set; }
|
||||||
public string ApiKey { get; set; }
|
public string ApiKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,5 +28,6 @@ namespace Ombi.Store.Context
|
||||||
DbSet<ChildRequests> ChildRequests { get; set; }
|
DbSet<ChildRequests> ChildRequests { get; set; }
|
||||||
DbSet<MovieIssues> MovieIssues { get; set; }
|
DbSet<MovieIssues> MovieIssues { get; set; }
|
||||||
DbSet<TvIssues> TvIssues { get; set; }
|
DbSet<TvIssues> TvIssues { get; set; }
|
||||||
|
DbSet<EmailTokens> EmailTokens { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,6 +32,7 @@ namespace Ombi.Store.Context
|
||||||
public DbSet<ChildRequests> ChildRequests { get; set; }
|
public DbSet<ChildRequests> ChildRequests { get; set; }
|
||||||
public DbSet<MovieIssues> MovieIssues { get; set; }
|
public DbSet<MovieIssues> MovieIssues { get; set; }
|
||||||
public DbSet<TvIssues> TvIssues { get; set; }
|
public DbSet<TvIssues> TvIssues { get; set; }
|
||||||
|
public DbSet<EmailTokens> EmailTokens { get; set; }
|
||||||
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
|
|
18
src/Ombi.Store/Entities/EmailTokens.cs
Normal file
18
src/Ombi.Store/Entities/EmailTokens.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Entities
|
||||||
|
{
|
||||||
|
[Table("EmailTokens")]
|
||||||
|
public class EmailTokens : Entity
|
||||||
|
{
|
||||||
|
public Guid Token { get; set; }
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public DateTime ValidUntil { get; set; }
|
||||||
|
public bool Used { get; set; }
|
||||||
|
public DateTime DateUsed { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(UserId))]
|
||||||
|
public User User { get; set; }
|
||||||
|
}
|
||||||
|
}
|
12
src/Ombi.Store/Repository/ITokenRepository.cs
Normal file
12
src/Ombi.Store/Repository/ITokenRepository.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Repository
|
||||||
|
{
|
||||||
|
public interface ITokenRepository
|
||||||
|
{
|
||||||
|
Task CreateToken(EmailTokens token);
|
||||||
|
Task<EmailTokens> GetToken(Guid tokenId);
|
||||||
|
}
|
||||||
|
}
|
30
src/Ombi.Store/Repository/TokenRepository.cs
Normal file
30
src/Ombi.Store/Repository/TokenRepository.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Store.Context;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Repository
|
||||||
|
{
|
||||||
|
public class TokenRepository : ITokenRepository
|
||||||
|
{
|
||||||
|
public TokenRepository(IOmbiContext db)
|
||||||
|
{
|
||||||
|
Db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IOmbiContext Db { get; }
|
||||||
|
|
||||||
|
public async Task CreateToken(EmailTokens token)
|
||||||
|
{
|
||||||
|
token.Token = Guid.NewGuid();
|
||||||
|
await Db.EmailTokens.AddAsync(token);
|
||||||
|
await Db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EmailTokens> GetToken(Guid tokenId)
|
||||||
|
{
|
||||||
|
return await Db.EmailTokens.FirstOrDefaultAsync(x => x.Token == tokenId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,8 +28,6 @@ import { RequestCardComponent } from './request-grid/request-card.component';
|
||||||
|
|
||||||
import { LoginComponent } from './login/login.component';
|
import { LoginComponent } from './login/login.component';
|
||||||
import { LandingPageComponent } from './landingpage/landingpage.component';
|
import { LandingPageComponent } from './landingpage/landingpage.component';
|
||||||
import { UserManagementComponent } from './usermanagement/usermanagement.component';
|
|
||||||
import { UserManagementEditComponent } from './usermanagement/usermanagement-edit.component';
|
|
||||||
import { PageNotFoundComponent } from './errors/not-found.component';
|
import { PageNotFoundComponent } from './errors/not-found.component';
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
|
@ -47,6 +45,7 @@ import { StatusService } from './services/status.service';
|
||||||
import { SettingsModule } from './settings/settings.module';
|
import { SettingsModule } from './settings/settings.module';
|
||||||
import { WizardModule } from './wizard/wizard.module';
|
import { WizardModule } from './wizard/wizard.module';
|
||||||
import { SearchModule } from './search/search.module';
|
import { SearchModule } from './search/search.module';
|
||||||
|
import { UserManagementModule } from './usermanagement/usermanagement.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '*', component: PageNotFoundComponent },
|
{ path: '*', component: PageNotFoundComponent },
|
||||||
|
@ -54,9 +53,7 @@ const routes: Routes = [
|
||||||
{ path: 'requests', component: RequestComponent, canActivate: [AuthGuard] },
|
{ path: 'requests', component: RequestComponent, canActivate: [AuthGuard] },
|
||||||
//{ path: 'requests-grid', component: RequestGridComponent },
|
//{ path: 'requests-grid', component: RequestGridComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
{ path: 'landingpage', component: LandingPageComponent },
|
{ path: 'landingpage', component: LandingPageComponent }
|
||||||
{ path: 'usermanagement', component: UserManagementComponent, canActivate: [AuthGuard] },
|
|
||||||
{ path: 'usermanagement/edit/:id', component: UserManagementEditComponent, canActivate: [AuthGuard] },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -82,7 +79,8 @@ const routes: Routes = [
|
||||||
MdCardModule,
|
MdCardModule,
|
||||||
MdInputModule,
|
MdInputModule,
|
||||||
MdTabsModule,
|
MdTabsModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule,
|
||||||
|
UserManagementModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -90,12 +88,10 @@ const routes: Routes = [
|
||||||
RequestComponent,
|
RequestComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
LandingPageComponent,
|
LandingPageComponent,
|
||||||
UserManagementComponent,
|
|
||||||
MovieRequestsComponent,
|
MovieRequestsComponent,
|
||||||
TvRequestsComponent,
|
TvRequestsComponent,
|
||||||
//RequestGridComponent,
|
//RequestGridComponent,
|
||||||
RequestCardComponent,
|
RequestCardComponent,
|
||||||
UserManagementEditComponent,
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
RequestService,
|
RequestService,
|
||||||
|
|
|
@ -14,7 +14,8 @@ export interface IOmbiSettings extends ISettings {
|
||||||
//baseUrl:string,
|
//baseUrl:string,
|
||||||
collectAnalyticData: boolean,
|
collectAnalyticData: boolean,
|
||||||
wizard: boolean,
|
wizard: boolean,
|
||||||
apiKey: string
|
apiKey: string,
|
||||||
|
externalUrl:string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEmbySettings extends IExternalSettings {
|
export interface IEmbySettings extends IExternalSettings {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
<input type="text" id="search" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
<input type="text" id="search" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!-- Movie tab -->
|
<!-- Movie tab -->
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!-- Movie tab -->
|
<!-- Movie tab -->
|
||||||
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
<div role="tabpanel" class="tab-pane" id="TvShowTab">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class PlexService extends ServiceAuthHelpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibraries(plexSettings: IPlexServer): Observable<IPlexLibraries> {
|
getLibraries(plexSettings: IPlexServer): Observable<IPlexLibraries> {
|
||||||
return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), { headers: this.headers }).map(this.extractData);
|
return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), { headers: this.headers }).map(this.extractData).catch(this.handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,50 +1,52 @@
|
||||||
|
<settings-menu></settings-menu>
|
||||||
<settings-menu></settings-menu>
|
|
||||||
<fieldset *ngIf="settings">
|
<fieldset *ngIf="form">
|
||||||
<legend>Ombi Configuration</legend>
|
<legend>Ombi Configuration</legend>
|
||||||
|
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="portNumber" class="control-label">Port</label>
|
<label for="portNumber" class="control-label">Port</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}" pTooltip="You will have to restart after changing the port.">
|
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" formControlName="port" pTooltip="You will have to restart after changing the port.">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<div class="form-group">
|
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||||
<label for="BaseUrl" class="control-label">Base Url @Html.ToolTip("This will make Ombi run with a base url, usually used in reverse proxy scenarios")</label>
|
<div>The External URL is incorrect</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="externalUrl" class="control-label">External Url</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control form-control-custom " id="BaseUrl" name="BaseUrl" placeholder="Base Url" value="@Model.BaseUrl">
|
<input type="text" class="form-control form-control-custom " id="externalUrl" name="externalUrl" placeholder="http://ombi.io/" formControlName="externalUrl" tooltipPosition="top" pTooltip="This will be the link that will be in any emails/notifications sent to the users.">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="control-label">You will have to restart after changing the base url.</small>-->
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="ApiKey" class="control-label">Api Key</label>
|
<label for="ApiKey" class="control-label">Api Key</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input [(ngModel)]="settings.apiKey" type="text" [readonly]="true" class="form-control form-control-custom" id="ApiKey" name="ApiKey" value="{{settings.apiKey}}">
|
<input type="text" class="form-control form-control-custom" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||||
|
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
<div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key"></div>
|
<div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group-addon">
|
<div class="input-group-addon">
|
||||||
<div class="fa fa-clipboard" ></div>
|
<div class="fa fa-clipboard"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" [(ngModel)]="settings.collectAnalyticData" ng-checked="settings.collectAnalyticData">
|
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" formControlName="collectAnalyticData">
|
||||||
<label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser used</label>
|
<label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser used</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</form>
|
||||||
|
</fieldset>
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
import { IOmbiSettings } from '../../interfaces/ISettings'
|
|
||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
import { NotificationService } from "../../services/notification.service";
|
import { NotificationService } from "../../services/notification.service";
|
||||||
|
|
||||||
|
@ -9,19 +9,21 @@ import { NotificationService } from "../../services/notification.service";
|
||||||
})
|
})
|
||||||
export class OmbiComponent implements OnInit {
|
export class OmbiComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { }
|
constructor(private settingsService: SettingsService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private fb: FormBuilder) { }
|
||||||
|
|
||||||
settings: IOmbiSettings;
|
form: FormGroup;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.settings = {
|
this.settingsService.getOmbi().subscribe(x => {
|
||||||
apiKey: "",
|
this.form = this.fb.group({
|
||||||
port: 3579,
|
port: [x.port],
|
||||||
wizard: true,
|
collectAnalyticData: [x.collectAnalyticData],
|
||||||
collectAnalyticData: true,
|
apiKey: [{ value: x.apiKey, disabled: true }],
|
||||||
id:0
|
externalUrl: [x.externalUrl],
|
||||||
}
|
});
|
||||||
this.settingsService.getOmbi().subscribe(x => this.settings = x);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,8 +31,12 @@ export class OmbiComponent implements OnInit {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
onSubmit(form: FormGroup) {
|
||||||
this.settingsService.saveOmbi(this.settings).subscribe(x => {
|
if (form.invalid) {
|
||||||
|
this.notificationService.error("Validation", "Please check your entered values");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.settingsService.saveOmbi(form.value).subscribe(x => {
|
||||||
if (x) {
|
if (x) {
|
||||||
this.notificationService.success("Settings Saved", "Successfully saved Ombi settings");
|
this.notificationService.success("Settings Saved", "Successfully saved Ombi settings");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -91,7 +91,8 @@ export class PlexComponent implements OnInit, OnDestroy {
|
||||||
};
|
};
|
||||||
server.plexSelectedLibraries.push(lib);
|
server.plexSelectedLibraries.push(lib);
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
err => { this.notificationService.error("Error", err) });
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
<h3>Create User</h3>
|
||||||
|
<button type="button" class="btn btn-primary-outline" style="float:right;" [routerLink]="['/usermanagement/']">Back</button>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-body" style="margin-top:45px;">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="control-label">Username</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" [(ngModel)]="user.username" class="form-control form-control-custom " id="username" name="username" value="{{user?.username}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="alias" class="control-label">Alias</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" [(ngModel)]="user.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{user?.alias}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="alias" class="control-label">Email Address</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" [(ngModel)]="user.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{user?.emailAddress}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngFor="let c of availableClaims">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" id="create{{c.value}}" [attr.name]="'create' + c.value" ng-checked="c.enabled">
|
||||||
|
<label for="create{{c.value}}">{{c.value}}</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-danger-outline" (click)="update()">Create</button>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { IUser, UserType, ICheckbox } from '../interfaces/IUser';
|
||||||
|
import { IdentityService } from '../services/identity.service';
|
||||||
|
import { NotificationService } from '../services/notification.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
|
||||||
|
templateUrl: './usermanagement-add.component.html'
|
||||||
|
})
|
||||||
|
export class UserManagementAddComponent implements OnInit {
|
||||||
|
constructor(private identityService: IdentityService,
|
||||||
|
private notificationSerivce: NotificationService,
|
||||||
|
private router : Router) { }
|
||||||
|
|
||||||
|
user: IUser;
|
||||||
|
availableClaims: ICheckbox[];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x);
|
||||||
|
this.user = {
|
||||||
|
alias: "",
|
||||||
|
claims: [],
|
||||||
|
emailAddress: "",
|
||||||
|
id: "",
|
||||||
|
password: "",
|
||||||
|
username: "",
|
||||||
|
userType: UserType.LocalUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(): void {
|
||||||
|
this.user.claims = this.availableClaims;
|
||||||
|
this.identityService.createUser(this.user).subscribe(x => {
|
||||||
|
this.notificationSerivce.success("Updated", `The user ${this.user.username} has been created successfully`)
|
||||||
|
this.router.navigate(['usermanagement']);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,37 +1,29 @@
|
||||||
<h1>User Management</h1>
|
<div *ngIf="user">
|
||||||
|
<h3>User: {{user.username}}</h3>
|
||||||
|
<button type="button" class="btn btn-primary-outline" style="float:right;" [routerLink]="['/usermanagement/']">Back</button>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-body" style="margin-top:45px;">
|
||||||
|
|
||||||
<div *ngIf="user">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" (click)="showEditDialog=false">×</button>
|
|
||||||
<h4 class="modal-title">Editing User {{user?.username}}</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="control-label">Username</label>
|
<label for="username" class="control-label">Username</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" [(ngModel)]="selectedUser.username" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{selectedUser?.username}}">
|
<input type="text" [(ngModel)]="user.username" [readonly]="true" class="form-control form-control-custom " id="username" name="username" value="{{user?.username}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="alias" class="control-label">Alias</label>
|
<label for="alias" class="control-label">Alias</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" [(ngModel)]="selectedUser.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{selectedUser?.alias}}">
|
<input type="text" [(ngModel)]="user.alias" class="form-control form-control-custom " id="alias" name="alias" value="{{user?.alias}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="alias" class="control-label">Email Address</label>
|
<label for="alias" class="control-label">Email Address</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" [(ngModel)]="selectedUser.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{selectedUser?.emailAddress}}">
|
<input type="text" [(ngModel)]="user.emailAddress" class="form-control form-control-custom " id="emailAddress" name="emailAddress" value="{{user?.emailAddress}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngFor="let c of user.claims">
|
||||||
<div *ngFor="let c of selectedUser.claims">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" id="create{{c.value}}" [attr.name]="'create' + c.value" ng-checked="c.enabled">
|
<input type="checkbox" [(ngModel)]="c.enabled" [value]="c.value" id="create{{c.value}}" [attr.name]="'create' + c.value" ng-checked="c.enabled">
|
||||||
|
@ -44,10 +36,8 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div>
|
||||||
<button type="button" class="btn btn-danger-outline" (click)="showEditDialog=false">Close</button>
|
<button type="button" class="btn btn-danger-outline" (click)="update()">Update</button>
|
||||||
<button type="button" class="btn btn-primary-outline" (click)="updateUser()">Save changes</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { IUser, ICheckbox } from '../interfaces/IUser';
|
import { IUser } from '../interfaces/IUser';
|
||||||
import { IdentityService } from '../services/identity.service';
|
import { IdentityService } from '../services/identity.service';
|
||||||
|
import { NotificationService } from '../services/notification.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
||||||
templateUrl: './usermanagement-edit.component.html'
|
templateUrl: './usermanagement-edit.component.html'
|
||||||
})
|
})
|
||||||
export class UserManagementEditComponent implements OnInit {
|
export class UserManagementEditComponent {
|
||||||
constructor(private identityService: IdentityService, private route: ActivatedRoute) {
|
constructor(private identityService: IdentityService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private notificationSerivce: NotificationService) {
|
||||||
this.route.params
|
this.route.params
|
||||||
.subscribe(params => {
|
.subscribe(params => {
|
||||||
this.userId = +params['id']; // (+) converts string 'id' to a number
|
this.userId = +params['id']; // (+) converts string 'id' to a number
|
||||||
|
|
||||||
|
this.identityService.getUserById(this.userId).subscribe(x => {
|
||||||
|
this.user = x;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
|
|
||||||
this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x);
|
|
||||||
this.identityService.getUserById(this.userId).subscribe(x => this.user = x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user: IUser;
|
user: IUser;
|
||||||
userId: number;
|
userId: number;
|
||||||
|
|
||||||
|
update(): void {
|
||||||
|
this.identityService.updateUser(this.user).subscribe(x => {
|
||||||
|
|
||||||
availableClaims : ICheckbox[];
|
this.notificationSerivce.success("Updated",`The user ${this.user.username} has been updated successfully`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@
|
||||||
<i class="fa fa-search"></i>
|
<i class="fa fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="text" class="form-control" placeholder="Search" [(ngModel)]="searchTerm">
|
<input type="text" class="form-control" id="search" placeholder="Search" [(ngModel)]="searchTerm">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-success-outline" (click)="showCreateDialogue=true">Add User</button>
|
<button type="button" class="btn btn-success-outline" [routerLink]="['/usermanagement/add']">Add User</button>
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
<span *ngIf="u.userType === 3">Emby User</span>
|
<span *ngIf="u.userType === 3">Emby User</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a (click)="edit(u)" class="btn btn-sm btn-info-outline">Details/Edit</a>
|
<a [routerLink]="['/usermanagement/edit/' + u.id]" class="btn btn-sm btn-info-outline">Details/Edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { NgModule, } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { UserManagementComponent } from './usermanagement.component';
|
||||||
|
import { UserManagementEditComponent } from './usermanagement-edit.component';
|
||||||
|
import { UserManagementAddComponent } from './usermanagement-add.component';
|
||||||
|
|
||||||
|
import { IdentityService } from '../services/identity.service';
|
||||||
|
|
||||||
|
import { AuthGuard } from '../auth/auth.guard';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: 'usermanagement', component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: 'usermanagement/add', component: UserManagementAddComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: 'usermanagement/edit/:id', component: UserManagementEditComponent, canActivate: [AuthGuard] },
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
NgbModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
UserManagementComponent,
|
||||||
|
UserManagementAddComponent,
|
||||||
|
UserManagementEditComponent
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
IdentityService
|
||||||
|
],
|
||||||
|
|
||||||
|
})
|
||||||
|
export class UserManagementModule { }
|
|
@ -62,10 +62,10 @@ hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control-custom {
|
.form-control-custom {
|
||||||
background-color: $form-color $i;
|
/*background-color: $form-color $i;
|
||||||
color: white $i;
|
color: white $i;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: 0 0 0 !important;
|
box-shadow: 0 0 0 !important;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-small {
|
.form-small {
|
||||||
|
@ -762,9 +762,16 @@ textarea {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-signin .form-control:focus {
|
.form-control:focus {
|
||||||
border-color: rgb(104, 145, 162);
|
border-color: rgb(104, 145, 162);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
|
||||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
|
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-control:focus#search {
|
||||||
|
border-color: transparent;
|
||||||
|
outline: 0;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue