Plumbling put in place to set Lync SipAddress and LineUri
This commit is contained in:
parent
ad52464658
commit
65ab6722fd
10 changed files with 597 additions and 432 deletions
|
@ -29,7 +29,7 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:2.0.50727.6387
|
||||
// Runtime Version:2.0.50727.6400
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
|
@ -52,8 +52,6 @@ namespace WebsitePanel.EnterpriseServer {
|
|||
using WebsitePanel.Providers.ResultObjects;
|
||||
|
||||
|
||||
|
||||
|
||||
/// <remarks/>
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute()]
|
||||
|
@ -85,6 +83,8 @@ namespace WebsitePanel.EnterpriseServer {
|
|||
|
||||
private System.Threading.SendOrPostCallback GetLyncUserGeneralSettingsOperationCompleted;
|
||||
|
||||
private System.Threading.SendOrPostCallback SetLyncUserGeneralSettingsOperationCompleted;
|
||||
|
||||
private System.Threading.SendOrPostCallback SetUserLyncPlanOperationCompleted;
|
||||
|
||||
private System.Threading.SendOrPostCallback GetFederationDomainsOperationCompleted;
|
||||
|
@ -134,6 +134,9 @@ namespace WebsitePanel.EnterpriseServer {
|
|||
/// <remarks/>
|
||||
public event GetLyncUserGeneralSettingsCompletedEventHandler GetLyncUserGeneralSettingsCompleted;
|
||||
|
||||
/// <remarks/>
|
||||
public event SetLyncUserGeneralSettingsCompletedEventHandler SetLyncUserGeneralSettingsCompleted;
|
||||
|
||||
/// <remarks/>
|
||||
public event SetUserLyncPlanCompletedEventHandler SetUserLyncPlanCompleted;
|
||||
|
||||
|
@ -680,6 +683,56 @@ namespace WebsitePanel.EnterpriseServer {
|
|||
}
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/SetLyncUserGeneralSettings", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
|
||||
public bool SetLyncUserGeneralSettings(int itemId, int accountId, string sipAddress, string lineUri) {
|
||||
object[] results = this.Invoke("SetLyncUserGeneralSettings", new object[] {
|
||||
itemId,
|
||||
accountId,
|
||||
sipAddress,
|
||||
lineUri});
|
||||
return ((bool)(results[0]));
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public System.IAsyncResult BeginSetLyncUserGeneralSettings(int itemId, int accountId, string sipAddress, string lineUri, System.AsyncCallback callback, object asyncState) {
|
||||
return this.BeginInvoke("SetLyncUserGeneralSettings", new object[] {
|
||||
itemId,
|
||||
accountId,
|
||||
sipAddress,
|
||||
lineUri}, callback, asyncState);
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public bool EndSetLyncUserGeneralSettings(System.IAsyncResult asyncResult) {
|
||||
object[] results = this.EndInvoke(asyncResult);
|
||||
return ((bool)(results[0]));
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public void SetLyncUserGeneralSettingsAsync(int itemId, int accountId, string sipAddress, string lineUri) {
|
||||
this.SetLyncUserGeneralSettingsAsync(itemId, accountId, sipAddress, lineUri, null);
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public void SetLyncUserGeneralSettingsAsync(int itemId, int accountId, string sipAddress, string lineUri, object userState) {
|
||||
if ((this.SetLyncUserGeneralSettingsOperationCompleted == null)) {
|
||||
this.SetLyncUserGeneralSettingsOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSetLyncUserGeneralSettingsOperationCompleted);
|
||||
}
|
||||
this.InvokeAsync("SetLyncUserGeneralSettings", new object[] {
|
||||
itemId,
|
||||
accountId,
|
||||
sipAddress,
|
||||
lineUri}, this.SetLyncUserGeneralSettingsOperationCompleted, userState);
|
||||
}
|
||||
|
||||
private void OnSetLyncUserGeneralSettingsOperationCompleted(object arg) {
|
||||
if ((this.SetLyncUserGeneralSettingsCompleted != null)) {
|
||||
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
|
||||
this.SetLyncUserGeneralSettingsCompleted(this, new SetLyncUserGeneralSettingsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
|
||||
}
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/SetUserLyncPlan", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
|
||||
public LyncUserResult SetUserLyncPlan(int itemId, int accountId, int lyncUserPlanId) {
|
||||
|
@ -1177,6 +1230,32 @@ namespace WebsitePanel.EnterpriseServer {
|
|||
}
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
|
||||
public delegate void SetLyncUserGeneralSettingsCompletedEventHandler(object sender, SetLyncUserGeneralSettingsCompletedEventArgs e);
|
||||
|
||||
/// <remarks/>
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
|
||||
[System.Diagnostics.DebuggerStepThroughAttribute()]
|
||||
[System.ComponentModel.DesignerCategoryAttribute("code")]
|
||||
public partial class SetLyncUserGeneralSettingsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
|
||||
|
||||
private object[] results;
|
||||
|
||||
internal SetLyncUserGeneralSettingsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
|
||||
base(exception, cancelled, userState) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public bool Result {
|
||||
get {
|
||||
this.RaiseExceptionIfNecessary();
|
||||
return ((bool)(this.results[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("wsdl", "2.0.50727.3038")]
|
||||
public delegate void SetUserLyncPlanCompletedEventHandler(object sender, SetUserLyncPlanCompletedEventArgs e);
|
||||
|
|
|
@ -228,7 +228,7 @@ namespace WebsitePanel.EnterpriseServer.Code.HostedSolution
|
|||
|
||||
LyncUserPlan plan = GetLyncUserPlan(itemId, lyncUserPlanId);
|
||||
|
||||
if (!lync.CreateUser(org.OrganizationId, user.PrimaryEmailAddress, plan))
|
||||
if (!lync.CreateUser(org.OrganizationId, user.UserPrincipalName, plan))
|
||||
{
|
||||
TaskManager.CompleteResultTask(res, LyncErrorCodes.CANNOT_ADD_LYNC_USER);
|
||||
return res;
|
||||
|
@ -318,7 +318,7 @@ namespace WebsitePanel.EnterpriseServer.Code.HostedSolution
|
|||
usr = OrganizationController.GetAccount(itemId, accountId);
|
||||
|
||||
if (usr != null)
|
||||
user = lync.GetLyncUserGeneralSettings(org.OrganizationId, usr.PrimaryEmailAddress);
|
||||
user = lync.GetLyncUserGeneralSettings(org.OrganizationId, usr.UserPrincipalName);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
|
@ -341,6 +341,59 @@ namespace WebsitePanel.EnterpriseServer.Code.HostedSolution
|
|||
|
||||
}
|
||||
|
||||
public static bool SetLyncUserGeneralSettings(int itemId, int accountId, string sipAddress, string lineUri)
|
||||
{
|
||||
TaskManager.StartTask("LYNC", "SET_LYNC_USER_GENERAL_SETTINGS");
|
||||
|
||||
LyncUser user = null;
|
||||
bool ret = true;
|
||||
|
||||
try
|
||||
{
|
||||
Organization org = (Organization)PackageController.GetPackageItem(itemId);
|
||||
if (org == null)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
string.Format("Organization is null. ItemId={0}", itemId));
|
||||
}
|
||||
|
||||
int lyncServiceId = GetLyncServiceID(org.PackageId);
|
||||
LyncServer lync = GetLyncServer(lyncServiceId, org.ServiceId);
|
||||
|
||||
OrganizationUser usr;
|
||||
usr = OrganizationController.GetAccount(itemId, accountId);
|
||||
|
||||
if (usr != null)
|
||||
user = lync.GetLyncUserGeneralSettings(org.OrganizationId, usr.UserPrincipalName);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
LyncUserPlan plan = ObjectUtils.FillObjectFromDataReader<LyncUserPlan>(DataProvider.GetLyncUserPlanByAccountId(accountId));
|
||||
|
||||
if (plan != null)
|
||||
{
|
||||
user.LyncUserPlanId = plan.LyncUserPlanId;
|
||||
user.LyncUserPlanName = plan.LyncUserPlanName;
|
||||
}
|
||||
|
||||
user.PrimaryUri = sipAddress;
|
||||
user.LineUri = lineUri;
|
||||
|
||||
lync.SetLyncUserGeneralSettings(org.OrganizationId, usr.UserPrincipalName, user);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ret = false;
|
||||
throw TaskManager.WriteError(ex);
|
||||
}
|
||||
TaskManager.CompleteTask();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int DeleteOrganization(int itemId)
|
||||
{
|
||||
// check account
|
||||
|
@ -403,7 +456,7 @@ namespace WebsitePanel.EnterpriseServer.Code.HostedSolution
|
|||
OrganizationUser user;
|
||||
user = OrganizationController.GetAccount(itemId, accountId);
|
||||
|
||||
if (!lync.SetLyncUserPlan(org.OrganizationId, user.PrimaryEmailAddress, plan))
|
||||
if (!lync.SetLyncUserPlan(org.OrganizationId, user.UserPrincipalName, plan))
|
||||
{
|
||||
TaskManager.CompleteResultTask(res, LyncErrorCodes.CANNOT_ADD_LYNC_USER);
|
||||
return res;
|
||||
|
@ -518,7 +571,7 @@ namespace WebsitePanel.EnterpriseServer.Code.HostedSolution
|
|||
user = OrganizationController.GetAccount(itemId, accountId);
|
||||
|
||||
if (user != null)
|
||||
lync.DeleteUser(user.PrimaryEmailAddress);
|
||||
lync.DeleteUser(user.UserPrincipalName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -5,11 +5,19 @@
|
|||
</configSections>
|
||||
<!-- Connection strings -->
|
||||
<connectionStrings>
|
||||
<add name="EnterpriseServer" connectionString="Server=(local)\SQLExpress;Database=WebsitePanel;uid=sa;pwd=Password12" providerName="System.Data.SqlClient" />
|
||||
<!--
|
||||
<add name="EnterpriseServer" connectionString="server=HSTPROV01;database=WebsitePanelMerge;uid=WebsitePanel;pwd=aj7ep6fyhmw3b5qeth7c;" />
|
||||
<add name="EnterpriseServer" connectionString="server=HSTWSP01;database=WebsitePanelMerge;uid=WebsitePanel;pwd=pserxfbnlc6hwmdedbp0;" providerName="System.Data.SqlClient" />
|
||||
-->
|
||||
<add name="EnterpriseServer" connectionString="server=HSTPROV01;database=WebsitePanelMerge;uid=WebsitePanel;pwd=aj7ep6fyhmw3b5qeth7c;" />
|
||||
</connectionStrings>
|
||||
<appSettings>
|
||||
<!-- Encryption util settings -->
|
||||
<add key="WebsitePanel.CryptoKey" value="1234567890" />
|
||||
<!-- A1D4KDHUE83NKHddF -->
|
||||
<!--
|
||||
<add key="WebsitePanel.CryptoKey" value="3x7eqt7zabc5n5afs6dg" />
|
||||
<add key="WebsitePanel.CryptoKey" value="fr2ym4wn2gmbrj7dz336" />
|
||||
-->
|
||||
<add key="WebsitePanel.CryptoKey" value="3x7eqt7zabc5n5afs6dg" />
|
||||
<!-- A1D4KDHUE83NKHddF -->
|
||||
<add key="WebsitePanel.EncryptionEnabled" value="true" />
|
||||
<!-- Web Applications -->
|
||||
|
|
|
@ -119,6 +119,13 @@ namespace WebsitePanel.EnterpriseServer
|
|||
return LyncController.GetLyncUserGeneralSettings(itemId, accountId);
|
||||
}
|
||||
|
||||
[WebMethod]
|
||||
public bool SetLyncUserGeneralSettings(int itemId, int accountId, string sipAddress, string lineUri)
|
||||
{
|
||||
return LyncController.SetLyncUserGeneralSettings(itemId, accountId, sipAddress, lineUri);
|
||||
}
|
||||
|
||||
|
||||
[WebMethod]
|
||||
public LyncUserResult SetUserLyncPlan(int itemId, int accountId, int lyncUserPlanId)
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace WebsitePanel.Providers.HostedSolution
|
|||
|
||||
bool CreateUser(string organizationId, string userUpn, LyncUserPlan plan);
|
||||
LyncUser GetLyncUserGeneralSettings(string organizationId, string userUpn);
|
||||
bool SetLyncUserGeneralSettings(string organizationId, string userUpn, LyncUser lyncUser);
|
||||
bool SetLyncUserPlan(string organizationId, string userUpn, LyncUserPlan plan);
|
||||
bool DeleteUser(string userUpn);
|
||||
|
||||
|
|
|
@ -127,6 +127,12 @@ namespace WebsitePanel.Providers.HostedSolution
|
|||
return GetLyncUserGeneralSettingsInternal(organizationId, userUpn);
|
||||
}
|
||||
|
||||
public bool SetLyncUserGeneralSettings(string organizationId, string userUpn, LyncUser lyncUser)
|
||||
{
|
||||
return SetLyncUserGeneralSettingsInternal(organizationId, userUpn, lyncUser);
|
||||
}
|
||||
|
||||
|
||||
public bool SetLyncUserPlan(string organizationId, string userUpn, LyncUserPlan plan)
|
||||
{
|
||||
return SetLyncUserPlanInternal(organizationId, userUpn, plan, null);
|
||||
|
@ -524,6 +530,40 @@ namespace WebsitePanel.Providers.HostedSolution
|
|||
return lyncUser;
|
||||
}
|
||||
|
||||
private bool SetLyncUserGeneralSettingsInternal(string organizationId, string userUpn, LyncUser lyncUser)
|
||||
{
|
||||
HostedSolutionLog.LogStart("SetLyncUserGeneralSettingsInternal");
|
||||
HostedSolutionLog.DebugInfo("organizationId: {0}", organizationId);
|
||||
HostedSolutionLog.DebugInfo("userUpn: {0}", userUpn);
|
||||
|
||||
bool ret = true;
|
||||
Runspace runSpace = null;
|
||||
try
|
||||
{
|
||||
runSpace = OpenRunspace();
|
||||
|
||||
Command cmd = new Command("Set-CsUser");
|
||||
cmd.Parameters.Add("Identity", userUpn);
|
||||
if (!string.IsNullOrEmpty(lyncUser.PrimaryUri)) cmd.Parameters.Add("SipAddress", lyncUser.PrimaryUri);
|
||||
if (!string.IsNullOrEmpty(lyncUser.PrimaryUri)) cmd.Parameters.Add("LineUri", lyncUser.LineUri);
|
||||
|
||||
ExecuteShellCommand(runSpace, cmd, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ret = false;
|
||||
HostedSolutionLog.LogError("SetLyncUserGeneralSettingsInternal", ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseRunspace(runSpace);
|
||||
}
|
||||
HostedSolutionLog.LogEnd("SetLyncUserGeneralSettingsInternal");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool SetLyncUserPlanInternal(string organizationId, string userUpn, LyncUserPlan plan, Runspace runSpace)
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -124,6 +124,24 @@ namespace WebsitePanel.Server
|
|||
}
|
||||
}
|
||||
|
||||
[WebMethod, SoapHeader("settings")]
|
||||
public bool SetLyncUserGeneralSettings(string organizationId, string userUpn, LyncUser lyncUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.WriteStart("{0}.SetLyncUserGeneralSettings", ProviderSettings.ProviderName);
|
||||
bool ret = Lync.SetLyncUserGeneralSettings(organizationId, userUpn, lyncUser);
|
||||
Log.WriteEnd("{0}.SetLyncUserGeneralSettings", ProviderSettings.ProviderName);
|
||||
return ret;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WriteError(String.Format("Error: {0}.SetLyncUserGeneralSettings", ProviderSettings.ProviderName), ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[WebMethod, SoapHeader("settings")]
|
||||
public bool SetLyncUserPlan(string organizationId, string userUpn, LyncUserPlan plan)
|
||||
{
|
||||
|
|
|
@ -65,8 +65,8 @@ REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\PackagesProxy.cs
|
|||
REM %WSDL% %SERVER_URL%/esScheduler.asmx /out:.\WebsitePanel.EnterpriseServer.Client\SchedulerProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\SchedulerProxy.cs
|
||||
|
||||
%WSDL% %SERVER_URL%/esServers.asmx /out:.\WebsitePanel.EnterpriseServer.Client\ServersProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
%WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\ServersProxy.cs
|
||||
REM %WSDL% %SERVER_URL%/esServers.asmx /out:.\WebsitePanel.EnterpriseServer.Client\ServersProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\ServersProxy.cs
|
||||
|
||||
REM %WSDL% %SERVER_URL%/esSharePointServers.asmx /out:.\WebsitePanel.EnterpriseServer.Client\SharePointServersProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\SharePointServersProxy.cs
|
||||
|
@ -92,8 +92,8 @@ REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\WebServersProxy.cs
|
|||
REM %WSDL% %SERVER_URL%/esVirtualizationServerForPrivateCloud.asmx /out:.\WebsitePanel.EnterpriseServer.Client\VirtualizationServerProxyForPrivateCloud.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\VirtualizationServerProxyForPrivateCloud.cs
|
||||
|
||||
REM %WSDL% %SERVER_URL%/esLync.asmx /out:.\WebsitePanel.EnterpriseServer.Client\LyncProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
REM %WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\LyncProxy.cs
|
||||
%WSDL% %SERVER_URL%/esLync.asmx /out:.\WebsitePanel.EnterpriseServer.Client\LyncProxy.cs /namespace:WebsitePanel.EnterpriseServer /type:webClient
|
||||
%WSE_CLEAN% .\WebsitePanel.EnterpriseServer.Client\LyncProxy.cs
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -50,13 +50,13 @@ REM %WSE_CLEAN% .\WebsitePanel.Server.Client\VirtualizationServerProxy.cs
|
|||
REM %WSDL% %SERVER_URL%/VirtualizationServerForPrivateCloud.asmx /out:.\WebsitePanel.Server.Client\VirtualizationServerForPrivateCloudProxy.cs /namespace:WebsitePanel.Providers.VirtualizationForPC /type:webClient /fields
|
||||
REM %WSE_CLEAN% .\WebsitePanel.Server.Client\VirtualizationServerForPrivateCloudProxy.cs
|
||||
|
||||
%WSDL% %SERVER_URL%/WebServer.asmx /out:.\WebsitePanel.Server.Client\WebServerProxy.cs /namespace:WebsitePanel.Providers.Web /type:webClient /fields
|
||||
%WSE_CLEAN% .\WebsitePanel.Server.Client\WebServerProxy.cs
|
||||
REM %WSDL% %SERVER_URL%/WebServer.asmx /out:.\WebsitePanel.Server.Client\WebServerProxy.cs /namespace:WebsitePanel.Providers.Web /type:webClient /fields
|
||||
REM %WSE_CLEAN% .\WebsitePanel.Server.Client\WebServerProxy.cs
|
||||
|
||||
REM %WSDL% %SERVER_URL%/WindowsServer.asmx /out:.\WebsitePanel.Server.Client\WindowsServerProxy.cs /namespace:WebsitePanel.Server /type:webClient /fields
|
||||
REM %WSE_CLEAN% .\WebsitePanel.Server.Client\WindowsServerProxy.cs
|
||||
|
||||
REM %WSDL% %SERVER_URL%/LyncServer.asmx /out:.\WebsitePanel.Server.Client\LyncServerProxy.cs /namespace:WebsitePanel.Providers.Lync /type:webClient /fields
|
||||
REM %WSE_CLEAN% .\WebsitePanel.Server.Client\LyncServerProxy.cs
|
||||
%WSDL% %SERVER_URL%/LyncServer.asmx /out:.\WebsitePanel.Server.Client\LyncServerProxy.cs /namespace:WebsitePanel.Providers.Lync /type:webClient /fields
|
||||
%WSE_CLEAN% .\WebsitePanel.Server.Client\LyncServerProxy.cs
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue