Add AMI integration
This commit is contained in:
parent
69c9a0333c
commit
4268e4d7d9
9 changed files with 109 additions and 4 deletions
PhoneToolMX.Models/ViewModels
PhoneToolMX
|
@ -53,5 +53,7 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
HoldMusic = extEnt.HoldMusic?.Id,
|
HoldMusic = extEnt.HoldMusic?.Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// TODO: fix hack
|
||||||
|
public string NotifyOnChange() => (ExtId == 0 ? Id + 1000 : ExtId).ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,12 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
public interface IViewModel
|
public interface IViewModel
|
||||||
{
|
{
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
public IOwnedModel ToEntity(PTMXContext ctx);
|
public IOwnedModel ToEntity(PTMXContext ctx);
|
||||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
||||||
|
|
||||||
public IViewModel FromEntity(IOwnedModel entity);
|
public IViewModel FromEntity(IOwnedModel entity);
|
||||||
|
|
||||||
|
public string NotifyOnChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
public class PhoneVM : IViewModel
|
public class PhoneVM : IViewModel
|
||||||
{
|
{
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
[Header("MAC Address")]
|
[Header("MAC Address")]
|
||||||
[Required]
|
[Required]
|
||||||
[RegularExpression("(?:[0-9a-fA-F]{2}[-:]?){6}", ErrorMessage = "Must be of the form XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX")]
|
[RegularExpression("(?:[0-9a-fA-F]{2}[-:]?){6}", ErrorMessage = "Must be of the form XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX")]
|
||||||
|
@ -66,5 +66,10 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
PrimaryExtension = phoneEnt.PrimaryExtension?.Id,
|
PrimaryExtension = phoneEnt.PrimaryExtension?.Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a bit of a hack, but we can just choose any extension that belongs to this phone
|
||||||
|
public string NotifyOnChange() => Extensions.Count > 0
|
||||||
|
? (Extensions.First() + 1000).ToString()
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using AsterNET.Manager;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -5,6 +6,7 @@ using PhoneToolMX.Data;
|
||||||
using PhoneToolMX.Helpers;
|
using PhoneToolMX.Helpers;
|
||||||
using PhoneToolMX.Models;
|
using PhoneToolMX.Models;
|
||||||
using PhoneToolMX.Models.ViewModels;
|
using PhoneToolMX.Models.ViewModels;
|
||||||
|
using PhoneToolMX.Services;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ namespace PhoneToolMX.Controllers
|
||||||
|
|
||||||
SetMessage(
|
SetMessage(
|
||||||
FormMessageType.Success,
|
FormMessageType.Success,
|
||||||
$"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(entity.Entity))} was created."
|
$"{typeof(T).Name} {GetFriendlyName(entity.Entity)} was created."
|
||||||
);
|
);
|
||||||
|
|
||||||
return RedirectToAction("Edit", new { id = entity.Entity.Id });
|
return RedirectToAction("Edit", new { id = entity.Entity.Id });
|
||||||
|
@ -170,9 +172,22 @@ namespace PhoneToolMX.Controllers
|
||||||
_context.Set<T>().Update(currentModel);
|
_context.Set<T>().Update(currentModel);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Try to notify the relevant endpoint to update its config
|
||||||
|
var amiStatus = "";
|
||||||
|
if (HttpContext.RequestServices.GetService<IAsteriskManager>() is {} ami) {
|
||||||
|
try { await ami.SendNotifyAsync(vm.NotifyOnChange());
|
||||||
|
amiStatus = "Your phone's configuration should be updated shortly.";
|
||||||
|
}
|
||||||
|
catch (BadResponseException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Response: {e.Message})."; }
|
||||||
|
catch (ManagerException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Client Error: {e.Message})."; }
|
||||||
|
catch (Exception e) {
|
||||||
|
amiStatus = $"You'll need to manually update your phone settings (Unknown Error: {e.Message}).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SetMessage(
|
SetMessage(
|
||||||
FormMessageType.Success,
|
FormMessageType.Success,
|
||||||
$"{typeof(T).Name} {GetFriendlyName(vm)} was updated."
|
$"{typeof(T).Name} {GetFriendlyName(currentModel)} was updated.{" " + amiStatus}"
|
||||||
);
|
);
|
||||||
|
|
||||||
return RedirectToAction("Edit", new { id = entity.Id });
|
return RedirectToAction("Edit", new { id = entity.Id });
|
||||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
using NuGet.Packaging;
|
using NuGet.Packaging;
|
||||||
using PhoneToolMX.Models;
|
using PhoneToolMX.Models;
|
||||||
|
using PhoneToolMX.Services;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
@ -112,6 +113,11 @@ builder.Services.AddAuthorization(opts =>
|
||||||
policy => policy.RequireRole("Administrator"));
|
policy => policy.RequireRole("Administrator"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// AMI for PJSIP notifications
|
||||||
|
if (builder.Configuration.GetSection("ami").Exists()) {
|
||||||
|
builder.Services.AddSingleton<IAsteriskManager, AsteriskManager>();
|
||||||
|
}
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
|
|
36
PhoneToolMX/Services/AsteriskManager.cs
Normal file
36
PhoneToolMX/Services/AsteriskManager.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using AsterNET.Manager;
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class AsteriskManager : IAsteriskManager
|
||||||
|
{
|
||||||
|
private IConfigurationSection amiConf;
|
||||||
|
private ManagerConnection conn;
|
||||||
|
public AsteriskManager(IConfiguration config)
|
||||||
|
{
|
||||||
|
amiConf = config.GetRequiredSection("ami");
|
||||||
|
conn = new ManagerConnection(amiConf["host"],
|
||||||
|
int.Parse(amiConf["port"]),
|
||||||
|
amiConf["username"],
|
||||||
|
amiConf["secret"]);
|
||||||
|
conn.Login();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a <c>polycom-check-cfg</c> notification to the requested PJSIP endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The PJSIP endpoint to notify, without the <c>PJSIP/</c> prefix.</param>
|
||||||
|
/// <exception cref="BadResponseException">If the request fails, this exception will be thrown.</exception>
|
||||||
|
public async Task SendNotifyAsync(string endpoint)
|
||||||
|
{
|
||||||
|
var response = await conn.SendActionAsync(new PJSIPNotifyAction(endpoint, "polycom-check-cfg"));
|
||||||
|
if (!response.IsSuccess()) {
|
||||||
|
throw new BadResponseException(response.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
PhoneToolMX/Services/BadResponseException.cs
Normal file
7
PhoneToolMX/Services/BadResponseException.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class BadResponseException : Exception
|
||||||
|
{
|
||||||
|
public BadResponseException(string message) : base(message) {}
|
||||||
|
}
|
||||||
|
}
|
7
PhoneToolMX/Services/IAsteriskManager.cs
Normal file
7
PhoneToolMX/Services/IAsteriskManager.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public interface IAsteriskManager
|
||||||
|
{
|
||||||
|
public Task SendNotifyAsync(string endpoint);
|
||||||
|
}
|
||||||
|
}
|
25
PhoneToolMX/Services/PJSIPNotifyAction.cs
Normal file
25
PhoneToolMX/Services/PJSIPNotifyAction.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class PJSIPNotifyAction : AsterNET.Manager.Action.ManagerAction
|
||||||
|
{
|
||||||
|
|
||||||
|
public override string Action => "PJSIPNotify";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The endpoint to which to send the NOTIFY.
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The config section name from <c>pjsip_notify.conf</c> to use.
|
||||||
|
/// One of Option or Variable must be specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Option { get; set; }
|
||||||
|
|
||||||
|
public PJSIPNotifyAction(string endpoint, string option)
|
||||||
|
{
|
||||||
|
Endpoint = endpoint;
|
||||||
|
Option = option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue