mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-15 01:32:55 -07:00
Added the testing notifications and discord notification #865
This commit is contained in:
parent
5970ee963d
commit
d73899fc53
29 changed files with 487 additions and 202 deletions
11
src/.vscode/settings.json
vendored
Normal file
11
src/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.js": {"when": "$(basename).ts"}, // Hide JS files when there is a ts file
|
||||
"**/*.js.map" : true
|
||||
}
|
||||
}
|
|
@ -14,15 +14,10 @@ namespace Ombi.Api.Discord
|
|||
private string Endpoint => "https://discordapp.com/api/"; //webhooks/270828242636636161/lLysOMhJ96AFO1kvev0bSqP-WCZxKUh1UwfubhIcLkpS0DtM3cg4Pgeraw3waoTXbZii
|
||||
private Api Api { get; }
|
||||
|
||||
public async Task SendMessage(string message, string webhookId, string webhookToken, string username = null)
|
||||
public async Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken)
|
||||
{
|
||||
var request = new Request(Endpoint, $"webhooks/{webhookId}/{webhookToken}", HttpMethod.Post);
|
||||
|
||||
var body = new DiscordWebhookBody
|
||||
{
|
||||
content = message,
|
||||
username = username
|
||||
};
|
||||
request.AddJsonBody(body);
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Api.Discord.Models;
|
||||
|
||||
namespace Ombi.Api.Discord
|
||||
{
|
||||
public interface IDiscordApi
|
||||
{
|
||||
Task SendMessage(string message, string webhookId, string webhookToken, string username = null);
|
||||
Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,24 @@
|
|||
namespace Ombi.Api.Discord.Models
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Api.Discord.Models
|
||||
{
|
||||
public class DiscordWebhookBody
|
||||
{
|
||||
public string content { get; set; }
|
||||
public string username { get; set; }
|
||||
public List<DiscordEmbeds> embeds { get; set; }
|
||||
}
|
||||
|
||||
public class DiscordEmbeds
|
||||
{
|
||||
public string title { get; set; }
|
||||
public string type => "rich"; // Always rich or embedded content
|
||||
public string description { get; set; } // Don't really need to set this
|
||||
public DiscordImage image { get; set; }
|
||||
}
|
||||
|
||||
public class DiscordImage
|
||||
{
|
||||
public string url { get; set; }
|
||||
}
|
||||
}
|
21
src/Ombi.Core/Models/UI/DiscordNotificationsViewModel.cs
Normal file
21
src/Ombi.Core/Models/UI/DiscordNotificationsViewModel.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// The view model for the notification settings page
|
||||
/// </summary>
|
||||
/// <seealso cref="DiscordNotificationSettings" />
|
||||
public class DiscordNotificationsViewModel : DiscordNotificationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the notification templates.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The notification templates.
|
||||
/// </value>
|
||||
public List<NotificationTemplates> NotificationTemplates { get; set; }
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Models.Notifications
|
||||
namespace Ombi.Core.Models.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// The view model for the notification settings page
|
||||
|
|
|
@ -27,6 +27,7 @@ using Ombi.Settings.Settings;
|
|||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Core.Rules;
|
||||
using Ombi.Notifications.Agents;
|
||||
using Ombi.Schedule.Jobs.Radarr;
|
||||
|
||||
namespace Ombi.DependencyInjection
|
||||
|
@ -84,7 +85,11 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IRequestServiceMain, RequestService>();
|
||||
services.AddTransient(typeof(IRequestService<>), typeof(JsonRequestService<>));
|
||||
services.AddSingleton<INotificationService, NotificationService>();
|
||||
services.AddSingleton<INotificationHelper, NotificationHelper>();
|
||||
services.AddTransient<INotificationHelper, NotificationHelper>();
|
||||
|
||||
|
||||
services.AddTransient<IDiscordNotification, DiscordNotification>();
|
||||
services.AddTransient<IEmailNotification, EmailNotification>();
|
||||
}
|
||||
|
||||
public static void RegisterJobs(this IServiceCollection services)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using AutoMapper;
|
||||
using Ombi.Models.Notifications;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
namespace Ombi.Mapping.Profiles
|
||||
|
@ -9,6 +9,7 @@ namespace Ombi.Mapping.Profiles
|
|||
public SettingsProfile()
|
||||
{
|
||||
CreateMap<EmailNotificationsViewModel, EmailNotificationSettings>().ReverseMap();
|
||||
CreateMap<DiscordNotificationsViewModel, DiscordNotificationSettings>().ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Discord;
|
||||
using Ombi.Api.Discord.Models;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Interfaces;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public class DiscordNotification : BaseNotification<DiscordNotificationSettings>
|
||||
public class DiscordNotification : BaseNotification<DiscordNotificationSettings>, IDiscordNotification
|
||||
{
|
||||
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, ILogger<DiscordNotification> log, INotificationTemplatesRepository r) : base(sn, r)
|
||||
{
|
||||
|
@ -47,21 +50,22 @@ namespace Ombi.Notifications.Agents
|
|||
|
||||
protected override async Task NewRequest(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var message = $"{model.Title} has been requested by user: {model.RequestedUser}";
|
||||
var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, NotificationType.NewRequest);
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = template.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
protected override async Task Issue(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var message = $"A new issue: {model.Body} has been reported by user: {model.RequestedUser} for the title: {model.Title}";
|
||||
var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, NotificationType.Issue);
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = template.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
@ -71,37 +75,41 @@ namespace Ombi.Notifications.Agents
|
|||
var message = $"Hello! The user '{model.RequestedUser}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying";
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = message
|
||||
};
|
||||
notification.Other.Add("image", model.ImgSrc);
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
protected override async Task RequestDeclined(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! Your request for {model.Title} has been declined, Sorry!";
|
||||
var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, NotificationType.RequestDeclined);
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = template.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
protected override async Task RequestApproved(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! The request for {model.Title} has now been approved!";
|
||||
var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, NotificationType.RequestApproved);
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = template.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
||||
protected override async Task AvailableRequest(NotificationOptions model, DiscordNotificationSettings settings)
|
||||
{
|
||||
var message = $"Hello! The request for {model.Title} is now available!";
|
||||
var template = await TemplateRepository.GetTemplate(NotificationAgent.Email, NotificationType.RequestAvailable);
|
||||
|
||||
var notification = new NotificationMessage
|
||||
{
|
||||
Message = message,
|
||||
Message = template.Message,
|
||||
};
|
||||
await Send(notification, settings);
|
||||
}
|
||||
|
@ -110,7 +118,28 @@ namespace Ombi.Notifications.Agents
|
|||
{
|
||||
try
|
||||
{
|
||||
await Api.SendMessage(model.Message, settings.WebookId, settings.Token, settings.Username);
|
||||
var discordBody = new DiscordWebhookBody
|
||||
{
|
||||
content = model.Message,
|
||||
username = settings.Username,
|
||||
};
|
||||
|
||||
string image;
|
||||
if (model.Other.TryGetValue("image", out image))
|
||||
{
|
||||
discordBody.embeds = new List<DiscordEmbeds>
|
||||
{
|
||||
new DiscordEmbeds
|
||||
{
|
||||
image = new DiscordImage
|
||||
{
|
||||
url = image
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
await Api.SendMessage(discordBody, settings.WebookId, settings.Token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ using MailKit.Net.Smtp;
|
|||
using MimeKit;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Interfaces;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Notifications.Templates;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
@ -11,7 +12,7 @@ using Ombi.Store.Repository;
|
|||
|
||||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public class EmailNotification : BaseNotification<EmailNotificationSettings>
|
||||
public class EmailNotification : BaseNotification<EmailNotificationSettings>, IEmailNotification
|
||||
{
|
||||
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r) : base(settings, r)
|
||||
{
|
||||
|
|
6
src/Ombi.Notifications/Agents/IDiscordNotification.cs
Normal file
6
src/Ombi.Notifications/Agents/IDiscordNotification.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public interface IDiscordNotification : INotification
|
||||
{
|
||||
}
|
||||
}
|
6
src/Ombi.Notifications/Agents/IEmailNotification.cs
Normal file
6
src/Ombi.Notifications/Agents/IEmailNotification.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ombi.Notifications.Agents
|
||||
{
|
||||
public interface IEmailNotification : INotification
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Store;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Notifications
|
||||
namespace Ombi.Notifications.Interfaces
|
||||
{
|
||||
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
|
||||
{
|
||||
|
|
|
@ -46,28 +46,6 @@ export enum NotificationType {
|
|||
|
||||
export interface IDiscordNotifcationSettings extends INotificationSettings{
|
||||
webhookUrl : string,
|
||||
username : string,
|
||||
|
||||
|
||||
// public string WebhookUrl { get; set; }
|
||||
// public string Username { get; set; }
|
||||
|
||||
// [JsonIgnore]
|
||||
// public string WebookId => SplitWebUrl(4);
|
||||
|
||||
// [JsonIgnore]
|
||||
// public string Token => SplitWebUrl(5);
|
||||
|
||||
// private string SplitWebUrl(int index)
|
||||
// {
|
||||
// if (!WebhookUrl.StartsWith("http", StringComparison.InvariantCulture))
|
||||
// {
|
||||
// WebhookUrl = "https://" + WebhookUrl;
|
||||
// }
|
||||
// var split = WebhookUrl.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// return split.Length < index
|
||||
// ? string.Empty
|
||||
// : split[index];
|
||||
// }
|
||||
username: string,
|
||||
notificationTemplates: INotificationTemplates[],
|
||||
}
|
|
@ -1,3 +1,45 @@
|
|||
<div *ngIf="request" style="background-color:black; color:white;">
|
||||
{{request.title}}
|
||||
<style>
|
||||
|
||||
/*$bg-colour: #333333;*/
|
||||
.card {
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
|
||||
transition: 0.3s;
|
||||
border-radius: 5px; /* 5px rounded corners */
|
||||
background: #4f4e4e;
|
||||
border-bottom: #333333 solid 1px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
/* On mouse-over, add a deeper shadow */
|
||||
.card:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.card-container {
|
||||
padding: 2px 16px;
|
||||
}
|
||||
|
||||
.image-fill {
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div *ngIf="request" class="card" style="color: white;">
|
||||
<div style="position: relative; overflow: hidden;">
|
||||
<img src="{{request.posterPath}}" alt="poster" class="image-fill">
|
||||
</div>
|
||||
<div class="card-container">
|
||||
{{request.title}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
<style>
|
||||
.landing-box {
|
||||
height: 150px;
|
||||
background: #333333 !important;
|
||||
border-radius: 2%;
|
||||
display: flex;
|
||||
|
@ -10,40 +9,45 @@
|
|||
text-align: center;
|
||||
box-shadow: 5px 3px 5px black;
|
||||
}
|
||||
|
||||
.dragula-container {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.gu-mirror {
|
||||
margin-left: 100px !important;
|
||||
transform: rotate(4deg);
|
||||
-moz-transform: rotate(4deg);
|
||||
-webkit-transform: rotate(4deg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div *ngIf="tvRequests">
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-center">New Requests</h4>
|
||||
<div class="landing-box">
|
||||
<md-card-title>Title</md-card-title>
|
||||
<div [dragula]='"requests-bag"' [dragulaModel]="tvRequests.new">
|
||||
<br />
|
||||
<br />
|
||||
<request-card *ngFor="let item of tvRequests.new" [request]="item" ></request-card>
|
||||
<div class="dragula-container" [dragula]='"requests-bag"' [dragulaModel]="tvRequests.new">
|
||||
<request-card *ngFor="let item of tvRequests.new" [request]="item"></request-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-center">Approved Requests</h4>
|
||||
<div class="landing-box">
|
||||
|
||||
<div [dragula]='"requests-bag"' [dragulaModel]="tvRequests.approved">
|
||||
<br />
|
||||
<br />
|
||||
<div class="dragula-container" [dragula]='"requests-bag"' [dragulaModel]="tvRequests.approved">
|
||||
<request-card *ngFor="let item of tvRequests.approved" [request]="item"></request-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-center">Available</h4>
|
||||
<div class="landing-box">
|
||||
<md-card-title>Title</md-card-title>
|
||||
<div style="border: dashed">
|
||||
<div [dragula]='"requests-bag"' [dragulaModel]="tvRequests.available">
|
||||
<br />
|
||||
<br />
|
||||
<request-card *ngFor="let item of tvRequests.available" [request]="item"></request-card>
|
||||
</div>
|
||||
<div class="dragula-container" [dragula]='"requests-bag"' [dragulaModel]="tvRequests.available">
|
||||
<request-card *ngFor="let item of tvRequests.available" [request]="item"></request-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { AuthHttp } from 'angular2-jwt';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { ServiceAuthHelpers } from '../service.helpers';
|
||||
|
||||
import { IDiscordNotifcationSettings, IEmailNotificationSettings } from '../../interfaces/INotifcationSettings'
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TesterService extends ServiceAuthHelpers {
|
||||
constructor(http: AuthHttp) {
|
||||
super(http, '/api/v1/tester/');
|
||||
}
|
||||
|
||||
discordTest(settings: IDiscordNotifcationSettings): Observable<boolean> {
|
||||
return this.http.post(`${this.url}discord`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
emailTest(settings: IEmailNotificationSettings): Observable<boolean> {
|
||||
return this.http.post(`${this.url}email`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import {
|
|||
ICustomizationSettings,
|
||||
IRadarrSettings
|
||||
} from '../interfaces/ISettings';
|
||||
import { IEmailNotificationSettings } from '../interfaces/INotifcationSettings';
|
||||
import { IEmailNotificationSettings, IDiscordNotifcationSettings } from '../interfaces/INotifcationSettings';
|
||||
|
||||
@Injectable()
|
||||
export class SettingsService extends ServiceAuthHelpers {
|
||||
|
@ -88,4 +88,12 @@ export class SettingsService extends ServiceAuthHelpers {
|
|||
saveEmailNotificationSettings(settings: IEmailNotificationSettings): Observable<boolean> {
|
||||
return this.httpAuth.post(`${this.url}/notifications/email`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError)
|
||||
}
|
||||
|
||||
getDiscordNotificationSettings(): Observable<IDiscordNotifcationSettings> {
|
||||
return this.httpAuth.get(`${this.url}/notifications/discord`).map(this.extractData).catch(this.handleError)
|
||||
}
|
||||
|
||||
saveDiscordNotificationSettings(settings: IDiscordNotifcationSettings): Observable<boolean> {
|
||||
return this.httpAuth.post(`${this.url}/notifications/discord`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData).catch(this.handleError)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
<settings-menu></settings-menu>
|
||||
<div *ngIf="emailForm">
|
||||
<div *ngIf="form">
|
||||
<fieldset>
|
||||
<legend>Email Notifications</legend>
|
||||
<legend>Discord Notifications</legend>
|
||||
<div class="col-md-6">
|
||||
<form novalidate [formGroup]="emailForm" (ngSubmit)="onSubmit(emailForm)">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
@ -13,68 +13,29 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="Authentication" formControlName="authentication"><label for="Authentication">Enable SMTP Authentication</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="emailForm.invalid && emailForm.dirty" class="alert alert-danger">
|
||||
<div *ngIf="emailForm.get('host').hasError('required')">Host is required</div>
|
||||
<div *ngIf="emailForm.get('port').hasError('required')">The Port is required</div>
|
||||
<div *ngIf="emailForm.get('sender').hasError('required')">The Email Sender is required</div>
|
||||
<div *ngIf="emailForm.get('sender').hasError('email')">The Email Sender needs to be a valid email address</div>
|
||||
<div *ngIf="emailForm.get('adminEmail').hasError('required')">The Email Sender is required</div>
|
||||
<div *ngIf="emailForm.get('adminEmail').hasError('email')">The Admin Email needs to be a valid email address</div>
|
||||
<div *ngIf="emailForm.get('username').hasError('required')">The Username is required</div>
|
||||
<div *ngIf="emailForm.get('password').hasError('required')">The Password is required</div>
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('webhookUrl').hasError('required')">The Webhook Url is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="host" class="control-label">SMTP Host</label>
|
||||
<label for="webhookUrl" class="control-label">Webhook Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="host" name="host" placeholder="localhost" formControlName="host">
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">SMTP Port</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" formControlName="port">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sender" class="control-label">Email Sender</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="sender" name="sender" formControlName="sender" tooltipPosition="top" pTooltip="The email address that the emails will be sent from">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="adminEmail" class="control-label">Admin Email</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="adminEmail" name="adminEmail" formControlName="adminEmail" tooltipPosition="top" pTooltip="The administrator email will be used to send emails for admin only notifications (e.g. New Requests that require approvals)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" *ngIf="emailForm.controls['username'].validator">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="username" name="username" formControlName="username" pTooltip="The username if authentication is enabled" tooltipPosition="top">
|
||||
<input type="text" class="form-control form-control-custom " id="username" name="username" formControlName="username" pTooltip="Optional, this will override the username you used for the Webhook">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="emailForm.get('password').validator">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<div>
|
||||
<input type="password" class="form-control form-control-custom " id="password" name="password" formControlName="password" pTooltip="The password if authentication is enabled" tooltipPosition="top">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button id="testPlex" type="submit" (click)="test()" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" type="submit" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test Connectivity
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -85,7 +46,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="emailForm.invalid" 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>
|
||||
</form>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
|
||||
|
||||
import { INotificationTemplates, IEmailNotificationSettings, NotificationType } from '../../interfaces/INotifcationSettings';
|
||||
import { INotificationTemplates, IDiscordNotifcationSettings, NotificationType } from '../../interfaces/INotifcationSettings';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { ValidationService } from "../../services/helpers/validation.service";
|
||||
import { TesterService } from "../../services/applications/tester.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: './discord.component.html',
|
||||
|
@ -13,75 +13,58 @@ export class DiscordComponent implements OnInit {
|
|||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private fb: FormBuilder,
|
||||
private validationService: ValidationService) { }
|
||||
private testerService : TesterService) { }
|
||||
|
||||
NotificationType = NotificationType;
|
||||
templates: INotificationTemplates[];
|
||||
|
||||
emailForm: FormGroup;
|
||||
form: FormGroup;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getEmailNotificationSettings().subscribe(x => {
|
||||
this.settingsService.getDiscordNotificationSettings().subscribe(x => {
|
||||
this.templates = x.notificationTemplates;
|
||||
|
||||
this.emailForm = this.fb.group({
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
authentication: [x.authentication],
|
||||
host: [x.host, [Validators.required]],
|
||||
password: [x.password],
|
||||
port: [x.port, [Validators.required]],
|
||||
sender: [x.sender, [Validators.required, Validators.email]],
|
||||
username: [x.username],
|
||||
adminEmail: [x.adminEmail, [Validators.required, Validators.email]],
|
||||
webhookUrl: [x.webhookUrl, [Validators.required]],
|
||||
|
||||
});
|
||||
|
||||
if (x.authentication) {
|
||||
this.validationService.enableValidation(this.emailForm, 'username');
|
||||
this.validationService.enableValidation(this.emailForm, 'password');
|
||||
}
|
||||
|
||||
this.subscribeToAuthChanges();
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(form: FormGroup) {
|
||||
console.log(form.value, form.valid);
|
||||
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return
|
||||
}
|
||||
|
||||
var settings = <IEmailNotificationSettings>form.value;
|
||||
var settings = <IDiscordNotifcationSettings>form.value;
|
||||
settings.notificationTemplates = this.templates;
|
||||
|
||||
this.settingsService.saveEmailNotificationSettings(settings).subscribe(x => {
|
||||
this.settingsService.saveDiscordNotificationSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Settings Saved", "Successfully saved Email settings");
|
||||
this.notificationService.success("Settings Saved", "Successfully saved the Discord settings");
|
||||
} else {
|
||||
this.notificationService.success("Settings Saved", "There was an error when saving the Email settings");
|
||||
this.notificationService.success("Settings Saved", "There was an error when saving the Discord settings");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
save() {
|
||||
test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private subscribeToAuthChanges() {
|
||||
const authCtrl = this.emailForm.controls.authentication;
|
||||
const changes$ = authCtrl.valueChanges;
|
||||
|
||||
changes$.subscribe((auth: boolean) => {
|
||||
|
||||
if (auth) {
|
||||
this.validationService.enableValidation(this.emailForm, 'username');
|
||||
this.validationService.enableValidation(this.emailForm, 'password');
|
||||
this.testerService.discordTest(form.value).subscribex(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successful", "Successfully sent a Discord message, please check the discord channel");
|
||||
} else {
|
||||
this.validationService.disableValidation(this.emailForm, 'username');
|
||||
this.validationService.disableValidation(this.emailForm, 'password');
|
||||
this.notificationService.success("Error", "There was an error when sending the Discord message. Please check your settings");
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button id="testPlex" type="submit" (click)="test()" class="btn btn-primary-outline">
|
||||
<button [disabled]="emailForm.invalid" type="submit" (click)="test(emailForm)" class="btn btn-primary-outline">
|
||||
Test Connectivity
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { FormGroup, Validators, FormBuilder } from '@angular/forms';
|
|||
import { INotificationTemplates, IEmailNotificationSettings, NotificationType } from '../../interfaces/INotifcationSettings';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { TesterService } from "../../services/applications/tester.service";
|
||||
import { ValidationService } from "../../services/helpers/validation.service";
|
||||
|
||||
@Component({
|
||||
|
@ -13,7 +14,8 @@ export class EmailNotificationComponent implements OnInit {
|
|||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private fb: FormBuilder,
|
||||
private validationService: ValidationService) { }
|
||||
private validationService: ValidationService,
|
||||
private testerService: TesterService) { }
|
||||
|
||||
NotificationType = NotificationType;
|
||||
templates: INotificationTemplates[];
|
||||
|
@ -45,8 +47,6 @@ export class EmailNotificationComponent implements OnInit {
|
|||
}
|
||||
|
||||
onSubmit(form: FormGroup) {
|
||||
console.log(form.value, form.valid);
|
||||
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return
|
||||
|
@ -65,8 +65,19 @@ export class EmailNotificationComponent implements OnInit {
|
|||
|
||||
}
|
||||
|
||||
save() {
|
||||
test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return
|
||||
}
|
||||
|
||||
this.testerService.emailTest(form.value).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Sent", "Successfully sent an email message, please check your inbox");
|
||||
} else {
|
||||
this.notificationService.success("Error", "There was an error when sending the Email message, please check your settings.");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private subscribeToAuthChanges() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { AuthGuard } from '../auth/auth.guard';
|
|||
import { AuthModule } from '../auth/auth.module';
|
||||
import { SonarrService } from '../services/applications/sonarr.service';
|
||||
import { RadarrService } from '../services/applications/radarr.service';
|
||||
import { TesterService } from '../services/applications/tester.service';
|
||||
import { ValidationService } from '../services/helpers/validation.service';
|
||||
|
||||
import { OmbiComponent } from './ombi/ombi.component';
|
||||
|
@ -73,7 +74,8 @@ const routes: Routes = [
|
|||
AuthService,
|
||||
RadarrService,
|
||||
AuthGuard,
|
||||
ValidationService
|
||||
ValidationService,
|
||||
TesterService
|
||||
],
|
||||
|
||||
})
|
||||
|
|
70
src/Ombi/Controllers/External/TesterController.cs
vendored
Normal file
70
src/Ombi/Controllers/External/TesterController.cs
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Notifications;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications.Agents;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
namespace Ombi.Controllers.External
|
||||
{
|
||||
/// <summary>
|
||||
/// The Tester Controller
|
||||
/// </summary>
|
||||
/// <seealso cref="Ombi.Controllers.BaseV1ApiController" />
|
||||
[Admin]
|
||||
public class TesterController : BaseV1ApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TesterController" /> class.
|
||||
/// </summary>
|
||||
/// <param name="service">The service.</param>
|
||||
/// <param name="notification">The notification.</param>
|
||||
public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN)
|
||||
{
|
||||
Service = service;
|
||||
DiscordNotification = notification;
|
||||
EmailNotification = emailN;
|
||||
}
|
||||
|
||||
private INotificationService Service { get; }
|
||||
private IDiscordNotification DiscordNotification { get; }
|
||||
private IEmailNotification EmailNotification { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends a test message to discord using the provided settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("discord")]
|
||||
public bool Discord([FromBody] DiscordNotificationSettings settings)
|
||||
{
|
||||
settings.Enabled = true;
|
||||
BackgroundJob.Enqueue(() => Service.PublishTest(new NotificationOptions{NotificationType = NotificationType.Test}, settings, DiscordNotification));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a test message via email to the admin email using the provided settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("email")]
|
||||
public bool Email([FromBody] EmailNotificationSettings settings)
|
||||
{
|
||||
settings.Enabled = true;
|
||||
var notificationModel = new NotificationOptions
|
||||
{
|
||||
NotificationType = NotificationType.Test,
|
||||
DateTime = DateTime.Now,
|
||||
ImgSrc = "https://imgs.xkcd.com/comics/shouldnt_be_hard.png"
|
||||
};
|
||||
BackgroundJob.Enqueue(() => Service.PublishTest(notificationModel, settings, EmailNotification));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,11 +6,11 @@ using AutoMapper;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Models.Notifications;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
@ -208,41 +208,74 @@ namespace Ombi.Controllers
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Email Notification Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("notifications/email")]
|
||||
public async Task<EmailNotificationsViewModel> EmailNotificationSettings()
|
||||
{
|
||||
var emailSettings = await Get<EmailNotificationSettings>();
|
||||
var model = Mapper.Map<EmailNotificationsViewModel>(emailSettings);
|
||||
/// <summary>
|
||||
/// Gets the Email Notification Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("notifications/email")]
|
||||
public async Task<EmailNotificationsViewModel> EmailNotificationSettings()
|
||||
{
|
||||
var emailSettings = await Get<EmailNotificationSettings>();
|
||||
var model = Mapper.Map<EmailNotificationsViewModel>(emailSettings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Email);
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Email);
|
||||
|
||||
return model;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
|
||||
{
|
||||
var templates = await TemplateRepository.GetAllTemplates(agent);
|
||||
return templates.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves the discord notification settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("notifications/email")]
|
||||
public async Task<bool> DiscordNotificationSettings([FromBody] DiscordNotificationsViewModel model)
|
||||
{
|
||||
// Save the email settings
|
||||
var settings = Mapper.Map<DiscordNotificationSettings>(model);
|
||||
var result = await Save(settings);
|
||||
|
||||
// Save the templates
|
||||
await TemplateRepository.UpdateRange(model.NotificationTemplates);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the discord Notification Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("notifications/email")]
|
||||
public async Task<DiscordNotificationsViewModel> DiscordNotificationSettings()
|
||||
{
|
||||
var emailSettings = await Get<DiscordNotificationSettings>();
|
||||
var model = Mapper.Map<DiscordNotificationsViewModel>(emailSettings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Discord);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
|
||||
{
|
||||
var templates = await TemplateRepository.GetAllTemplates(agent);
|
||||
return templates.ToList();
|
||||
}
|
||||
|
||||
|
||||
private async Task<T> Get<T>()
|
||||
{
|
||||
var settings = SettingsResolver.Resolve<T>();
|
||||
return await settings.GetSettingsAsync();
|
||||
}
|
||||
private async Task<T> Get<T>()
|
||||
{
|
||||
var settings = SettingsResolver.Resolve<T>();
|
||||
return await settings.GetSettingsAsync();
|
||||
}
|
||||
|
||||
private async Task<bool> Save<T>(T settingsModel)
|
||||
{
|
||||
var settings = SettingsResolver.Resolve<T>();
|
||||
return await settings.SaveSettingsAsync(settingsModel);
|
||||
private async Task<bool> Save<T>(T settingsModel)
|
||||
{
|
||||
var settings = SettingsResolver.Resolve<T>();
|
||||
return await settings.SaveSettingsAsync(settingsModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
@import "../bower_components/font-awesome/scss/font-awesome.scss";*/
|
||||
|
||||
@import '../bootstrap.css';
|
||||
@import './Styles.scss';
|
||||
@import './Styles.scss';
|
||||
@import '_loading.scss';
|
69
src/Ombi/Styles/_loading.scss
Normal file
69
src/Ombi/Styles/_loading.scss
Normal file
|
@ -0,0 +1,69 @@
|
|||
.app-loading-container {
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-box-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.app-loading {
|
||||
margin: auto;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.app-loading-one {
|
||||
opacity: 0;
|
||||
-webkit-animation: dot 1.3s infinite;
|
||||
-webkit-animation-delay: 0.0s;
|
||||
animation: app-loading-dot 1.3s infinite;
|
||||
animation-delay: 0.0s;
|
||||
}
|
||||
|
||||
.app-loading-two {
|
||||
opacity: 0;
|
||||
-webkit-animation: dot 1.3s infinite;
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation: app-loading-dot 1.3s infinite;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.app-loading-three {
|
||||
opacity: 0;
|
||||
-webkit-animation: dot 1.3s infinite;
|
||||
-webkit-animation-delay: 0.3s;
|
||||
animation: app-loading-dot 1.3s infinite;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes app-loading-dot {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes app-loading-dot {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1,11 @@
|
|||
<ombi>Loading..</ombi>
|
||||
<ombi>
|
||||
<div class="app-loading-container">
|
||||
|
||||
<div class="app-loading">
|
||||
Loading
|
||||
<span class="app-loading-one">.</span>
|
||||
<span class="app-loading-two">.</span>
|
||||
<span class="app-loading-three">.</span>
|
||||
</div>
|
||||
</div>
|
||||
</ombi>
|
||||
|
|
|
@ -48,6 +48,8 @@ module.exports = function (env) {
|
|||
'font-awesome/scss/font-awesome.scss',
|
||||
'pace-progress',
|
||||
'pace-progress/themes/orange/pace-theme-flash.css',
|
||||
'ng2-dragula',
|
||||
'dragula/dist/dragula.min.css'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue