Working on tenants and config
This commit is contained in:
parent
c10de11bbe
commit
f3352318f5
12 changed files with 309 additions and 19 deletions
|
|
@ -41,7 +41,7 @@ namespace PlanTempus
|
||||||
|
|
||||||
builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule
|
builder.RegisterModule(new Core.ModuleRegistry.TelemetryModule
|
||||||
{
|
{
|
||||||
TelemetryConfig = ConfigurationRoot.GetSection("ApplicationInsights").Get<Core.ModuleRegistry.TelemetryConfig>()
|
TelemetryConfig = ConfigurationRoot.GetSection(nameof(Core.ModuleRegistry.TelemetryConfig)).Get<Core.ModuleRegistry.TelemetryConfig>()
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
{
|
{
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=postgres;Password=3911;"
|
"ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=sathumper;Password=3911;"
|
||||||
},
|
},
|
||||||
"SendInBlue": {
|
"TelemetryConfig": {
|
||||||
"APIkey": "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"
|
|
||||||
},
|
|
||||||
"ApplicationInsights": {
|
|
||||||
"ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548"
|
"ConnectionString": "InstrumentationKey=07d2a2b9-5e8e-4924-836e-264f8438f6c5;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/;LiveEndpoint=https://northeurope.livediagnostics.monitor.azure.com/;ApplicationId=56748c39-2fa3-4880-a1e2-24068e791548"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
57
Core/Configurations/Class1.cs
Normal file
57
Core/Configurations/Class1.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Configurations
|
||||||
|
{
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
public class KeyValueNester
|
||||||
|
{
|
||||||
|
private JObject _rootObject = new JObject();
|
||||||
|
|
||||||
|
public void AddKeyValue(string key, string jsonValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Parse input JSON value
|
||||||
|
var valueObject = JsonConvert.DeserializeObject<JObject>(jsonValue);
|
||||||
|
var parts = key.Split(':');
|
||||||
|
|
||||||
|
// Start med root object eller nuværende struktur
|
||||||
|
JObject current = _rootObject;
|
||||||
|
|
||||||
|
// Gennemgå hver del af key'en
|
||||||
|
for (int i = 0; i < parts.Length - 1; i++)
|
||||||
|
{
|
||||||
|
string part = parts[i];
|
||||||
|
if (!current.ContainsKey(part))
|
||||||
|
{
|
||||||
|
current[part] = new JObject();
|
||||||
|
}
|
||||||
|
current = (JObject)current[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tilføj den sidste værdi
|
||||||
|
current[parts[^1]] = valueObject;
|
||||||
|
}
|
||||||
|
catch (JsonReaderException ex)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid JSON value", nameof(jsonValue), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JObject GetResult()
|
||||||
|
{
|
||||||
|
return _rootObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_rootObject = new JObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs
Normal file
115
Core/Configurations/PostgresqlConfigurationBuilder/Class1.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Npgsql;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Configurations.PostgresqlConfigurationBuilder
|
||||||
|
{
|
||||||
|
public class PostgresConfigurationSource : IConfigurationSource
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
private readonly string _channel;
|
||||||
|
private readonly string _configurationQuery;
|
||||||
|
|
||||||
|
public PostgresConfigurationSource(string connectionString, string channel, string configurationQuery)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
|
||||||
|
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||||
|
_configurationQuery = configurationQuery ?? throw new ArgumentNullException(nameof(configurationQuery));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfigurationProvider Build(IConfigurationBuilder builder)
|
||||||
|
{
|
||||||
|
return new PostgresConfigurationProvider(_connectionString, _channel, _configurationQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PostgresConfigurationProvider : ConfigurationProvider, IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
private readonly string _channel;
|
||||||
|
private readonly string _configurationQuery;
|
||||||
|
private readonly NpgsqlConnection _listenerConnection;
|
||||||
|
private bool _disposedValue;
|
||||||
|
|
||||||
|
public PostgresConfigurationProvider(string connectionString, string channel, string configurationQuery)
|
||||||
|
{
|
||||||
|
_connectionString = connectionString;
|
||||||
|
_channel = channel;
|
||||||
|
_configurationQuery = configurationQuery;
|
||||||
|
_listenerConnection = new NpgsqlConnection(connectionString);
|
||||||
|
|
||||||
|
// Start listening for notifications
|
||||||
|
StartListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartListening()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _listenerConnection.OpenAsync();
|
||||||
|
_listenerConnection.Notification += OnNotificationReceived;
|
||||||
|
|
||||||
|
using var cmd = new NpgsqlCommand($"LISTEN {_channel};", _listenerConnection);
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log error and possibly retry
|
||||||
|
Console.WriteLine($"Error starting listener: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNotificationReceived(object sender, NpgsqlNotificationEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Channel == _channel)
|
||||||
|
{
|
||||||
|
// Reload configuration and notify
|
||||||
|
Load();
|
||||||
|
OnReload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Load()
|
||||||
|
{
|
||||||
|
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
using (var connection = new NpgsqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
using var cmd = new NpgsqlCommand(_configurationQuery, connection);
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
string key = reader.GetString(0);
|
||||||
|
string value = reader.GetString(1);
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_listenerConnection?.Dispose();
|
||||||
|
}
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs
Normal file
34
Core/Configurations/PostgresqlConfigurationBuilder/Class2.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Core.Configurations.PostgresqlConfigurationBuilder
|
||||||
|
{
|
||||||
|
public static class PostgresConfigurationExtensions
|
||||||
|
{
|
||||||
|
public static IConfigurationBuilder AddPostgresConfiguration(
|
||||||
|
this IConfigurationBuilder builder,
|
||||||
|
Action<PostgresConfigurationOptions> setupAction)
|
||||||
|
{
|
||||||
|
var options = new PostgresConfigurationOptions();
|
||||||
|
setupAction(options);
|
||||||
|
|
||||||
|
builder.Add(new PostgresConfigurationSource(
|
||||||
|
options.ConnectionString,
|
||||||
|
options.Channel,
|
||||||
|
options.ConfigurationQuery));
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PostgresConfigurationOptions
|
||||||
|
{
|
||||||
|
public string ConnectionString { get; set; }
|
||||||
|
public string Channel { get; set; }
|
||||||
|
public string ConfigurationQuery { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,6 @@ namespace Core.ModuleRegistry
|
||||||
|
|
||||||
public class TelemetryConfig
|
public class TelemetryConfig
|
||||||
{
|
{
|
||||||
public string InstrumentationKey { get; set; }
|
|
||||||
public string ConnectionString { get; set; }
|
public string ConnectionString { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
namespace Database.Tenants
|
namespace Database.Tenants
|
||||||
{
|
{
|
||||||
|
|
@ -18,13 +14,13 @@ namespace Database.Tenants
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateTenant(string schema)
|
public async Task CreateTenantInDatabase(string schema, string user, string password)
|
||||||
{
|
{
|
||||||
if (!Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"))
|
if (!Regex.IsMatch(schema, "^[a-zA-Z0-9_]+$"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Invalid schema name");
|
throw new ArgumentException("Invalid schema name");
|
||||||
}
|
}
|
||||||
|
await CreateUser(user, password);
|
||||||
await CreateSchema(schema);
|
await CreateSchema(schema);
|
||||||
await CreateRolesTable(schema);
|
await CreateRolesTable(schema);
|
||||||
await CreatePermissionsTable(schema);
|
await CreatePermissionsTable(schema);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.10.35013.160
|
VisualStudioVersion = 17.10.35013.160
|
||||||
|
|
@ -9,7 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{111CE8AE-E637-4376-A5A3-88D33E77EA88}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Database", "Database\Database.csproj", "{D5096A7F-E6D4-4C87-874E-2D9A6BEAD57F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPostgresLISTEN", "TestPostgresLISTEN\TestPostgresLISTEN.csproj", "{743EF625-6C74-419C-A492-AA069956F471}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
@ -17,6 +18,10 @@ Global
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{743EF625-6C74-419C-A492-AA069956F471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{743EF625-6C74-419C-A492-AA069956F471}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{743EF625-6C74-419C-A492-AA069956F471}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{743EF625-6C74-419C-A492-AA069956F471}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7B554252-1CE4-44BD-B108-B0BDCCB24742}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
|
||||||
51
TestPostgresLISTEN/Program.cs
Normal file
51
TestPostgresLISTEN/Program.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using Npgsql;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
// Tilpas connection string til dine behov
|
||||||
|
var connectionString = "Host=192.168.1.57;Database=ptdb01;Username=postgres;Password=3911";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using NpgsqlConnection conn = new NpgsqlConnection(connectionString);
|
||||||
|
await conn.OpenAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Forbundet til databasen. Lytter efter notifikationer...");
|
||||||
|
|
||||||
|
// Opsæt notification handling
|
||||||
|
conn.Notification += (o, e) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Notifikation modtaget:");
|
||||||
|
Console.WriteLine($" PID: {e.PID}");
|
||||||
|
Console.WriteLine($" Kanal: {e.Channel}");
|
||||||
|
Console.WriteLine($" Payload: {e.Payload}");
|
||||||
|
Console.WriteLine("------------------------");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start lytning
|
||||||
|
await using (var cmd = new NpgsqlCommand("LISTEN config_changes;", conn))
|
||||||
|
{
|
||||||
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold programmet kørende og lyt efter notifikationer
|
||||||
|
Console.WriteLine("Tryk på en tast for at stoppe...");
|
||||||
|
|
||||||
|
// Mens vi venter på input, skal vi huske at wait for notifikationer
|
||||||
|
while (!Console.KeyAvailable)
|
||||||
|
{
|
||||||
|
// Wait for notification for 1 second, then continue loop
|
||||||
|
await conn.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Der opstod en fejl: {ex.Message}");
|
||||||
|
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
TestPostgresLISTEN/TestPostgresLISTEN.csproj
Normal file
13
TestPostgresLISTEN/TestPostgresLISTEN.csproj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Core\Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Insight.Database;
|
using Insight.Database;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Core.Configurations.PostgresqlConfigurationBuilder;
|
||||||
namespace Tests
|
namespace Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class UnitTest1 : TestFixture
|
public class UnitTest1 : TestFixture
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MyTestMethod()
|
public void TestDefaultConnection()
|
||||||
{
|
{
|
||||||
var conn = Container.Resolve<IDbConnection>();
|
var conn = Container.Resolve<IDbConnection>();
|
||||||
//https://www.reddit.com/r/dotnet/comments/6wdoyn/how_to_properly_register_dapper_on_net_core_2_di/
|
//https://www.reddit.com/r/dotnet/comments/6wdoyn/how_to_properly_register_dapper_on_net_core_2_di/
|
||||||
|
|
@ -16,8 +19,28 @@ namespace Tests
|
||||||
|
|
||||||
conn.ExecuteSql("SELECT 1 as p");
|
conn.ExecuteSql("SELECT 1 as p");
|
||||||
|
|
||||||
var sql = "SELECT * FROM public.Foo";
|
var sql = "SELECT * FROM swp.foo";
|
||||||
var customers = conn.Query(sql, commandType:CommandType.Text);
|
var customers = conn.Query(sql, commandType:CommandType.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TryTenantSetupService()
|
||||||
|
{
|
||||||
|
var conn = Container.Resolve<IDbConnection>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MyTestMethod()
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.AddPostgresConfiguration(options =>
|
||||||
|
{
|
||||||
|
options.ConnectionString = "Host=192.168.1.57;Database=ptdb01;Username=postgres;Password=3911";
|
||||||
|
options.Channel = "config_changes";
|
||||||
|
options.ConfigurationQuery = @"select * from dev.app_configuration";
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"ptdb": "Host=localhost;Port=5433;Database=ptdb01;User Id=postgres;Password=3911;"
|
"ptdb": "Host=192.168.1.57;Port=5432;Database=ptdb01;User Id=sathumper;Password=3911;"
|
||||||
},
|
},
|
||||||
"ApplicationInsights": {
|
"ApplicationInsights": {
|
||||||
"ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/"
|
"ConnectionString": "InstrumentationKey=6d2e76ee-5343-4691-a5e3-81add43cb584;IngestionEndpoint=https://northeurope-0.in.applicationinsights.azure.com/"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue