Compare commits
5 commits
bff4b2675c
...
ea21d62c03
Author | SHA1 | Date | |
---|---|---|---|
snow flurry | ea21d62c03 | ||
snow flurry | 4268e4d7d9 | ||
snow flurry | 69c9a0333c | ||
snow flurry | 060b4ee1a1 | ||
snow flurry | fa882a1f4a |
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
[submodule "external/AsterNET"]
|
||||
path = external/AsterNET
|
||||
url = https://git.2ki.xyz/snow/AsterNET.git
|
||||
branch = master
|
7
PhoneToolMX.Models/AllowNullAttribute.cs
Normal file
7
PhoneToolMX.Models/AllowNullAttribute.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public class AllowNullAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -38,6 +38,20 @@ namespace PhoneToolMX.Data {
|
|||
var entry = await AddAsync(entity);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a DbSet with all required relationships
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">IOwnedModel</typeparam>
|
||||
/// <returns>DbSet<TEntity></returns>
|
||||
private IQueryable<TEntity> GetFullSet<TEntity>() where TEntity: class, IModel
|
||||
{
|
||||
var set = Set<TEntity>().AsQueryable();
|
||||
return typeof(TEntity).GetProperties()
|
||||
.Where(p => p.GetCustomAttributes(typeof(AlwaysIncludeAttribute), true)
|
||||
.Length != 0)
|
||||
.Aggregate(set, (current, prop) => current.Include(prop.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entities of a certain model owned by the <see cref="User"/>.
|
||||
|
@ -48,14 +62,11 @@ namespace PhoneToolMX.Data {
|
|||
public ICollection<TEntity> GetOwned<TEntity>(User owner) where TEntity : class, IOwnedModel
|
||||
{
|
||||
if (owner == null) return null;
|
||||
var entity = Set<TEntity>().Where(x => x.Owners.Any(o => o.Id == owner.Id));
|
||||
var entity = GetFullSet<TEntity>().Where(x => x.Owners.Any(o => o.Id == owner.Id));
|
||||
// eager load all w/ AlwaysInclude
|
||||
entity = typeof(TEntity).GetProperties()
|
||||
.Where(p => p.GetCustomAttributes(typeof(AlwaysIncludeAttribute), true)
|
||||
.Length != 0)
|
||||
.Aggregate(entity, (current, prop) => current.Include(prop.Name));
|
||||
return entity.ToList();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a model entity by its Id
|
||||
|
@ -67,7 +78,7 @@ namespace PhoneToolMX.Data {
|
|||
/// </remarks>
|
||||
public TEntity GetEntityById<TEntity>(int? id) where TEntity : class, IModel
|
||||
{
|
||||
return id == null ? null : Set<TEntity>().FirstOrDefault(o => o.Id == id);
|
||||
return id == null ? null : GetFullSet<TEntity>().FirstOrDefault(o => o.Id == id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -79,6 +90,7 @@ namespace PhoneToolMX.Data {
|
|||
ext.HasKey(x => x.Id);
|
||||
ext.Property(x => x.Id).UseIdentityColumn();
|
||||
ext.Property(x => x.ExtId).HasComputedColumnSql("\"Id\" + 1000", stored: true);
|
||||
// Randomly generates a 24-char password
|
||||
ext
|
||||
.Property(p => p.Password)
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
@ -89,7 +101,7 @@ namespace PhoneToolMX.Data {
|
|||
.WithMany(x => x.Phones);
|
||||
phone.HasKey(p => p.Id);
|
||||
phone.Property(p => p.Id).UseIdentityColumn();
|
||||
// Randomly generates a 24-char password
|
||||
phone.HasOne(p => p.PrimaryExtension).WithMany();
|
||||
|
||||
// Wallpapers, ringtones, etc
|
||||
var cd = modelBuilder.Entity<CustomData>();
|
||||
|
|
361
PhoneToolMX.Models/Migrations/20231020205952_PrimaryExtension.Designer.cs
generated
Normal file
361
PhoneToolMX.Models/Migrations/20231020205952_PrimaryExtension.Designer.cs
generated
Normal file
|
@ -0,0 +1,361 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using PhoneToolMX.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
[DbContext(typeof(PTMXContext))]
|
||||
[Migration("20231020205952_PrimaryExtension")]
|
||||
partial class PrimaryExtension
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.23")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ExtensionPhone", b =>
|
||||
{
|
||||
b.Property<int>("ExtensionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("PhonesId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ExtensionsId", "PhonesId");
|
||||
|
||||
b.HasIndex("PhonesId");
|
||||
|
||||
b.ToTable("ExtensionPhone");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionUser", b =>
|
||||
{
|
||||
b.Property<int>("ExtensionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("OwnersId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ExtensionsId", "OwnersId");
|
||||
|
||||
b.HasIndex("OwnersId");
|
||||
|
||||
b.ToTable("ExtensionUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<int>("DataType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<int?>("PhoneId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PhoneId");
|
||||
|
||||
b.ToTable("CustomData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("ExtId")
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("integer")
|
||||
.HasComputedColumnSql("\"Id\" + 1000", true);
|
||||
|
||||
b.Property<int?>("HoldMusicId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("Listed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("HoldMusicId");
|
||||
|
||||
b.ToTable("Extensions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<int?>("BackgroundId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<PhysicalAddress>("MacAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("macaddr");
|
||||
|
||||
b.Property<int>("ModelId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("PrimaryExtension")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BackgroundId");
|
||||
|
||||
b.HasIndex("ModelId");
|
||||
|
||||
b.ToTable("Phones");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.PhoneModel", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<long>("MaxExtensions")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ModelName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PreVvxPolycom")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PhoneModels");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 0,
|
||||
MaxExtensions = 6L,
|
||||
ModelName = "Polycom VVX300/310",
|
||||
PreVvxPolycom = false
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Role", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Role");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneUser", b =>
|
||||
{
|
||||
b.Property<string>("OwnersId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("PhonesId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("OwnersId", "PhonesId");
|
||||
|
||||
b.HasIndex("PhonesId");
|
||||
|
||||
b.ToTable("PhoneUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionPhone", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ExtensionsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhonesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionUser", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ExtensionsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany("Ringtones")
|
||||
.HasForeignKey("PhoneId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.CustomData", "HoldMusic")
|
||||
.WithMany()
|
||||
.HasForeignKey("HoldMusicId");
|
||||
|
||||
b.Navigation("HoldMusic");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.CustomData", "Background")
|
||||
.WithMany()
|
||||
.HasForeignKey("BackgroundId");
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.PhoneModel", "Model")
|
||||
.WithMany()
|
||||
.HasForeignKey("ModelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Background");
|
||||
|
||||
b.Navigation("Model");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneUser", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhonesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.Navigation("Ringtones");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
public partial class PrimaryExtension : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PrimaryExtension",
|
||||
table: "Phones",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PrimaryExtension",
|
||||
table: "Phones");
|
||||
}
|
||||
}
|
||||
}
|
369
PhoneToolMX.Models/Migrations/20231020213049_PrimaryExtensionAsExtension.Designer.cs
generated
Normal file
369
PhoneToolMX.Models/Migrations/20231020213049_PrimaryExtensionAsExtension.Designer.cs
generated
Normal file
|
@ -0,0 +1,369 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using PhoneToolMX.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
[DbContext(typeof(PTMXContext))]
|
||||
[Migration("20231020213049_PrimaryExtensionAsExtension")]
|
||||
partial class PrimaryExtensionAsExtension
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.23")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ExtensionPhone", b =>
|
||||
{
|
||||
b.Property<int>("ExtensionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("PhonesId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ExtensionsId", "PhonesId");
|
||||
|
||||
b.HasIndex("PhonesId");
|
||||
|
||||
b.ToTable("ExtensionPhone");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionUser", b =>
|
||||
{
|
||||
b.Property<int>("ExtensionsId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("OwnersId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ExtensionsId", "OwnersId");
|
||||
|
||||
b.HasIndex("OwnersId");
|
||||
|
||||
b.ToTable("ExtensionUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<byte[]>("Data")
|
||||
.HasColumnType("bytea");
|
||||
|
||||
b.Property<int>("DataType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<int?>("PhoneId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PhoneId");
|
||||
|
||||
b.ToTable("CustomData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<string>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("ExtId")
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("integer")
|
||||
.HasComputedColumnSql("\"Id\" + 1000", true);
|
||||
|
||||
b.Property<int?>("HoldMusicId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("Listed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("HoldMusicId");
|
||||
|
||||
b.ToTable("Extensions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<int?>("BackgroundId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<PhysicalAddress>("MacAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("macaddr");
|
||||
|
||||
b.Property<int>("ModelId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("PrimaryExtensionId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BackgroundId");
|
||||
|
||||
b.HasIndex("ModelId");
|
||||
|
||||
b.HasIndex("PrimaryExtensionId");
|
||||
|
||||
b.ToTable("Phones");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.PhoneModel", b =>
|
||||
{
|
||||
b.Property<int?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||
|
||||
b.Property<long>("MaxExtensions")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ModelName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PreVvxPolycom")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PhoneModels");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = 0,
|
||||
MaxExtensions = 6L,
|
||||
ModelName = "Polycom VVX300/310",
|
||||
PreVvxPolycom = false
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Role", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Role");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneUser", b =>
|
||||
{
|
||||
b.Property<string>("OwnersId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("PhonesId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("OwnersId", "PhonesId");
|
||||
|
||||
b.HasIndex("PhonesId");
|
||||
|
||||
b.ToTable("PhoneUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionPhone", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ExtensionsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhonesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExtensionUser", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ExtensionsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany("Ringtones")
|
||||
.HasForeignKey("PhoneId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.CustomData", "HoldMusic")
|
||||
.WithMany()
|
||||
.HasForeignKey("HoldMusicId");
|
||||
|
||||
b.Navigation("HoldMusic");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.CustomData", "Background")
|
||||
.WithMany()
|
||||
.HasForeignKey("BackgroundId");
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.PhoneModel", "Model")
|
||||
.WithMany()
|
||||
.HasForeignKey("ModelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Extension", "PrimaryExtension")
|
||||
.WithMany()
|
||||
.HasForeignKey("PrimaryExtensionId");
|
||||
|
||||
b.Navigation("Background");
|
||||
|
||||
b.Navigation("Model");
|
||||
|
||||
b.Navigation("PrimaryExtension");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneUser", b =>
|
||||
{
|
||||
b.HasOne("PhoneToolMX.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnersId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PhonesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||
{
|
||||
b.Navigation("Ringtones");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
public partial class PrimaryExtensionAsExtension : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PrimaryExtension",
|
||||
table: "Phones",
|
||||
newName: "PrimaryExtensionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Phones_PrimaryExtensionId",
|
||||
table: "Phones",
|
||||
column: "PrimaryExtensionId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Phones_Extensions_PrimaryExtensionId",
|
||||
table: "Phones",
|
||||
column: "PrimaryExtensionId",
|
||||
principalTable: "Extensions",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Phones_Extensions_PrimaryExtensionId",
|
||||
table: "Phones");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Phones_PrimaryExtensionId",
|
||||
table: "Phones");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "PrimaryExtensionId",
|
||||
table: "Phones",
|
||||
newName: "PrimaryExtension");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -141,12 +141,17 @@ namespace PhoneToolMX.Models.Migrations
|
|||
b.Property<int>("ModelId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("PrimaryExtensionId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BackgroundId");
|
||||
|
||||
b.HasIndex("ModelId");
|
||||
|
||||
b.HasIndex("PrimaryExtensionId");
|
||||
|
||||
b.ToTable("Phones");
|
||||
});
|
||||
|
||||
|
@ -326,9 +331,15 @@ namespace PhoneToolMX.Models.Migrations
|
|||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("PhoneToolMX.Models.Extension", "PrimaryExtension")
|
||||
.WithMany()
|
||||
.HasForeignKey("PrimaryExtensionId");
|
||||
|
||||
b.Navigation("Background");
|
||||
|
||||
b.Navigation("Model");
|
||||
|
||||
b.Navigation("PrimaryExtension");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PhoneUser", b =>
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace PhoneToolMX.Models
|
|||
foreach (var prop in GetType().GetProperties().Where(p => p.CanWrite)) {
|
||||
if (prop.GetValue(obj, null) is {} propVal) {
|
||||
prop.SetValue(this, propVal, null);
|
||||
} else if (prop.GetCustomAttributes(typeof(AllowNullAttribute)).FirstOrDefault() != null) {
|
||||
prop.SetValue(this, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,10 @@ namespace PhoneToolMX.Models {
|
|||
|
||||
[AlwaysInclude]
|
||||
public ICollection<Extension> Extensions { get; set; }
|
||||
|
||||
public CustomData Background { get; set; }
|
||||
public ICollection<CustomData> Ringtones { get; set; }
|
||||
|
||||
public Extension PrimaryExtension { get; set; }
|
||||
}
|
||||
}
|
|
@ -53,5 +53,7 @@ namespace PhoneToolMX.Models.ViewModels
|
|||
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 int? Id { get; set; }
|
||||
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx);
|
||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
||||
|
||||
public IViewModel FromEntity(IOwnedModel entity);
|
||||
|
||||
public string NotifyOnChange();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Models;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
|
@ -10,7 +8,7 @@ namespace PhoneToolMX.Models.ViewModels
|
|||
public class PhoneVM : IViewModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
|
||||
[Header("MAC Address")]
|
||||
[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")]
|
||||
|
@ -25,18 +23,25 @@ namespace PhoneToolMX.Models.ViewModels
|
|||
|
||||
public ICollection<int?> Extensions { get; set; }
|
||||
|
||||
[AllowNull]
|
||||
public int? PrimaryExtension { get; set; }
|
||||
|
||||
public int MaxExtensions { get; set; }
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx)
|
||||
{
|
||||
var exts = Extensions?.Select(x => ctx.Extensions.FirstOrDefault(e => e.Id == x)).ToList();
|
||||
return new Phone
|
||||
{
|
||||
Id = Id,
|
||||
MacAddress = PhysicalAddress.Parse(MacAddress),
|
||||
FriendlyName = FriendlyName,
|
||||
Model = ctx.PhoneModels.FirstOrDefault(p => p.Id == Model)!,
|
||||
Extensions = Extensions?.Select(x => ctx.Extensions.FirstOrDefault(e => e.Id == x)).ToList(),
|
||||
Extensions = exts,
|
||||
Owners = new List<User>(),
|
||||
PrimaryExtension = PrimaryExtension != null
|
||||
? exts?.FirstOrDefault(x => x.Id == PrimaryExtension)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -58,7 +63,13 @@ namespace PhoneToolMX.Models.ViewModels
|
|||
Model = phoneEnt.Model!.Id,
|
||||
Extensions = phoneEnt.Extensions?.Select(x => x.Id).ToList(),
|
||||
MaxExtensions = (int)phoneEnt.Model!.MaxExtensions,
|
||||
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.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -5,6 +6,7 @@ using PhoneToolMX.Data;
|
|||
using PhoneToolMX.Helpers;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
using PhoneToolMX.Services;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
@ -118,7 +120,7 @@ namespace PhoneToolMX.Controllers
|
|||
|
||||
SetMessage(
|
||||
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 });
|
||||
|
@ -170,9 +172,22 @@ namespace PhoneToolMX.Controllers
|
|||
_context.Set<T>().Update(currentModel);
|
||||
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(
|
||||
FormMessageType.Success,
|
||||
$"{typeof(T).Name} {GetFriendlyName(vm)} was updated."
|
||||
$"{typeof(T).Name} {GetFriendlyName(currentModel)} was updated.{" " + amiStatus}"
|
||||
);
|
||||
|
||||
return RedirectToAction("Edit", new { id = entity.Id });
|
||||
|
|
|
@ -17,8 +17,10 @@ namespace PhoneToolMX.Controllers
|
|||
{
|
||||
var myExts = _context.GetOwned<Extension>(await CurrentUser());
|
||||
var phoneModels = _context.PhoneModels.ToList();
|
||||
var selectedExts = pvm?.Extensions == null ? null : myExts.Where(x => pvm.Extensions.Contains(x.Id)).ToList();
|
||||
ViewBag.MyExtensions = myExts;
|
||||
ViewBag.SelectedExtensions = pvm?.Extensions == null ? null : myExts.Where(x => pvm.Extensions.Contains(x.Id)).ToList();
|
||||
ViewBag.SelectedExtensions = selectedExts;
|
||||
ViewBag.PrimaryExtension = selectedExts?.FirstOrDefault(x => x.Id == (pvm.PrimaryExtension ?? -1));
|
||||
ViewBag.ModelNumbers = phoneModels;
|
||||
ViewBag.CurrentModel = pvm?.Model == null ? null : phoneModels.Where(m => m.Id == pvm.Model);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\external\AsterNET\Asterisk.2013\Asterisk.NET\AsterNET.csproj" />
|
||||
<ProjectReference Include="..\PhoneToolMX.Models\PhoneToolMX.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.Options;
|
|||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using NuGet.Packaging;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Services;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
@ -39,11 +40,8 @@ builder.Services.AddIdentityCore<User>(opts =>
|
|||
.AddUserManager<UserManager<User>>()
|
||||
.AddEntityFrameworkStores<PTMXContext>();
|
||||
|
||||
Console.WriteLine("Testing one two");
|
||||
|
||||
var proxyConfig = builder.Configuration.GetSection("Proxies");
|
||||
if (proxyConfig?.GetSection("TrustedProxies")?.Get<IList<string>>() is {} trustedProxies) {
|
||||
Console.WriteLine("Got trusted proxies!");
|
||||
builder.Services.Configure<ForwardedHeadersOptions>(opts =>
|
||||
{
|
||||
opts.KnownProxies.AddRange(trustedProxies.Select(IPAddress.Parse));
|
||||
|
@ -115,6 +113,11 @@ builder.Services.AddAuthorization(opts =>
|
|||
policy => policy.RequireRole("Administrator"));
|
||||
});
|
||||
|
||||
// AMI for PJSIP notifications
|
||||
if (builder.Configuration.GetSection("ami").Exists()) {
|
||||
builder.Services.AddSingleton<IAsteriskManager, AsteriskManager>();
|
||||
}
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,14 @@
|
|||
<th>@Html.LabelFor(m => m.Extensions, "Extensions:")</th>
|
||||
<td>@Html.ListBoxFor(m => m.Extensions, new MultiSelectList(ViewBag.MyExtensions, "Id", "ListViewName", ViewBag.SelectedExtensions))
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.PrimaryExtension, "Primary Extension:")</th>
|
||||
<td>@if (ViewBag.SelectedExtensions == null || ((ICollection<Extension>)ViewBag.SelectedExtensions).Count == 0) {
|
||||
@Html.DropDownListFor(m => m.PrimaryExtension, new List<SelectListItem>(), "-- No Extensions Selected --", new { @disabled=true })
|
||||
} else {
|
||||
@Html.DropDownListFor(m => m.PrimaryExtension, new SelectList(ViewBag.SelectedExtensions, "Id", "ListViewName", ViewBag.PrimaryExtension), "None")
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="right">
|
||||
<input type="reset" value="Reset" />
|
||||
|
|
|
@ -54,6 +54,14 @@ namespace PolyProv.Controllers
|
|||
public IActionResult PhoneSettings(string addr)
|
||||
{
|
||||
var phone = GetByMacStr(addr);
|
||||
|
||||
// ReSharper disable once InvertIf
|
||||
if (phone?.PrimaryExtension is {} primary && phone.Extensions.FirstOrDefault() != primary) {
|
||||
// primary extension exists and is not first, reorder so it's first
|
||||
phone.Extensions.Remove(primary);
|
||||
phone.Extensions = phone.Extensions.Prepend(primary).ToList();
|
||||
}
|
||||
|
||||
return phone != null ? View("Phone", phone) : NotFound();
|
||||
}
|
||||
|
||||
|
|
1
external/AsterNET
vendored
Submodule
1
external/AsterNET
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit ae89dc5c61343bb8f3990fbf34656fc9555aae75
|
|
@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneToolMX.Models", "Phone
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolyProv", "PolyProv\PolyProv.csproj", "{18199094-90AE-4C58-BE9E-65D486ECCC38}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsterNET", "external\AsterNET\Asterisk.2013\Asterisk.NET\AsterNET.csproj", "{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -39,5 +41,11 @@ Global
|
|||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.Build.0 = Release|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
Loading…
Reference in a new issue