commit 4112d6019f6a7cc5ab0a6f81fa788d80f3c54974 Author: Meik Date: Wed Jan 28 12:27:00 2026 +0100 Initialer Status diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..688980b --- /dev/null +++ b/.gitignore @@ -0,0 +1,81 @@ +# -------------------------------------------------------------------------------------------------- +# Core Visual Studio / .NET build outputs +# -------------------------------------------------------------------------------------------------- +bin/ +obj/ +[Bb]uild/ +[Dd]ebug*/ +[Rr]elease*/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +_ReSharper.*/ +*.user +*.userosscache +*.suo +*.cache +*.pdb +*.mpdb +*.opendb +*.VC.db +*.vscode/ + +# -------------------------------------------------------------------------------------------------- +# Package artefacts (NuGet, npm, etc.) +# -------------------------------------------------------------------------------------------------- +/packages/ +*.nupkg +*.snupkg +*.nuspec +package-lock.json +packages.config +npm-debug.log* +yarn-error.log + +# -------------------------------------------------------------------------------------------------- +# TFS / TFVC specific files and folders +# -------------------------------------------------------------------------------------------------- +$tf/ +*$tf/ +*.tf +*.tfs +.tfignore + +# -------------------------------------------------------------------------------------------------- +# IDE tooling and workspace settings +# -------------------------------------------------------------------------------------------------- +.vs/ +.vscode/ +*.swp +*.sln.docstates +*.VC.VC.opendb +.idea/ +.DS_Store +Thumbs.db + +# -------------------------------------------------------------------------------------------------- +# Test results and profiling data +# -------------------------------------------------------------------------------------------------- +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.trx +*.coverage +*.coveragexml +*.opencover.xml +*.dotCover +BenchmarkDotNet.Artifacts/ + +# -------------------------------------------------------------------------------------------------- +# Miscellaneous recommended ignores +# -------------------------------------------------------------------------------------------------- +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +*.tmp +*.temp +*.log +*.bak +*.orig +*.scc + diff --git a/C4IT - F4SD - M42WebApi.sln b/C4IT - F4SD - M42WebApi.sln new file mode 100644 index 0000000..e5e1f32 --- /dev/null +++ b/C4IT - F4SD - M42WebApi.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33424.131 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F4SD - M42WebApi", "F4SDM42WebApi\F4SD - M42WebApi.csproj", "{D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{01521575-9B5F-4E86-8F40-634119D46B9F}" + ProjectSection(SolutionItems) = preProject + SharedAssemblyInfo.cs = SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "F4SD - Helper", "F4SDHelper\F4SD - Helper.csproj", "{EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "F4SD - M42 UUX Workspace", "M42F4SDUUXWorkspace\F4SD - M42 UUX Workspace.shproj", "{C3D52A04-2461-4F35-8EDF-DE7226ADD677}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug_and_copy|Any CPU = Debug_and_copy|Any CPU + Debug|Any CPU = Debug|Any CPU + Release_and_copy|Any CPU = Release_and_copy|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Debug_and_copy|Any CPU.ActiveCfg = Debug_and_copy|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Debug_and_copy|Any CPU.Build.0 = Debug_and_copy|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Release_and_copy|Any CPU.ActiveCfg = Release_and_copy|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Release_and_copy|Any CPU.Build.0 = Release_and_copy|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE}.Release|Any CPU.Build.0 = Release|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Debug_and_copy|Any CPU.ActiveCfg = Debug|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Debug_and_copy|Any CPU.Build.0 = Debug|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Release_and_copy|Any CPU.ActiveCfg = Release_and_copy|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Release_and_copy|Any CPU.Build.0 = Release_and_copy|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EEEA7DD2-9C5A-4AAE-B5CA-77E4C7F1257B} + EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems*{c3d52a04-2461-4f35-8edf-de7226add677}*SharedItemsImports = 13 + M42F4SDUUXWorkspace\M42F4SDUUXWorkspace.projitems*{d8cbffca-0b43-4acc-80ea-c944e7420cee}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(TeamFoundationVersionControl) = preSolution + SccNumberOfProjects = 5 + SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + SccTeamFoundationServer = https://consulting4it.visualstudio.com/ + SccLocalPath0 = . + SccProjectUniqueName1 = F4SDHelper\\F4SD\u0020-\u0020Helper.csproj + SccProjectName1 = F4SDHelper + SccLocalPath1 = F4SDHelper + SccProjectUniqueName2 = F4SDM42WebApi\\F4SD\u0020-\u0020M42WebApi.csproj + SccProjectName2 = F4SDM42WebApi + SccLocalPath2 = F4SDM42WebApi + SccProjectUniqueName3 = M42F4SDUUXWorkspace\\F4SD\u0020-\u0020M42\u0020UUX\u0020Workspace.shproj + SccProjectName3 = M42F4SDUUXWorkspace + SccLocalPath3 = M42F4SDUUXWorkspace + SccProjectUniqueName4 = M42F4SDUUXWorkspace\\M42F4SDUUXWorkspace.projitems + SccProjectTopLevelParentUniqueName4 = M42F4SDUUXWorkspace\\F4SD\u0020-\u0020M42\u0020UUX\u0020Workspace.shproj + SccProjectName4 = M42F4SDUUXWorkspace + SccLocalPath4 = M42F4SDUUXWorkspace + EndGlobalSection +EndGlobal diff --git a/F4SDHelper/App.config b/F4SDHelper/App.config new file mode 100644 index 0000000..d9d17a6 --- /dev/null +++ b/F4SDHelper/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/F4SDHelper/Common/C4IT.FASD.Base.cs b/F4SDHelper/Common/C4IT.FASD.Base.cs new file mode 100644 index 0000000..20d265e --- /dev/null +++ b/F4SDHelper/Common/C4IT.FASD.Base.cs @@ -0,0 +1,500 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using static C4IT.Logging.cLogManager; + +namespace C4IT.FASD.Base +{ + public enum enumFasdInformationClass + { + Unknown = -1, + Main = 0, + Computer = 1, + User = 2, + Ticket = 3 + } + + public enum enumF4sdSearchResultClass + { + Unknown = -1, + Computer = 0, + User = 1, + Phone = 2, + Ticket = 3 + } + + public enum enumF4sdSearchResultStatus + { + Unknown = -1, + Inactive = 0, + Active = 1 + } + + public enum enumFasdValueType + { + Unknown = 0, + STRING = 1, + GUID = 2, + INT = 3, + BIGINT = 4, + FLOAT = 5, + DATETIME = 6, + VERSION = 7, + MD5 = 8, + SID = 9, + IPV4 = 10, + IPV6 = 11, + TEXT = 12, + BOOLEAN = 13, + AUTOID = 255 + }; + + public enum enumFasdCharCaseType { none = 0, lower = 1, upper = 2 }; + + public enum enumFasdConfigurationType + { + unknown = 0, + commonIcons, + menuSections, + quickActions, + copyTemplates, + healthCard + } + + public class cFasdApiConnectionInfo + { + public string ServerVersion { get; set; } + public string MinCockpitVersion { get; set; } + public UInt64 ConfigRevision { get; set; } = 0; + } + + public class cAgentApiConfiguration + { + public string LogonUrl { get; set; } + public string ApiUrl { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public int? OrganizationCode { get; set; } + + [JsonIgnore] + public static cAgentApiConfiguration Instance { get; set; } + } + + public class cFasdApiSearchResultDefault + { + public enumF4sdSearchResultClass Type { get; set; } + public string Name { get; set; } + public Guid id { get; set; } + public enumF4sdSearchResultStatus Status { get; set; } = enumF4sdSearchResultStatus.Unknown; + public Dictionary Infos { get; set; } = null; + + static public enumF4sdSearchResultClass getSearchClass(enumFasdInformationClass infoClass) + { + switch (infoClass) + { + case enumFasdInformationClass.User: + return enumF4sdSearchResultClass.User; + case enumFasdInformationClass.Computer: + return enumF4sdSearchResultClass.Computer; + default: + return enumF4sdSearchResultClass.Unknown; + } + + } + } + + public class cF4sdIdentityEntry + { + public enumFasdInformationClass Class { get; set; } + public Guid Id { get; set; } + + public static enumFasdInformationClass GetFromSearchResult(enumF4sdSearchResultClass searchResult) + { + var infoClass = enumFasdInformationClass.Unknown; + switch (searchResult) + { + case enumF4sdSearchResultClass.Computer: + infoClass = enumFasdInformationClass.Computer; + break; + case enumF4sdSearchResultClass.User: + case enumF4sdSearchResultClass.Phone: + infoClass = enumFasdInformationClass.User; + break; + } + + return infoClass; + } + } + + public class cF4sdApiSearchResultRelation : cFasdApiSearchResultDefault + { + public DateTime LastUsed { get; set; } + public double UsingLevel { get; set; } + public List Identities { get; set; } = null; + } + + public class cF4sdHealthCardRawDataRequest + { + public List Tables { get; set; } + public List Identities { get; set; } + + public int MaxAge { get; set; } = 14; + + public DateTime RefTime { get; set; } = DateTime.Now.Date.ToUniversalTime(); + } + + public class cF4SDHealthCardRawData + { + public class cHealthCardTableColumn + { + public string ColumnName { get; set; } + public List Values { get; set; } = new List(); + public bool IsIncomplete { get; set; } = false; + } + + public class cHealthCardTable + { + public string Name { get; set; } + public int StartingIndex { get; set; } = 0; + public bool IsIncomplete { get; set; } = false; + public bool IsStatic { get; set; } + public DateTime[,] TimeFrames { get; set; } + public Dictionary Columns { get; set; } = new Dictionary(); + } + + public class cHealthCardDetailsTable + { + public string Name { get; set; } + public List Columns { get; set; } + public Dictionary> Values { get; set; } + } + + public Guid Id { get; set; } + + public Dictionary Tables { get; set; } = new Dictionary(); + + #region GetObjects + + #region GetInteger + + public static int? GetInteger(object Value, int? Default = null) + { + int? output = Default; + + if (Value == null) + return output; + + try + { + var valuetype = Value.GetType(); + + if (Value is int @int) + output = @int; + if (Value is Int64 @int64) + output = Convert.ToInt32(@int64); + else if (Value is double @double) + output = Convert.ToInt32(@double); + else if (Value is string @string) + { + if (int.TryParse(@string, out int parsedInt)) + output = parsedInt; + else + output = Default; + } + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + public int? GetIntegerByAddressInfo(string TableName, string TableColumn, int index = 0, int? Default = null) + { + int? output = Default; + + try + { + if (Tables.TryGetValue(TableName, out var selectedTable)) + if (selectedTable.Columns.TryGetValue(TableColumn, out var selectedColumn)) + output = GetInteger(selectedColumn.Values[index], Default); + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + #endregion + + #region GetString + + public static string GetString(object Value, string Default = null) + { + string output = Default; + try + { + if (Value is int @int) + output = Convert.ToString(@int); + else if (Value is double @double) + output = Convert.ToString(@double); + else if (Value is string @string) + output = @string; + } + catch (Exception E) + { + LogException(E); + } + + return output?.ToString(); + } + + public string GetStringByAddressInfo(string TableName, string TableColumn, int index = 0, string Default = null) + { + string output = Default; + + try + { + if (Tables.TryGetValue(TableName, out var selectedTable)) + if (selectedTable.Columns.TryGetValue(TableColumn, out var selectedColumn)) + output = GetString(selectedColumn.Values[index], Default); + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + #endregion + + #region GetVersion + + public static Version GetVersion(object Value, Version Default = null) + { + Version output = Default; + + if (Value == null) + return output; + + try + { + if (Value is string @string) + { + if (Version.TryParse(@string, out var tempVersionFromString)) + output = tempVersionFromString; + } + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + public Version GetVersionByAddressInfo(string TableName, string TableColumn, int index = 0, Version Default = null) + { + Version output = Default; + + try + { + if (Tables.TryGetValue(TableName, out var selectedTable)) + if (selectedTable.Columns.TryGetValue(TableColumn, out var selectedColumn)) + output = GetVersion(selectedColumn.Values[index], Default); + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + #endregion + + #region GetDateTime + + public static DateTime? GetDateTime(object Value, DateTime? Default = null) + { + DateTime? output = Default; + + if (Value == null) + return output; + + try + { + if (Value is DateTime time) + output = time; + else if (Value is string @string) + { + var tempOutput = Convert.ToDateTime(@string); + DateTime.SpecifyKind(tempOutput, DateTimeKind.Utc); + if (tempOutput != null) + output = tempOutput; + else + output = Default; + + } + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + public DateTime? GetDateTimeByAddressInfo(string TableName, string TableColumn, int index = 0, DateTime? Default = null) + { + DateTime? output = Default; + + try + { + if (Tables.TryGetValue(TableName, out var selectedTable)) + if (selectedTable.Columns.TryGetValue(TableColumn, out var selectedColumn)) + output = GetDateTime(selectedColumn.Values[index], Default); + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + #endregion + + #region GetDouble + + public static double? GetDouble(object Value, double? Default = null) + { + double? output = Default; + + try + { + if (Value is int @int) + output = Convert.ToDouble(@int); + else if (Value is Int64 @int64) + output = Convert.ToDouble(@int64); + else if (Value is double @double) + output = @double; + else if (Value is string @string) + { + if (double.TryParse(@string, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.GetCultureInfo("en-US").NumberFormat, out double tempOutput)) + output = tempOutput; + else + output = Default; + } + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + public double? GetDoubleByAddressInfo(string TableName, string TableColumn, int index = 0, double? Default = null) + { + double? output = Default; + + try + { + if (Tables.TryGetValue(TableName, out var selectedTable)) + if (selectedTable.Columns.TryGetValue(TableColumn, out var selectedColumn)) + output = GetDouble(selectedColumn.Values[index], Default); + } + catch (Exception E) + { + LogException(E); + } + + return output; + } + + #endregion + + #endregion + } + public class cApiM42TicketQueueInfo + { + public string QueueName { get; set; } + public Guid QueueID { get; set; } + } + + public class cF4SDTicketSummary + { + public Guid TicketObjectId { get; set; } + public string Name { get; set; } + public string ActivityType { get; set; } + public string Summary { get; set; } + public string Sid { get; set; } + public string Status { get; set; } + public int StatusId { get; set; } + public string Urgency { get; set; } + public int UrgencyId { get; set; } + public string Impact { get; set; } + public int ImpactId { get; set; } + public string AssetCIName { get; set; } + public string AssetName { get; set; } + public string ServiceName { get; set; } + public Guid ServiceId { get; set; } + public bool IsPrimaryAccount { get; set; } + } + + public class cF4SDTicket : cF4SDTicketSummary + { + public enum enumTicketCreationSource + { + Unknown = 0, + Mail = 1, + Phone = 2, + F4SD = 3 + } + + public class cTicketJournalItem + { + public DateTime CreationDate { get; set; } + public string Header { get; set; } + public string CreatedBy { get; set; } + public string DescriptionHtml { get; set; } + public string Description { get; set; } + public bool IsVisibleForUser { get; set; } + public Guid ActivityObjectId { get; set; } + public Guid JournalId { get; set; } + } + + public Guid AffectedUserId { get; set; } + public Guid AssetId { get; set; } + public DateTime CreationDate { get; set; } + public DateTime? ClosingDate { get; set; } + public int CreationSourceId { get; set; } + public string CreationSource { get; set; } + + public string Description { get; set; } + public string DescriptionHtml { get; set; } + public int PriorityId { get; set; } + public string Priority { get; set; } + public Guid CategoryId { get; set; } + public string Category { get; set; } + public string CategoryHierarchical { get; set; } + public string CIName { get; set; } + public string DirectLinkEdit { get; set; } + public Guid AssetCIId { get; set; } + public int AssetSKUAssetGroupId { get; set; } + public string AssetSKUAssetGroup { get; set; } + public int AssetSKUTypeId { get; set; } + public string AssetSKUType { get; set; } + public string DirectLinkPreview { get; set; } + public string DirectLinkClose { get; set; } + public string AffectedUser { get; set; } + public string SolutionHtml { get; set; } + public string Solution { get; set; } + public string AssetDomain { get; set; } + } +} \ No newline at end of file diff --git a/F4SDHelper/F4SD - Helper.csproj b/F4SDHelper/F4SD - Helper.csproj new file mode 100644 index 0000000..5e47625 --- /dev/null +++ b/F4SDHelper/F4SD - Helper.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {EB95E1FB-7A9E-4894-BD28-2D0BE2715EBE} + Library + Properties + C4IT.F4SDM + C4ITF4SDM42WebApiHelper + v4.7.2 + 512 + true + + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + bin\Release_and_copy\ + TRACE + true + pdbonly + AnyCPU + 7.3 + prompt + + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + Common\C4IT.Logging.LogManager.cs + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + \ No newline at end of file diff --git a/F4SDHelper/F4SD - Helper.csproj.vspscc b/F4SDHelper/F4SD - Helper.csproj.vspscc new file mode 100644 index 0000000..feffdec --- /dev/null +++ b/F4SDHelper/F4SD - Helper.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/F4SDHelper/Properties/AssemblyInfo.cs b/F4SDHelper/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8f0c1ee --- /dev/null +++ b/F4SDHelper/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("C4IT - F4SD - WebApi for M42")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("eb95e1fb-7a9e-4894-bd28-2d0be2715ebe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/F4SDM42WebApi/App.config b/F4SDM42WebApi/App.config new file mode 100644 index 0000000..db96dfb --- /dev/null +++ b/F4SDM42WebApi/App.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/F4SDM42WebApi/F4SD - M42WebApi.csproj b/F4SDM42WebApi/F4SD - M42WebApi.csproj new file mode 100644 index 0000000..4aa97e3 --- /dev/null +++ b/F4SDM42WebApi/F4SD - M42WebApi.csproj @@ -0,0 +1,143 @@ + + + + + Debug + AnyCPU + {D8CBFFCA-0B43-4ACC-80EA-C944E7420CEE} + Library + Properties + C4IT.F4SDM + C4ITF4SDM42WebApi + v4.7.2 + 512 + true + + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug_and_copy\ + DEBUG;TRACE + full + AnyCPU + 7.3 + prompt + + + bin\Release_and_copy\ + TRACE + true + pdbonly + AnyCPU + 7.3 + prompt + + + + False + M42Libraries\Matrix42.Common.dll + + + False + M42Libraries\Matrix42.Contracts.Common.dll + + + False + M42Libraries\Matrix42.Contracts.Platform.dll + + + False + M42Libraries\Matrix42.Contracts.ServiceManagement.dll + + + False + M42Libraries\Matrix42.Pandora.Contracts.dll + + + False + M42Libraries\Matrix42.Services.Description.Contracts.dll + + + False + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + False + M42Libraries\System.Web.Http.dll + + + False + M42Libraries\System.Web.OData.dll + + + + + + + + + False + M42Libraries\update4u.SPS.DataLayer.dll + + + False + M42Libraries\update4u.SPS.Utility.dll + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + {eb95e1fb-7a9e-4894-bd28-2d0be2715ebe} + F4SD - Helper + True + + + + + + + + + + + + @echo powershell -ExecutionPolicy Unrestricted $(ProjectDir)deploy.ps1 -ProjectDir $(ProjectDir) -SolutionDir $(SolutionDir) -OutDir $(OutDir) -ConfigurationName $(ConfigurationName) + + \ No newline at end of file diff --git a/F4SDM42WebApi/F4SD - M42WebApi.csproj.vspscc b/F4SDM42WebApi/F4SD - M42WebApi.csproj.vspscc new file mode 100644 index 0000000..feffdec --- /dev/null +++ b/F4SDM42WebApi/F4SD - M42WebApi.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/F4SDM42WebApi/F4SDHelperService.cs b/F4SDM42WebApi/F4SDHelperService.cs new file mode 100644 index 0000000..cbc0e29 --- /dev/null +++ b/F4SDM42WebApi/F4SDHelperService.cs @@ -0,0 +1,1803 @@ +using C4IT.F4SDM; +using C4IT.FASD.Base; +using C4IT.Logging; +using Matrix42.Common; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Dynamic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using update4u.SPS.DataLayer; +using update4u.SPS.DataLayer.Transaction; +using static C4IT.FASD.Base.cF4SDTicket; +using static C4IT.Logging.cLogManager; + +namespace C4IT.F4SD +{ + public class F4SDHelperService + { + private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase"); + private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase"); + private static Guid SPSCommonClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSCommonClassBase"); + private static Guid SPSAccountClassADID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassAD"); + private static Guid SPSActivityClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassBase"); + private static Guid SPSActivityClassIncidentID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassIncident"); + private static Guid SPSActivityClassUnitOfWorkID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSActivityClassUnitOfWork"); + private static Guid SPSScCategoryClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSScCategoryClassBase"); + private static Guid SPSAssetClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAssetClassBase"); + private static Guid SPSSecurityClassRole = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSSecurityClassRole"); + + public static readonly Guid Administrators = new Guid("{A5D7B682-B211-4D94-A96D-8C57EEDAFDEA}"); + + private const string TicketCloseActionId = "51bb3283-7bd1-e511-9a82-60e327035d31"; + private const string TicketDirectLinkCreateTemplate = @"{0}/wm/app-ServiceDesk/notSet/create-object/{1}?view-options=%7B%22embedded%22:false%7D&presetParams={2}"; + private const string IncidentDirectLinkCloseTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/SPSActivityTypeIncident/{1}/0/?view-options={2}"; + private const string TicketDirectLinkPreviewTemplate = @"{0}/wm/app-ServiceDesk/notSet/preview-object/{1}/{2}/0/"; + private const string TicketDirectLinkEditTemplate = @"{0}/wm/app-ServiceDesk/notSet/edit-object/{1}/{2}"; + + private const string placeHolderSubject = "PARAM_SUBJECT"; + private const string placeHolderDescription = "PARAM_DESCRIPTION"; + private const string c4itf4sdmonLinkBase = "f4sdsend://localhost"; + + private const string F4SDTicketTableName = "M42WPM-TICKETS-INFOS"; + private const string F4SDTicketStatusColumnName = "STATUS"; + + + public static IEnumerable ReadLines(Func streamProvider, + Encoding encoding) + { + using (var stream = streamProvider()) + using (var reader = new StreamReader(stream, encoding)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + } + + internal async Task getDirectLinkF4SD(Guid EOID, string type) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Generating F4SD URI for type: '{type}', GUID: '{EOID}'", LogLevels.Debug); + var builder = new UriBuilder(c4itf4sdmonLinkBase); + var queryString = HttpUtility.ParseQueryString(builder.Query); + + switch (type.ToLowerInvariant().Split('.').Last()) + { + case "person": + case "user": + var user = getUsersByAsql($"[Expression-ObjectId]='{EOID}'")?.FirstOrDefault(); + string Sids = string.Empty; + + if (user != null) + { + var accounts = getAccountsByAsql($"Owner='{user.Id}'"); + Sids = string.Join(",", accounts?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty()); + } + + if (!string.IsNullOrEmpty(user?.Name)) + { + queryString.Add("command", "UserSidSearch"); + queryString.Add("name", user.Name); + queryString.Add("sids", Sids); + } + break; + + case "asset": + var asset = getAssetsByAsql($"UsedInTypeSPSComputerType is not null AND [Expression-ObjectId]='{EOID}'")?.FirstOrDefault(); + if (asset != null) + { + queryString.Add("command", "ComputerDomainSearch"); + queryString.Add("name", asset.Name); + queryString.Add("domain", asset.DomainName); + } + break; + + case "incident": + case "servicerequest": + case "ticket": + var ticket = (await getTicketDetails(new List { EOID }))?.FirstOrDefault(); + if (ticket != null) + { + queryString.Add("command", "TicketSearch"); + queryString.Add("tname", ticket.Name); + queryString.Add("tid", ticket.TicketObjectId.ToString()); + + if (ticket.AffectedUserId != Guid.Empty) + { + var initiator = getUsersByAsql($"ID = '{ticket.AffectedUserId}'")?.FirstOrDefault(); + string userName = initiator?.Name; + Sids = string.Join(",", getAccountsByAsql($"Owner='{ticket.AffectedUserId}'")?.Where(x => !string.IsNullOrEmpty(x.Sid)).Select(x => x.Sid) ?? Array.Empty()); + + queryString.Add("uname", userName); + queryString.Add("sids", Sids); + } + } + break; + + default: + break; + } + + builder.Query = queryString.ToString(); + return string.IsNullOrEmpty(builder.Query) ? null : builder.ToString(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + + internal async Task> getTicketListByUser(string userSid, int hours, int queueoption, List queues) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Generating ticket list for userSid: '{userSid}', {hours} hours", LogLevels.Debug); + await Task.Delay(0); + + var tickets = new List(); + var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues); + LogEntry($"ASql Filter: {activityFilter}"); + + var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + string.Format("[Expression-ObjectID] as EOID, TicketNumber, Subject, Service.ID as ServiceId, Service.Name as ServiceName, SUBQUERY(BasicSchemaObjectType as bso, bso.Name, bso.id = base.T(SPSCommonClassBase).TypeID) as ActivityType" + + ", COALESCE(T(SPSActivityClassIncident).Asset.T(SPSComputerClassBase).Name, T(SPSActivityClassIncident).Asset.T(SPSAssetClassSIMCard).PhoneNumber, T(SPSActivityClassIncident).Asset.Name, T(SPSActivityClassIncident).Asset.objectid) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSActivityClassIncident).Asset.T(SPSCommonClassBase).TypeID) as AssetCIName" + + ", CASE WHEN Initiator.PrimaryAccount.T(SPSAccountClassAd).Sid = '{0}' THEN 1 ELSE 0 END AS IsPrimaryAccount", + userSid), + activityFilter); + + if (activityTable?.Rows == null || activityTable.Rows.Count == 0) + { + LogEntry($"No activity entries found for userSid: '{userSid}'", LogLevels.Warning); + return null; + } + + for (int i = 0; i < activityTable.Rows.Count; i++) + { + DataRow entry = activityTable.Rows[i]; + var activityEOId = getGuidFromObject(entry["EOID"]); + var ticketNumber = getStringFromObject(entry["TicketNumber"]); + var activityType = getStringFromObject(entry["ActivityType"]); + var assetName = getStringFromObject(entry["AssetName"]); + var ServiceName = getStringFromObject(entry["ServiceName"]); + var assetCIName = getStringFromObject(entry["AssetCIName"]); + var subject = getStringFromObject(entry["Subject"]); + + var stateData = GetActivityState(activityEOId); + var state = stateData.Item1; + var stateDisp = stateData.Item2; + + LogEntry($"Activity found {i + 1}/{activityTable.Rows.Count}: ObjectID={activityEOId}, TicketNumber={ticketNumber}, Subject={subject}, State={state}", LogLevels.Debug); + + if (string.IsNullOrEmpty(ticketNumber)) + { + LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning); + continue; + } + + if (string.IsNullOrEmpty(assetName)) + { + LogEntry($"No AssetName found for activity entry", LogLevels.Debug); + } + + if (string.IsNullOrEmpty(assetCIName)) + { + LogEntry($"No AssetCIName found for activity entry", LogLevels.Debug); + } + var ServiceId = getGuidFromObject(entry["ServiceId"]); + if (ServiceId == Guid.Empty) + { + LogEntry($"no ServiceId found for activity entry", LogLevels.Debug); + } + var AssetCIName = getStringFromObject(entry["AssetCIName"]); + if (AssetCIName == string.Empty) + { + LogEntry($"no AssetCIName found for activity entry", LogLevels.Debug); + } + var Subject = getStringFromObject(entry["Subject"]); + if (Subject == string.Empty) + { + LogEntry($"no Subject found for activity entry", LogLevels.Warning); + continue; + } + var IsPrimaryAccount = getIntFromObject(entry["IsPrimaryAccount"]); + + if (string.IsNullOrEmpty(subject)) + { + LogEntry($"No Subject found for activity entry", LogLevels.Warning); + continue; + } + + tickets.Add(new cF4SDTicketSummary() + { + TicketObjectId = activityEOId, + Name = ticketNumber, + ActivityType = activityType, + AssetCIName = assetCIName, + AssetName = assetName, + ServiceId = ServiceId, + ServiceName = ServiceName, + StatusId = state, + Status = stateDisp, + Summary = subject, + IsPrimaryAccount = IsPrimaryAccount == 1 + }); + } + + tickets = tickets.OrderByDescending(x => x.Name).ToList(); + return tickets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private static readonly string[] TicketOverviewKeys = new[] + { + "TicketsNew", + "TicketsActive", + "TicketsCritical", + "TicketsNewInfo", + "IncidentNew", + "IncidentActive", + "IncidentCritical", + "IncidentNewInfo", + "UnassignedTickets", + "UnassignedTicketsCritical" + }; + + public class TicketOverviewCountsResult + { + [JsonProperty("counts")] + public Dictionary Counts { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public class TicketOverviewRelationDto + { + public enumF4sdSearchResultClass Type { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public Guid id { get; set; } + public enumF4sdSearchResultStatus Status { get; set; } = enumF4sdSearchResultStatus.Unknown; + public Dictionary Infos { get; set; } = null; + public List Identities { get; set; } = null; + } + + private sealed class TicketOverviewEntry + { + public Guid TicketId { get; set; } + public string TicketNumber { get; set; } + public string Summary { get; set; } + public Guid InitiatorId { get; set; } + public string InitiatorDisplayName { get; set; } + public string InitiatorAccount { get; set; } + public string InitiatorDomain { get; set; } + public Guid RecipientId { get; set; } + public Guid RecipientRoleId { get; set; } + public int State { get; set; } + public DateTime CreatedDate { get; set; } + public bool NewInformationReceived { get; set; } + public bool ReactionTimeEscalated { get; set; } + public bool SolutionTimeEscalated { get; set; } + public bool IsIncident { get; set; } + } + + internal async Task getTicketOverviewCounts(string sid, string scope, IEnumerable keys) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var normalizedKeys = (keys ?? Array.Empty()) + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (normalizedKeys.Count == 0) + { + normalizedKeys.AddRange(TicketOverviewKeys); + } + + var entries = await LoadTicketOverviewEntries(sid, useRoleScope); + var counts = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var key in normalizedKeys) + { + counts[key] = entries.Count(entry => MatchesTicketOverviewKey(entry, key)); + } + + return new TicketOverviewCountsResult { Counts = counts }; + } + catch (Exception E) + { + LogException(E); + return new TicketOverviewCountsResult(); + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task> getTicketOverviewRelations(string sid, string scope, string key, int count) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + if (string.IsNullOrWhiteSpace(key)) + return new List(); + + var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase); + var entries = await LoadTicketOverviewEntries(sid, useRoleScope); + + var filtered = entries + .Where(entry => MatchesTicketOverviewKey(entry, key)) + .OrderByDescending(entry => entry.CreatedDate) + .ToList(); + + if (count > 0) + filtered = filtered.Take(count).ToList(); + + var relations = new List(filtered.Count); + foreach (var entry in filtered) + { + var relation = new TicketOverviewRelationDto + { + Type = enumF4sdSearchResultClass.Ticket, + DisplayName = entry.TicketNumber ?? string.Empty, + Name = entry.TicketNumber ?? string.Empty, + id = entry.TicketId, + Status = enumF4sdSearchResultStatus.Active, + Infos = new Dictionary + { + ["Summary"] = entry.Summary ?? string.Empty, + ["StatusId"] = ConvertM42State(entry.State), + ["UserDisplayName"] = entry.InitiatorDisplayName ?? string.Empty, + ["UserAccount"] = entry.InitiatorAccount ?? string.Empty, + ["UserDomain"] = entry.InitiatorDomain ?? string.Empty + }, + Identities = new List + { + new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = entry.TicketId }, + new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = entry.InitiatorId } + } + }; + relations.Add(relation); + } + + return relations; + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task> LoadTicketOverviewEntries(string sid, bool useRoleScope) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + if (string.IsNullOrWhiteSpace(sid)) + return new List(); + + var filter = await BuildTicketOverviewFilterAsync(sid, useRoleScope); + if (string.IsNullOrWhiteSpace(filter)) + return new List(); + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + "[Expression-ObjectID] as EOID" + + ", TicketNumber" + + ", Subject" + + ", Initiator as InitiatorId" + + ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" + + ", Initiator.PrimaryAccount.T(SPSAccountClassAD).NBAccountName as InitiatorAccount" + + ", Initiator.PrimaryAccount.T(SPSAccountClassAD).Domain.NT4Name as InitiatorDomain" + + ", Recipient as RecipientId" + + ", RecipientRole as RecipientRoleId" + + ", T(SPSCommonClassBase).State as State" + + ", CreatedDate" + + ", NewInformationReceived" + + ", ReactionTimeEscalated" + + ", SolutionTimeEscalated" + + ", UsedInTypeSPSActivityTypeIncident as IsIncident", + filter); + + if (tbl?.Rows == null || tbl.Rows.Count == 0) + return new List(); + + var entries = new List(tbl.Rows.Count); + foreach (DataRow row in tbl.Rows) + { + var ticketId = getGuidFromObject(row["EOID"]); + if (ticketId == Guid.Empty) + continue; + + entries.Add(new TicketOverviewEntry + { + TicketId = ticketId, + TicketNumber = getStringFromObject(row["TicketNumber"]), + Summary = getStringFromObject(row["Subject"]), + InitiatorId = getGuidFromObject(row["InitiatorId"]), + InitiatorDisplayName = getStringFromObject(row["Initiator"]), + InitiatorAccount = getStringFromObject(row["InitiatorAccount"]), + InitiatorDomain = getStringFromObject(row["InitiatorDomain"]), + RecipientId = getGuidFromObject(row["RecipientId"]), + RecipientRoleId = getGuidFromObject(row["RecipientRoleId"]), + State = getIntFromObject(row["State"]), + CreatedDate = getDateTimeFromObject(row["CreatedDate"]), + NewInformationReceived = GetBoolValue(row["NewInformationReceived"]), + ReactionTimeEscalated = GetBoolValue(row["ReactionTimeEscalated"]), + SolutionTimeEscalated = GetBoolValue(row["SolutionTimeEscalated"]), + IsIncident = row["IsIncident"] != DBNull.Value + }); + } + + return entries; + } + catch (Exception E) + { + LogException(E); + return new List(); + } + finally + { + LogMethodEnd(CM); + } + } + + private async Task BuildTicketOverviewFilterAsync(string sid, bool useRoleScope) + { + var filter = "T(SPSCommonClassBase).State <> 204"; + + if (ticketAndServiceRequestEnabled()) + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)"; + } + + if (!useRoleScope) + { + filter += $" AND Initiator.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'"; + return filter; + } + + var userId = getUserBySid(sid); + if (userId == Guid.Empty) + return null; + + var roles = await getRoleMembershipById(userId) ?? new List(); + if (roles.Count == 0) + return null; + + var roleIDs = roles + .Select(role => role?.Id) + .Where(Id => !Guid.Empty.Equals(Id)) + .Select(Id => $"'{(Id)}'") + .ToList(); + + if (roleIDs.Count == 0) + return null; + + filter += $" AND RecipientRole.T(SPSSecurityClassRole).ID IN ({string.Join(", ", roleIDs)})"; + return filter; + } + + private static bool MatchesTicketOverviewKey(TicketOverviewEntry entry, string key) + { + if (entry == null || string.IsNullOrWhiteSpace(key)) + return false; + + var isTicket = !entry.IsIncident; + var isAssigned = entry.RecipientId != Guid.Empty || entry.RecipientRoleId != Guid.Empty; + var isCritical = entry.ReactionTimeEscalated || entry.SolutionTimeEscalated; + var isNew = entry.State == 200; + var isActive = entry.State == 201 || entry.State == 202 || entry.State == 203; + + switch (key.Trim()) + { + case "TicketsNew": + return isTicket && isAssigned && isNew; + case "TicketsActive": + return isTicket && isAssigned && isActive; + case "TicketsCritical": + return isTicket && isAssigned && isCritical; + case "TicketsNewInfo": + return isTicket && isAssigned && entry.NewInformationReceived; + case "IncidentNew": + return entry.IsIncident && isNew; + case "IncidentActive": + return entry.IsIncident && isActive; + case "IncidentCritical": + return entry.IsIncident && isCritical; + case "IncidentNewInfo": + return entry.IsIncident && entry.NewInformationReceived; + case "UnassignedTickets": + return isTicket && !isAssigned && isNew; + case "UnassignedTicketsCritical": + return isTicket && !isAssigned && isCritical; + default: + return false; + } + } + + private static bool GetBoolValue(object value) + { + if (value == null || value is DBNull) + return false; + if (value is bool boolValue) + return boolValue; + if (value is int intValue) + return intValue != 0; + if (value is long longValue) + return longValue != 0; + + if (bool.TryParse(value.ToString(), out var parsedBool)) + return parsedBool; + if (int.TryParse(value.ToString(), out var parsedInt)) + return parsedInt != 0; + + return false; + } + + private static string ConvertM42State(int state) + { + switch (state) + { + case 200: + return "New"; + case 201: + case 202: + return "InProgress"; + case 203: + return "OnHold"; + case 204: + return "Closed"; + default: + return "Unknown"; + } + } + private (int, string) GetActivityState(Guid activityEOId) + { + var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID, + "State.Value as State, State.DisplayString as StateDisp", $"[Expression-ObjectID] = '{activityEOId}'"); + if (tbl3?.Rows == null || tbl3.Rows.Count <= 0) + { + LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={activityEOId}", LogLevels.Debug); + return (0, ""); + } + var state = getIntFromObject(tbl3.Rows[0]["State"]); + var stateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]); + return (state, stateDisp); + } + private string Escape(string input) + { + return input?.Replace("'", "''"); + } + private string GetActivityFilter( + string userSid, + int hours, + bool ticketAndServiceRequestEnabled, + int queueoption, + List queues + ) + { + // Baseline-Filter auf User und Datum + var filter = $"Initiator.Accounts.T(SPSAccountClassAd).Sid = '{Escape(userSid)}'"; + + string fStartDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow.AddHours(-hours), true); + string fEndDate = AsqlHelper.PrepareDateTime(DateTime.UtcNow, true); + + // Incident vs. Ticket/ServiceRequest + if (ticketAndServiceRequestEnabled) + { + filter += + " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeTicket IS NOT NULL" + + " OR UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL)"; + } + else + { + filter += " AND (UsedInTypeSPSActivityTypeIncident IS NOT NULL)"; + } + + // Offene bzw. kürzlich geschlossene Objekte + filter += + " AND (T(SPSCommonClassBase).State <> 204" + + $" OR (ClosedDate > {fStartDate} AND ClosedDate < {fEndDate}))"; + + // Queue-Filter nur, wenn tatsächlich Queues übergeben wurden + if (queues != null && queues.Count > 0) + { + // URL-escaping für Name und ID + var escapedNames = queues + .Select(q => $"'{Escape(q.QueueName)}'") + .ToList(); + var escapedIds = queues + .Select(q => $"'{Escape(q.QueueID.ToString())}'") + .ToList(); + + string nameList = string.Join(", ", escapedNames); + string idList = string.Join(", ", escapedIds); + + switch (queueoption) + { + // 1 = entweder keine Queue oder eine der übergebenen Queues (Name oder ID) + case 1: + filter += + $" AND (" + + "Queue IS NULL" + + $" OR Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList})" + + ")"; + break; + + // 2 = nur die übergebenen Queues (Name oder ID) + case 2: + filter += + $" AND (" + + "Queue IS NOT NULL" + + $" AND (Queue.Name IN ({nameList})" + + $" OR Queue.ID IN ({idList}))" + + ")"; + break; + + // 3 = nur Objekte ohne Queue + case 3: + filter += " AND Queue IS NULL"; + break; + + // 0 oder andere = keine zusätzliche Einschränkung + default: + break; + } + } + else if (queueoption == 3) + { + // Ausnahme: wenn keine Queues übergeben, aber Option 3 = nur ohne Queue + filter += " AND Queue IS NULL"; + } + + return filter; + } + + + internal async Task getDirectLinkCreateTicket(string sid, string assetname) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + // get global settings + bool TicketAndServiceRequestEnabled = ticketAndServiceRequestEnabled(); + + var user = getUserBySid(sid); + var asset = getAssetByName(assetname); + dynamic presetParamsDyn = new ExpandoObject(); + + presetParamsDyn.SPSActivityClassBase = new ExpandoObject(); + presetParamsDyn.SPSActivityClassBase.Subject = placeHolderSubject; + presetParamsDyn.SPSActivityClassBase.DescriptionHTML = placeHolderDescription; + if (user != Guid.Empty) + presetParamsDyn.SPSActivityClassBase.Initiator = user; + if (asset != null) + { + presetParamsDyn.SPSActivityClassIncident = new ExpandoObject(); + presetParamsDyn.SPSActivityClassIncident.Asset = asset.Id; + } + var type = TicketAndServiceRequestEnabled ? "SPSActivityTypeTicket" : "SPSActivityTypeIncident"; + var presetParams = HttpUtility.UrlEncode(JsonConvert.SerializeObject(presetParamsDyn)); + var directLink = string.Format(TicketDirectLinkCreateTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, type, presetParams); + return new DirectLink() + { + Link = directLink, + DescriptionParameter = placeHolderDescription, + SubjectParameter = placeHolderSubject + }; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private static bool ticketAndServiceRequestEnabled() + { + var configProvider = F4SDM42WebApiController.defaultInstance._globalConfigurationProvider; + + // using reflection because signature of "ReloadSettings" changed in ESM v12 + var method = configProvider.GetType().GetMethod("ReloadSettings"); + + if (method != null) + { + var defaultParameters = method.GetParameters().Select(p => p.HasDefaultValue ? p.DefaultValue : null).ToArray(); + method.Invoke(configProvider, defaultParameters); + } + + return configProvider.ServiceDeskConfiguration.TicketAndServiceRequestEnabled; + } + + internal async Task> getTicketDetails(List ticketObjectIds) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + + var Filter = AsqlHelper.BuildInCondition("[Expression-ObjectId]", ticketObjectIds); + LogEntry($"ASql Filter: {Filter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, + "[Expression-ObjectId] as EOID" + + ", TicketNumber" + + ", Subject" + + ", Category.Name as Category" + + ", Category as CategoryId" + + ", CreatedDate, ClosedDate" + + ", Initiator as InitiatorId" + + ", Initiator.LastName + ISNULL(', ' + Initiator.FirstName, '') as Initiator" + + ", DescriptionHTML" + + ", Impact" + + ", Impact.DisplayString as ImpactDisp" + + ", Urgency" + + ", Urgency.DisplayString as UrgencyDisp" + + ", Priority" + + ", Priority.Description as PrioDisp" + + ", SolutionHTML", Filter); + + if (tbl == null || tbl.Rows == null) + { + LogEntry($"No activity entry list found with [Expression-ObjectId]='{string.Join(", ", ticketObjectIds)}'", LogLevels.Warning); + return null; + } + + var tickets = new List(); + + foreach (DataRow Entry in tbl.Rows) + { + var ActivityEOID = getGuidFromObject(Entry["EOID"]); + if (ActivityEOID == Guid.Empty) + { + LogEntry($"No expression object id found for activity entry", LogLevels.Warning); + continue; + } + + var InitiatorId = getGuidFromObject(Entry["InitiatorId"]); + var Initiator = getStringFromObject(Entry["Initiator"]); + if (InitiatorId == Guid.Empty) + { + LogEntry($"No Initiator id found for activity entry", LogLevels.Warning); + } + + var TicketNumber = getStringFromObject(Entry["TicketNumber"]); + if (string.IsNullOrEmpty(TicketNumber)) + { + LogEntry($"No TicketNumber found for activity entry", LogLevels.Warning); + continue; + } + + var CategoryId = getGuidFromObject(Entry["CategoryId"]); + if (CategoryId == Guid.Empty) + { + LogEntry($"No Initiator object id found for activity entry", LogLevels.Warning); + continue; + } + + var Category = getStringFromObject(Entry["Category"]); + if (string.IsNullOrEmpty(Category)) + { + LogEntry($"No Category found for activity entry", LogLevels.Warning); + continue; + } + + var CreatedDate = getDateTimeFromObject(Entry["CreatedDate"]); + if (CreatedDate == DateTime.MinValue) + { + LogEntry($"No CreationDate found for activity entry", LogLevels.Warning); + continue; + } + + var ClosedDate = getDateTimeFromObject(Entry["ClosedDate"]); + var DescriptionHtml = getStringFromObject(Entry["DescriptionHTML"]); + var Description = DescriptionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(DescriptionHtml) : string.Empty; + + var SolutionHtml = getStringFromObject(Entry["SolutionHTML"]); + var Solution = SolutionHtml != string.Empty ? Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(SolutionHtml) : string.Empty; + + var Subject = getStringFromObject(Entry["Subject"]); + if (string.IsNullOrEmpty(Subject)) + { + LogEntry($"No Subject found for activity entry", LogLevels.Warning); + continue; + } + + var PriorityId = getIntFromObject(Entry["Priority"]); + var Priority = getStringFromObject(Entry["PrioDisp"]); + + var UrgencyId = getIntFromObject(Entry["Urgency"]); + var Urgency = getStringFromObject(Entry["UrgencyDisp"]); + + var ImpactId = getIntFromObject(Entry["Impact"]); + var Impact = getStringFromObject(Entry["ImpactDisp"]); + + LogEntry($"get category information: {Filter}"); + Filter = string.Format("Recursive(Children).T(SPSScCategoryClassBase).Id='{0}'", CategoryId); + var tbl1 = FragmentRequestBase.SimpleLoad(SPSScCategoryClassBaseID, "Name", Filter); + if (tbl1?.Rows == null || tbl1.Rows.Count <= 0) + { + LogEntry($"No Category entry list found with Initiator='{CategoryId}'", LogLevels.Warning); + return null; + } + + var CategoryHierarchical = string.Join(" > ", tbl1.Rows.Cast().Reverse().Select(row => getStringFromObject(row["Name"]))); + + var tbl2 = FragmentRequestBase.SimpleLoad(SPSActivityClassIncidentID, "EntryBy as EntryBy, EntryBy.DisplayString as EntryByDisp, COALESCE(AssetAffected, Asset) as Asset, COALESCE(AssetAffected.T(SPSComputerClassAD).Domain.NT4Name, Asset.T(SPSComputerClassAD).Domain.NT4Name) as AssetDomain, COALESCE(COALESCE(AssetAffected.T(SPSComputerClassBase).Name, AssetAffected.T(SPSAssetClassSIMCard).PhoneNumber, AssetAffected.Name, AssetAffected.objectid), COALESCE(Asset.T(SPSComputerClassBase).Name, Asset.T(SPSAssetClassSIMCard).PhoneNumber, Asset.Name, Asset.objectid)) as AssetName", $"[Expression-ObjectID] = '{ActivityEOID}'"); + + if (tbl2?.Rows == null || tbl2.Rows.Count <= 0) + { + LogEntry($"SPSActivityClassIncident fragment not found: ClassId={SPSActivityClassIncidentID}, ObjectId={ActivityEOID}", LogLevels.Debug); + continue; + } + + var AssetId = getGuidFromObject(tbl2.Rows[0]["Asset"]); + var Asset = getStringFromObject(tbl2.Rows[0]["AssetName"]); + var AssetDomain = getStringFromObject(tbl2.Rows[0]["AssetDomain"]); + var EntryBy = getIntFromObject(tbl2.Rows[0]["EntryBy"]); + var EntryByDisp = getStringFromObject(tbl2.Rows[0]["EntryByDisp"]); + + M42Asset AssetObject = null; + if (AssetId != Guid.Empty) + AssetObject = getAssetsByAsql($"ID = '{AssetId}'").FirstOrDefault(); + + var tbl3 = FragmentRequestBase.SimpleLoad(SPSCommonClassBaseID, "State, State.DisplayString as StateDisp, SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.TypeID) as CIName", $"[Expression-ObjectID] = '{ActivityEOID}'"); + if (tbl3?.Rows == null || tbl3.Rows.Count <= 0) + { + LogEntry($"SPSCommonClassBase fragment not found: ClassId={SPSCommonClassBaseID}, ObjectId={ActivityEOID}", LogLevels.Debug); + continue; + } + + var State = getIntFromObject(tbl3.Rows[0]["State"]); + var StateDisp = getStringFromObject(tbl3.Rows[0]["StateDisp"]); + var CIName = getStringFromObject(tbl3.Rows[0]["CIName"]); + + dynamic viewOptionsDyn = new ExpandoObject(); + viewOptionsDyn.objectId = ActivityEOID; + viewOptionsDyn.type = CIName; + viewOptionsDyn.viewType = "action"; + viewOptionsDyn.actionId = TicketCloseActionId; + var viewOptions = HttpUtility.UrlEncode(JsonConvert.SerializeObject(viewOptionsDyn)); + + var ticketDirectLinkPreview = string.Format(TicketDirectLinkPreviewTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); + var ticketDirectLinkEdit = string.Format(TicketDirectLinkEditTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, CIName, ActivityEOID); + var ticketDirectLinkClose = string.Format(IncidentDirectLinkCloseTemplate, F4SDM42WebApiController.defaultInstance.BaseUrl, ActivityEOID, viewOptions); + + LogEntry($"Activity found: ObjectID={ActivityEOID}, TicketNumber={TicketNumber}, Subject={Subject}, State={State}", LogLevels.Debug); + var ticket = new cF4SDTicket() + { + TicketObjectId = ActivityEOID, + Name = TicketNumber, + CIName = CIName, + StatusId = State, + Status = StateDisp, + Summary = Subject, + AffectedUserId = InitiatorId, + AffectedUser = Initiator, + CreationDate = CreatedDate, + ClosingDate = ClosedDate, + DescriptionHtml = DescriptionHtml, + Description = Description, + SolutionHtml = SolutionHtml, + Solution = Solution, + Priority = Priority, + PriorityId = PriorityId, + CreationSourceId = EntryBy, + CreationSource = EntryByDisp, + CategoryHierarchical = CategoryHierarchical, + CategoryId = CategoryId, + Category = Category, + DirectLinkPreview = ticketDirectLinkPreview, + Urgency = Urgency, + UrgencyId = UrgencyId, + Impact = Impact, + ImpactId = ImpactId + //JournalItems = GetJournalEntriesByEOID(ActivityEOID) + }; + + if (AssetObject != null) + { + ticket.AssetId = AssetId; + ticket.AssetName = Asset; + ticket.AssetDomain = AssetDomain; + ticket.AssetCIId = AssetObject.CIId; + ticket.AssetCIName = AssetObject.CIName; + ticket.AssetSKUAssetGroupId = AssetObject.SKUAssetGroupId; + ticket.AssetSKUAssetGroup = AssetObject.SKUAssetGroup; + ticket.AssetSKUTypeId = AssetObject.SKUTypeId; + ticket.AssetSKUType = AssetObject.SKUType; + } + + if (ticket.StatusId != 204) + ticket.DirectLinkEdit = ticketDirectLinkEdit; + + if ((CIName.ToUpper() == "SPSACTIVITYTYPEINCIDENT" || CIName.ToUpper() == "SPSACTIVITYTYPESERVICEREQUEST") && ticket.StatusId != 204) + ticket.DirectLinkClose = ticketDirectLinkClose; + + tickets.Add(ticket); + } + + tickets = tickets.OrderBy(x => x.CreationDate).ToList(); + tickets.Reverse(); + return tickets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task> GetJournalEntries(Guid activityEOID) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + List journalEntries = new List(); + var entries = F4SDM42WebApiController.defaultInstance._journalService.GetJournalList(activityEOID, false, 0, 50, "{2}", 120); + LogEntry($"{entries.Length} Journal entries found for ObjectID={activityEOID}", LogLevels.Debug); + for (int i = 0; i < entries.Length; i++) + { + Matrix42.Contracts.Platform.Data.JournalEntryInfo item = entries[i]; + LogEntry($"Journal entry {i + 1}/{entries.Length}: ID={item.Id}, CreatedDate={item.CreatedDate}, CreatedBy={item.Creator}, Header={item.Header}", LogLevels.Debug); + journalEntries.Add(new cTicketJournalItem() + { + JournalId = item.Id, + ActivityObjectId = activityEOID, + CreatedBy = item.Creator, + CreationDate = (DateTime)item.CreatedDate, + DescriptionHtml = item.Text, + Description = Matrix42.Common.Html.HtmlConverter.ConvertHtmlToPlainText(item.Text), + Header = item.Header, + IsVisibleForUser = item.VisibleInPortal + }); + } + return journalEntries; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + internal async Task updateActivitySolution(Guid objectId, string solutionHtml) + { + + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + if (objectId == Guid.Empty) + return false; + + var tbl = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID, "ID", $"[Expression-ObjectID] = '{objectId}'"); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"SPSActivityClassBase fragment not found: ClassId={SPSActivityClassBaseID}, ObjectId={objectId}", LogLevels.Debug); + return false; + } + + var fragmentId = getGuidFromObject(tbl.Rows[0]["ID"]); + SPSFragment activityFragment = ObjectBroker.GetFragment(SPSActivityClassBaseID, fragmentId); + activityFragment["SolutionHTML"] = solutionHtml; + ObjectBroker.UpdateFragment(SPSActivityClassBaseID, activityFragment); + return true; + } + catch (Exception E) + { + LogException(E); + return false; + } + finally + { + LogMethodEnd(CM); + } + } + + private List getAssetsByAsql(string Filter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + // get SPSAssetClassBase + if (string.IsNullOrEmpty(Filter)) + return null; + List assets = new List(); + LogEntry($"ASql Filter: {Filter}"); + var tbl = FragmentRequestBase.SimpleLoad(SPSAssetClassBaseID + , "Id" + + ", COALESCE(T(SPSComputerClassBase).Name, T(SPSAssetClassSIMCard).PhoneNumber, Name) as AssetName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Name, t.ID=base.T(SPSCommonClassBase).TypeID) as CIName" + + ", SUBQUERY(BasicSchemaObjectType AS t, t.Id, t.ID=base.T(SPSCommonClassBase).TypeID) as CIId" + + ", SKU.Type as SKUTypeId" + + ", SKU.Type.DisplayString as SKUType" + + ", SKU.Type.AssetGroup as SKUAssetGroupId" + + ", SUBQUERY(SPSAssetPickupTypeCategory AS ag, ag.DisplayString, ag.Value=base.SKU.Type.AssetGroup) as SKUAssetGroup" + + ", T(SPSComputerClassAD).Domain.NT4Name as DomainName" + + ", T(SPSComputerClassAD).Sid as Sid" + , Filter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"SPSAssetClassBase fragment not found: ClassId={SPSActivityClassIncidentID}, Filter={Filter}", LogLevels.Debug); + return null; + } + else + { + foreach (DataRow row in tbl.Rows) + { + var asset = new M42Asset() + { + Id = getGuidFromObject(row["Id"]), + Name = getStringFromObject(row["AssetName"]), + CIName = getStringFromObject(row["CIName"]), + CIId = getGuidFromObject(row["CIId"]), + SKUTypeId = getIntFromObject(row["SKUTypeId"]), + SKUType = getStringFromObject(row["SKUType"]), + SKUAssetGroupId = getIntFromObject(row["SKUAssetGroupId"]), + SKUAssetGroup = getStringFromObject(row["SKUAssetGroup"]), + DomainName = getStringFromObject(row["DomainName"]), + Sid = getStringFromObject(row["Sid"]), + }; + assets.Add(asset); + LogEntry($"Asset found: Id={asset.Id}, Name={asset.Name}, CIName={asset.CIName}", LogLevels.Debug); + } + } + return assets; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private M42Asset getAssetByName(string assetname) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + // get SPSAssetClassBase + if (string.IsNullOrEmpty(assetname)) + return null; + var Filter = string.Format("T(SPSComputerClassBase).Name = '{0}' OR T(SPSAssetClassSIMCard).PhoneNumber = '{0}' OR Name = '{0}'", assetname); + return getAssetsByAsql(Filter)?.First(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + private Guid getUserBySid(string sid) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + string sidPattern = @"^S-\d-\d+-(\d+-){1,14}\d+$"; + if (string.IsNullOrEmpty(sid) || !Regex.IsMatch(sid, sidPattern)) + { + LogEntry($"Invalid or empty SID: '{sid}'", LogLevels.Warning); + return Guid.Empty; + } + + var users = getUsersByAsql(string.Format("Accounts.T(SPSAccountClassAD).Sid = '{0}' OR PrimaryAccount.T(SPSAccountClassAD).Sid = '{0}'", sid)); + return users != null ? users.First().Id : Guid.Empty; + } + catch (Exception E) + { + LogException(E); + return Guid.Empty; + } + finally + { + LogMethodEnd(CM); + } + } + + + private List getUsersByAsql(string asqlFilter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var Persons = new List(); + + + LogEntry($"ASql Filter: {asqlFilter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSUserClassBaseID, + "Id as Id" + + ", [Expression-ObjectID] as UserEOID" + + ", T(SPSCommonClassBase).State as State" + + ", LastName + ISNULL(', ' + FirstName,'') as Name" + + ", PreferredMailCulture.Locale as Locale" + + ", T(SPSCommonClassBase).State.DisplayString as StateDisp", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no user entry list found with asql='{asqlFilter}'", LogLevels.Warning); + return null; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var UserId = getGuidFromObject(Entry["ID"]); + if (UserId == Guid.Empty) + { + LogEntry($"no id found for user entry", LogLevels.Warning); + continue; + } + var UserEOID = getGuidFromObject(Entry["UserEOID"]); + if (UserEOID == Guid.Empty) + { + LogEntry($"no expression object id found for user entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + var Locale = getStringFromObject(Entry["Locale"]); + if (string.IsNullOrEmpty(Locale)) + { + LogEntry($"no locale found for entry", LogLevels.Debug); + } + var StateDisp = getStringFromObject(Entry["StateDisp"]); + if (string.IsNullOrEmpty(StateDisp)) + { + LogEntry($"no statedisp found for entry", LogLevels.Debug); + } + + + + var State = getIntFromObject(Entry["State"]); + LogEntry($"User found: ID={UserId}, ObjectID={UserEOID}, State={State}", LogLevels.Debug); + if (State != 2023) + LogEntry($"User is not active", LogLevels.Debug); + Persons.Add(new M42User() + { + Id = UserId, + ObjectId = UserEOID, + Name = Name, + State = State, + Locale = Locale, + LanguageId = new CultureInfo(Locale).LCID % 1024, + StateDisp = StateDisp + }); + } + return Persons; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + private List getAccountsByAsql(string asqlFilter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var result = new List(); + + + LogEntry($"ASql Filter: {asqlFilter}"); + + var tbl = FragmentRequestBase.SimpleLoad(SPSAccountClassBaseID, + "Id as Id" + + ", [Expression-ObjectID] as EOID" + + ", T(SPSCommonClassBase).State as State" + + ", T(SPSAccountClassAD).Sid as Sid" + + ", T(SPSAccountClassAD).UserPrincipalName as UserPrincipalName" + + ", Owner ", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no account entry list found with asql='{asqlFilter}'", LogLevels.Warning); + return null; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var Id = getGuidFromObject(Entry["ID"]); + if (Id == Guid.Empty) + { + LogEntry($"no id found for entry", LogLevels.Warning); + continue; + } + var EOID = getGuidFromObject(Entry["EOID"]); + if (EOID == Guid.Empty) + { + LogEntry($"no expression object id found for entry", LogLevels.Warning); + continue; + } + var Owner = getGuidFromObject(Entry["Owner"]); + if (Owner == Guid.Empty) + { + LogEntry($"no owner found for entry", LogLevels.Warning); + } + var Sid = getStringFromObject(Entry["Sid"]); + if (string.IsNullOrEmpty(Sid)) + { + LogEntry($"no Sid found for entry", LogLevels.Debug); + } + var UserPrincipalName = getStringFromObject(Entry["UserPrincipalName"]); + if (string.IsNullOrEmpty(UserPrincipalName)) + { + LogEntry($"no UserPrincipalName found for entry", LogLevels.Debug); + } + + + var State = getIntFromObject(Entry["State"]); + LogEntry($"Account found: ID={Id}, ObjectID={EOID}, State={State}", LogLevels.Debug); + result.Add(new M42Account() + { + Id = Id, + EOID = EOID, + State = State, + UserPrincipalName = UserPrincipalName, + Sid = Sid, + Owner = Owner + }); + } + return result; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + internal HttpResponseMessage privGetLog(string download, int maxLines, HttpRequestMessage request, string filter) + { + var response = new HttpResponseMessage(); + + + + if (download == "1") + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + MemoryStream memoryStream = new MemoryStream(); + + ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true); + string[] files = Directory.GetFiles(Path.GetDirectoryName(logFileName), $"*.log"); + foreach (string fileName in files) + { + using (var tmpInStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(Path.GetFileName(fileName)); + using (Stream destination = zipArchiveEntry.Open()) + { + tmpInStream.CopyTo(destination); + } + } + } + zipArchive.Dispose(); + memoryStream.Position = 0L; + HttpResponseMessage httpResponseMessage = request.CreateResponse(HttpStatusCode.OK); + httpResponseMessage.Content = new StreamContent(memoryStream); + httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") + { + FileName = Path.GetFileNameWithoutExtension(logFileName) + ".zip" + }; + httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + + return httpResponseMessage; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + + } + else + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + using (var inStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + var lines = ReadLines(() => inStream, + Encoding.UTF8) + .ToList(); + var sb = new StringBuilder(); + sb.AppendLine(""); + var htmlLines = new List(); + foreach (var line in lines) + { + var lineSplit = line.Split('\t'); + if (string.IsNullOrEmpty(filter) || !string.IsNullOrEmpty(filter) && lineSplit.Length > 4 && filter.ToLowerInvariant().Split(',').Contains(lineSplit[3].ToLowerInvariant())) + htmlLines.Add(""); + } + if (maxLines > 0) + htmlLines = htmlLines.Skip(Math.Max(0, htmlLines.Count() - maxLines)).ToList(); + foreach (var line in htmlLines) + { + sb.AppendLine(line); + } + sb.AppendLine("
" + string.Join("", lineSplit) + "
"); + + response.Content = new StringContent(sb.ToString()); + response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html") + { + CharSet = Encoding.UTF8.HeaderName + }; + response.Content.Headers.Add("CodePage", Encoding.UTF8.CodePage.ToString()); + return response; + } + } + } + + internal List privGetLog2() + { + var response = new HttpResponseMessage(); + { + var logger = (DefaultLogger.Manager as cLogManagerFile); + var logFileName = logger.GetLogFileName(); + using (var inStream = new FileStream(logFileName, FileMode.Open, + FileAccess.Read, FileShare.ReadWrite)) + { + var lines = ReadLines(() => inStream, + Encoding.UTF8) + .ToList(); + + var entries = new List(); + DateTime dateValue; + for (int i = 0; i < lines.Count; i++) + { + string line = lines[i]; + try + { + var lineSplit = line.Split('\t'); + if (lineSplit.Length >= 5) + { + if (string.IsNullOrEmpty(lineSplit[0])) + { + entries.Last().Message += Environment.NewLine + lineSplit[4]; + continue; + } + DateTime.TryParseExact(lineSplit[0], "yyyy-MM-dd HH:mm:ss:fffff", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateValue); + entries.Add(new cM42LogEntry() + { + LineNumber = i + 1, + date = dateValue, + ProcessId = lineSplit[1], + logLvl = lineSplit[3], + Theme = lineSplit[2], + Message = lineSplit[4], + }); + } + } + catch (Exception) + { + + } + } + return entries; + } + } + } + + private int getIntFromObject(object o, int Default = 0) + { + try + { + if (o == null) + return Default; + if (o is DBNull) + return Default; + if (o is int @int) + return @int; + if (o is long int1) + return (int)int1; + if (int.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return Default; + } + + private DateTime getDateTimeFromObject(object o) + { + try + { + if (o == null) + return DateTime.MinValue; + if (o is DBNull) + return DateTime.MinValue; + if (o is DateTime @dateTime) + return @dateTime; + if (o is string dstr && DateTime.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return DateTime.MinValue; + } + + private string getStringFromObject(object o, string Default = "") + { + try + { + if (o == null) + return Default; + if (o is DBNull) + return Default; + return o.ToString(); + } + catch { } + return Default; + } + + private Guid getGuidFromObject(object o) + { + try + { + if (o == null) + return Guid.Empty; + if (o is DBNull) + return Guid.Empty; + if (o is Guid G) + return G; + if (Guid.TryParse(o.ToString(), out var r)) + return r; + } + catch { } + return Guid.Empty; + } + + internal cM42LogEntry privGetLog2(int id) + { + return privGetLog2().Find(x => x.LineNumber == id); + } + + internal async Task> getRoleMembershipById(Guid UserId) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString()); + LogEntry($"ASql Filter: {asqlFilter}"); + + FragmentRequestBase fragmentRequestBase = new FragmentRequestBase(SPSSecurityClassRole, ColumnSelectOption.List, "Id as Id" + + ", Name as Name"); + fragmentRequestBase.Where = asqlFilter; + var langExt = new FragmentRequestExtensionLanguage(); + langExt.AddCultureRequest(new CultureInfo("en")); + fragmentRequestBase.AddExtension(langExt); + + using (SPSTransactionScope sPSTransactionScope = new SPSTransactionScope(SPSTransactionScopeOption.Required, new SPSTransactionOptions(IsolationLevel.ReadUncommitted))) + { + fragmentRequestBase.Load(); + sPSTransactionScope.Complete(); + } + + // if requested language is already "en" use the english table + var tbl = fragmentRequestBase.DataSet.Tables[1]; + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + tbl = fragmentRequestBase.DataSet.Tables[0]; + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug); + return RoleMemberships; + } + foreach (DataRow Entry in tbl.Rows) + { + if (Entry.Table.Columns.Contains("LCID") && (int)Entry["LCID"] != 9) + continue; + // get the id + var IDColumn = Entry.Table.Columns.Contains("Owner") ? "Owner" : "ID"; + var RoleId = getGuidFromObject(Entry[IDColumn]); + if (RoleId == Guid.Empty) + { + LogEntry($"no id found for role entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug); + RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name }); + } + return RoleMemberships; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + /* + internal async Task> getRoleMembershipById(Guid UserId) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + var asqlFilter = string.Format("Members.Id = '{0}'", UserId.ToString()); + LogEntry($"ASql Filter: {asqlFilter}"); + var tbl = FragmentRequestBase.SimpleLoad(SPSSecurityClassRole, + "Id as Id" + + ", Name as Name", + asqlFilter); + if (tbl?.Rows == null || tbl.Rows.Count <= 0) + { + LogEntry($"no role membership found with asql='{asqlFilter}'", LogLevels.Debug); + return RoleMemberships; + } + foreach (DataRow Entry in tbl.Rows) + { + // get the id + var RoleId = getGuidFromObject(Entry["ID"]); + if (RoleId == Guid.Empty) + { + LogEntry($"no id found for role entry", LogLevels.Warning); + continue; + } + var Name = getStringFromObject(Entry["Name"]); + if (string.IsNullOrEmpty(Name)) + { + LogEntry($"no Name found for entry", LogLevels.Debug); + } + LogEntry($"Role Membership found: ID={RoleId}, Name={Name}", LogLevels.Debug); + RoleMemberships.Add(new M42Role() { Id = RoleId, Name = Name }); + } + return RoleMemberships; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + */ + internal async Task UserPermissionsInfo(string filter) + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + var RoleMemberships = new List(); + try + { + await Task.Delay(0); + + var retVal = new UserPermissionsInfo(); + List m42Users = getUsersByAsql(filter); + if (m42Users != null && m42Users.Count == 1) + { + retVal.User = m42Users[0]; + + + var user = F4SDM42WebApiController.defaultInstance._userProfile.GetInteractiveUserInfo(retVal.User.Id); + if (user != null) + { + retVal.User.Id = user.Id; + retVal.User.Currency = user.Currency; + retVal.User.Email = user.Email; + retVal.User.FirstName = user.FirstName; + retVal.User.LastName = user.LastName; + retVal.User.Phone = user.Phone; + retVal.User.Photo = user.Photo; + } + + retVal.Roles = await getRoleMembershipById(retVal.User.Id); + retVal.User.IsAdmin = retVal.Roles?.Where(i => i.Id == Administrators).FirstOrDefault() != null; + return retVal; + } + return null; + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + private enum eM42EntryBy + { + Phone = 0, + Email = 1, + Portal = 2, + CatalogOrder = 3, + EventTrigger = 4, + Feedback = 5, + VisualSupportAgent = 6, + ComplianceAlert = 7, + F4SD = 20010, + } + + public class DirectLink + { + public string Link { get; set; } + public string SubjectParameter { get; set; } + public string DescriptionParameter { get; set; } + } + + public class M42Asset + { + public Guid Id { get; set; } + public string Name { get; set; } + public string CIName { get; set; } + public string SKUType { get; set; } + public string SKUAssetGroup { get; set; } + public Guid CIId { get; set; } + public int SKUTypeId { get; internal set; } + public int SKUAssetGroupId { get; internal set; } + public string DomainName { get; internal set; } + public string Sid { get; internal set; } + } + } + + public class M42User + { + public Guid Id { get; internal set; } + public Guid ObjectId { get; internal set; } + public string Name { get; internal set; } + public string FirstName { get; internal set; } + public string LastName { get; internal set; } + public string Email { get; internal set; } + public string Photo { get; internal set; } + public string Phone { get; internal set; } + public string Currency { get; internal set; } + public string Locale { get; internal set; } + public int LanguageId { get; internal set; } + public bool IsAdmin { get; internal set; } + public int State { get; internal set; } + public string StateDisp { get; internal set; } + } + + internal class M42Account + { + public Guid Id { get; internal set; } + public Guid EOID { get; internal set; } + public Guid Owner { get; internal set; } + public int State { get; internal set; } + public string UserPrincipalName { get; internal set; } + public string Sid { get; internal set; } + } + + public class M42Role + { + public Guid Id { get; internal set; } + public string Name { get; internal set; } + + } + + public class UserPermissionsInfo + { + public M42User User { get; internal set; } + public List Roles { get; internal set; } + public UserPermissionsInfo() + { + User = new M42User(); + Roles = new List(); + } + } +} diff --git a/F4SDM42WebApi/F4SDM42WebApiController.cs b/F4SDM42WebApi/F4SDM42WebApiController.cs new file mode 100644 index 0000000..7a021c7 --- /dev/null +++ b/F4SDM42WebApi/F4SDM42WebApiController.cs @@ -0,0 +1,377 @@ +using C4IT.F4SDM; +using C4IT.FASD.Base; +using C4IT.Logging; +using Matrix42.Common; +using Matrix42.Contracts.ServiceManagement.ServiceContracts; +using Matrix42.Pandora.Contracts; +using Matrix42.Services.Description.Contracts; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.OData; +using System.Web.OData.Query; +using update4u.SPS.Utility.GlobalConfiguration; +using static C4IT.FASD.Base.cF4SDTicket; +using static C4IT.Logging.cLogManager; + +namespace C4IT.F4SD +{ + [RoutePrefix("api/C4ITF4SDWebApi")] + public partial class F4SDM42WebApiController : ApiController + { + public static bool IsInitialized { get; private set; } = false; + public static F4SDM42WebApiController defaultInstance; + //private readonly IIncidentService _incidentService; + internal readonly GlobalConfigurationProvider _globalConfigurationProvider; + public readonly IJournalService _journalService; + //public readonly IObjectService _objectService; + //public readonly IFragmentService _fragmentService; + internal readonly IEntityDataService _entityDataService; + internal readonly IPandoraUserProfile _userProfile; + + + private readonly F4SDHelperService _f4stHelperService; + public string BaseUrl => $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}"; + public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi"; + public F4SDM42WebApiController( + //IObjectService objectService, + //IIncidentService incidentService, + IJournalService journalService + //IFragmentService fragmentService, + , IEntityDataService entityDataService + , IPandoraUserProfile userProfile + ) + { + defaultInstance = this; + //_objectService = objectService; + //_fragmentService = fragmentService; + //_incidentService = Guard.NullArgument(incidentService, "incidentService"); + + _entityDataService = entityDataService; + _journalService = journalService; + _globalConfigurationProvider = GlobalConfigurationProvider.Instance; + _f4stHelperService = new F4SDHelperService(); + _userProfile = userProfile; + } + + private static object initLock = new object(); + protected override void Initialize(HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + try + { + //System.Diagnostics.Debugger.Launch(); + lock (initLock) + { + if (IsInitialized || F4SDM42LogsWebApiController.IsInitialized) + return; + var Ass = Assembly.GetExecutingAssembly(); + var LM = cLogManagerFile.CreateInstance(LocalMachine: true, A: Ass); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + cLogManager.DefaultLogger.LogAssemblyInfo(Ass); + IsInitialized = true; + LogMethodEnd(CM); + } + } + catch { }; + } + + [Route("getDirectLinkCreateTicket"), HttpGet] + public async Task getDirectLinkCreateTicket(string sid = "", string assetname = "") + { + await Task.Delay(0); + return await _f4stHelperService.getDirectLinkCreateTicket(sid, assetname); + } + [Route("getDirectLinkF4SD"), HttpGet] + public async Task getDirectLinkF4SD(Guid EOID, string Type) + { + return await _f4stHelperService.getDirectLinkF4SD(EOID, Type); + } + + [Route("getTicketList"), HttpGet] + public async Task> getTicketList( + string sid, + int hours, + int queueoption = 0, + string queues = "" + ) + { + // "HR:2a7e..." → Tuple("HR", "2a7e..."), dann UrlDecode + var decodedPairs = queues + .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .Select(part => + { + var segments = part.Split(':'); + if (segments.Length != 2) + return null; + + var name = HttpUtility.UrlDecode(segments[0]); + var idStr = HttpUtility.UrlDecode(segments[1]); + + return Guid.TryParse(idStr, out var guid) + ? new cApiM42TicketQueueInfo { QueueName = name, QueueID = guid } + : null; + }) + .Where(q => q != null) + .ToList(); + + // Nun weiterreichen an Service + return await _f4stHelperService.getTicketListByUser( + sid, + hours, + queueoption, + decodedPairs + ); + } + + + [Route("getTicketDetails"), HttpGet] + public async Task getTicketDetails(Guid objectId) + { + var tickets = await _f4stHelperService.getTicketDetails(new List() { objectId }); + if (tickets.Count > 0) + return tickets[0]; + else + return null; + } + + [Route("getTicketHistory"), HttpGet] + public async Task> getTicketHistory(Guid objectId) + { + return await _f4stHelperService.GetJournalEntries(objectId); + } + + [Route("getTicketOverviewCounts"), HttpGet] + public async Task getTicketOverviewCounts(string sid, string scope = "personal", string keys = "") + { + var parsedKeys = (keys ?? string.Empty) + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(key => key.Trim()) + .Where(key => !string.IsNullOrWhiteSpace(key)) + .ToList(); + + return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys); + } + + [Route("getTicketOverviewRelations"), HttpGet] + public async Task> getTicketOverviewRelations(string sid, string scope = "personal", string key = "", int count = 0) + { + return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count); + } + /* + [Route("updateActivitySolution/{objectId}"), HttpPost] + public async Task updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml) + { + return new HttpResponseMessage + { + StatusCode = await _f4stHelperService.updateActivitySolution(objectId, SolutionHtml) ? HttpStatusCode.NoContent : HttpStatusCode.BadRequest, + }; + } + */ + [Route("getPickup/{name}"), HttpGet] + //[CacheOutput(UseETAG = true)] + public async Task getPickup(string name, [FromUri] EntityEnumerationVisibilityMode mode = EntityEnumerationVisibilityMode.None, [FromUri] Int32 group = -1) + { + await Task.Delay(0); + EntityEnumeration enumerationTemp = _entityDataService.GetEnumeration(name, mode); + var vals = enumerationTemp.Values; + + if (group > -1) + { + vals = vals.Where(row => !row.Extentions.TryGetValue("StateGroup", out var stateGroup) || (ConvertHelper.ParseInt(stateGroup, 0) == group)).ToArray(); + } + + string[] columns = new string[] { "position" }; + foreach (var item in vals) + { + item.Extentions = item.Extentions.Where(x => columns.Contains(x.Key.ToLower())).ToDictionary(x => x.Key, x => x.Value); + } + + EntityEnumeration enumeration = new EntityEnumeration + { + Name = enumerationTemp.Name, + Values = vals.ToArray() + }; + //CacheOutputAttribute.RegisterResponseEtag($"enum_{enumeration.Name}_{(int)mode}", $"{enumeration.Name}_{(int)mode}", cultureInvariant: false, userInvariant: true, val); + return Request.CreateResponse(HttpStatusCode.OK, enumeration); + } + + [Route("getMyRoleMemberships", Order = 2), HttpGet] + public async Task getMyRoleMemberships() + { + var User = _userProfile.GetInteractiveUserInfo(); + var filter = AsqlHelper.BuildInCondition("ID", new Guid[] { User.Id }); + return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + } + [HttpGet] + [Route("getRoleMemberships", Order = 1)] + public async Task getRoleMemberships([FromUri] GetRoleMembershipsRequest req) + { + var filter = ""; + + if (req.Id != null && req.Id.Value != Guid.Empty) + { + filter = AsqlHelper.BuildInCondition("ID", new Guid[] { req.Id.Value }); + } + else if (!string.IsNullOrEmpty(req.Sid)) + { + filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).Sid", new string[] { req.Sid }); + } + else if (!string.IsNullOrEmpty(req.Upn)) + { + filter = AsqlHelper.BuildInCondition("Accounts.T(SPSAccountClassAD).UserPrincipalName", new string[] { req.Upn }); + } + + if (!string.IsNullOrEmpty(filter)) + { + return Request.CreateResponse(HttpStatusCode.OK, await _f4stHelperService.UserPermissionsInfo(filter)); + } + else + { + return null; + } + } + + public class GetRoleMembershipsRequest + { + public Guid? Id { get; set; } + public string Sid { get; set; } + public string Upn { get; set; } + public GetRoleMembershipsRequest() { } + } + + [Route("isAlive"), HttpGet] + public HttpResponseMessage isAlive() + { + return new HttpResponseMessage(HttpStatusCode.NoContent); + } + + [Route("loglevel"), HttpGet] + public async Task setDebugMode(string debug = "0") + { + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + await Task.Delay(0); + DefaultLogger.Manager.Level = debug == "1" || debug.Equals("true", StringComparison.OrdinalIgnoreCase) ? LogLevels.Debug : LogLevels.Info; + return DefaultLogger.Manager.Level.ToString(); + } + catch (Exception E) + { + LogException(E); + return null; + } + finally + { + LogMethodEnd(CM); + } + } + + [Route("log"), HttpGet] + public HttpResponseMessage getLog(string download = "0", int count = 50, string filter = "") + { + try + { + return _f4stHelperService.privGetLog(download, count, Request, filter); + } + catch (Exception E) + { + LogException(E); + return null; + } + } + } + [RoutePrefix("api/C4ITF4SDWebApi/Logs")] + public partial class F4SDM42LogsWebApiController : ApiController + { + private readonly F4SDHelperService _f4stHelperService; + + public static bool IsInitialized { get; private set; } = false; + + private static object initLock = new object(); + protected override void Initialize(HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + try + { + lock (initLock) + { + if (IsInitialized || F4SDM42WebApiController.IsInitialized) + return; + var Ass = Assembly.GetExecutingAssembly(); + var LM = cLogManagerFile.CreateInstance(LocalMachine: true, A: Ass); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + cLogManager.DefaultLogger.LogAssemblyInfo(Ass); + IsInitialized = true; + LogMethodEnd(CM); + } + } + catch { }; + } + + + + public F4SDM42LogsWebApiController() + { + _f4stHelperService = new F4SDHelperService(); + } + + [Route(""), HttpGet] + [EnableQuery] + public IEnumerable getLog2(ODataQueryOptions queryOptions) + { + try + { + IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); + if (queryOptions.Filter != null) + { + queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); + } + return queryable; + } + catch (Exception E) + { + LogException(E); + return null; + } + } + + [Route("$count")] + [HttpGet] + public int Log2Count(ODataQueryOptions queryOptions) + { + IQueryable queryable = _f4stHelperService.privGetLog2().AsQueryable(); + if (queryOptions.Filter != null) + { + queryable = queryOptions.Filter.ApplyTo(queryable, new ODataQuerySettings()).Cast(); + } + return queryable.Count(); + } + + [Route("{id}")] + [OperationType(OperationType.GetObject)] + public cM42LogEntry GetClass(int id) + { + return _f4stHelperService.privGetLog2(id); + } + } + + public class cGetPropertyBody + { + public string TableName { get; set; } + public List Columns { get; set; } = new List(); + public cGetPropertyBody() { } + + } +} diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Common.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Common.dll new file mode 100644 index 0000000..e938b92 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Common.dll differ diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Common.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Common.dll new file mode 100644 index 0000000..53f09f8 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Common.dll differ diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Platform.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Platform.dll new file mode 100644 index 0000000..977fa3d Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.Platform.dll differ diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.ServiceManagement.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.ServiceManagement.dll new file mode 100644 index 0000000..e9b175a Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Contracts.ServiceManagement.dll differ diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Pandora.Contracts.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Pandora.Contracts.dll new file mode 100644 index 0000000..366aac0 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Pandora.Contracts.dll differ diff --git a/F4SDM42WebApi/M42Libraries/Matrix42.Services.Description.Contracts.dll b/F4SDM42WebApi/M42Libraries/Matrix42.Services.Description.Contracts.dll new file mode 100644 index 0000000..7d38937 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/Matrix42.Services.Description.Contracts.dll differ diff --git a/F4SDM42WebApi/M42Libraries/System.Web.Http.dll b/F4SDM42WebApi/M42Libraries/System.Web.Http.dll new file mode 100644 index 0000000..e1dbdd1 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/System.Web.Http.dll differ diff --git a/F4SDM42WebApi/M42Libraries/System.Web.OData.dll b/F4SDM42WebApi/M42Libraries/System.Web.OData.dll new file mode 100644 index 0000000..d68e666 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/System.Web.OData.dll differ diff --git a/F4SDM42WebApi/M42Libraries/update4u.SPS.DataLayer.dll b/F4SDM42WebApi/M42Libraries/update4u.SPS.DataLayer.dll new file mode 100644 index 0000000..e61466c Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/update4u.SPS.DataLayer.dll differ diff --git a/F4SDM42WebApi/M42Libraries/update4u.SPS.Utility.dll b/F4SDM42WebApi/M42Libraries/update4u.SPS.Utility.dll new file mode 100644 index 0000000..ccdee59 Binary files /dev/null and b/F4SDM42WebApi/M42Libraries/update4u.SPS.Utility.dll differ diff --git a/F4SDM42WebApi/Properties/AssemblyInfo.cs b/F4SDM42WebApi/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fe61b2e --- /dev/null +++ b/F4SDM42WebApi/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("C4IT - F4SD - WebApi for M42")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d8cbffca-0b43-4acc-80ea-c944e7420cee")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] \ No newline at end of file diff --git a/F4SDM42WebApi/SignSourceFiles.cmd b/F4SDM42WebApi/SignSourceFiles.cmd new file mode 100644 index 0000000..0c685d1 --- /dev/null +++ b/F4SDM42WebApi/SignSourceFiles.cmd @@ -0,0 +1,10 @@ +set ProductName="C4IT First Aid Service Desk Matrix42 ESM Addon" + +set SignTool=..\..\..\Common Code\Tools\signtool.exe +set TimeStamp=http://rfc3161timestamp.globalsign.com/advanced + +"%SignTool%" sign /a /tr %TimeStamp% /td SHA256 /fd SHA256 /d %ProductName% ".\bin\Release\C4ITF4SDM42WebApi.dll" ".\bin\Release\C4ITF4SDM42WebApiHelper.dll" + +pause + + diff --git a/F4SDM42WebApi/cM42LogEntry.cs b/F4SDM42WebApi/cM42LogEntry.cs new file mode 100644 index 0000000..101c2b2 --- /dev/null +++ b/F4SDM42WebApi/cM42LogEntry.cs @@ -0,0 +1,21 @@ +using Matrix42.Services.Description.Contracts; +using System; + +namespace C4IT.F4SDM +{ + [DisplayName(Name = "Log Entry", Type = DisplayNameTypes.Static)] + public class cM42LogEntry + { + public string DisplayName + { + get => string.Format("Line {0}", LineNumber.ToString()); + } + public DateTime date { get; set; } + public string ProcessId { get; set; } + public string logLvl { get; set; } + public string Theme { get; set; } + public string Message { get; set; } + [Identifier] + public int LineNumber { get; internal set; } + } +} diff --git a/F4SDM42WebApi/deploy.ps1 b/F4SDM42WebApi/deploy.ps1 new file mode 100644 index 0000000..ad93099 --- /dev/null +++ b/F4SDM42WebApi/deploy.ps1 @@ -0,0 +1,77 @@ +#POSTBUILD powershell -ExecutionPolicy Unrestricted $(ProjectDir)deploy.ps1 -ProjectDir $(ProjectDir) -SolutionDir $(SolutionDir) -OutDir $(OutDir) -ConfigurationName $(ConfigurationName) +param([string]$OutDir, + [string]$ConfigurationName, + [string]$SolutionDir, + [string]$ProjectDir); + +$copyAssembly = 0 +$copyWorkspace = 0 +$signAssembly = 1 +$ProductName="C4IT - F4SD - WebApi for M42" + + +$targetPath = "\\srvwsm001.imagoverum.com\c$\Program Files (x86)\Matrix42\Matrix42 Workplace Management" +$minifyApi = "https://www.toptal.com/developers/javascript-minifier/api/raw" + + +#minify js file +$workdirPath = "$ProjectDir${OutDir}" +$workdirPathZip = "$workdirPath\zip" + + +Get-ChildItem -Path "$workdirPathZip" -File -Recurse | Remove-Item +Expand-Archive -Path "$workdirPath\F4SD - M42.zip" -DestinationPath $workdirPathZip + +$buildDate = $((get-date).ToLocalTime()).ToString("yyyy-MM-dd") +$jsCode = Get-Content -Path "$workdirPath\F4SD.js" -Raw +$body = @{input=$jsCode} +$contentType = 'application/x-www-form-urlencoded' +$resp = Invoke-WebRequest -Method POST -Uri $minifyApi -body $body -ContentType $contentType +$workspaceVersion = (Get-Content -Raw -Path "$workdirPath\workspace.json" | ConvertFrom-Json).version +[IO.File]::WriteAllLines("$workdirPath\F4SD.min.js","/*v$workspaceVersion ($buildDate)*/" + $resp.Content) + +$finalWorkspacePath = "$workdirPathZip\Files\WM\workspaces\C4IT_F4SD" +New-Item -ItemType Directory -Force -Path $finalWorkspacePath | out-null +Copy-Item "$workdirPath\F4SD.min.js" -Destination "$finalWorkspacePath" +Copy-Item "$workdirPath\F4SDIcons.svg" -Destination "$finalWorkspacePath" +Copy-Item "$workdirPath\workspace.json" -Destination "$finalWorkspacePath" + +$finalAssemblyPath = "$workdirPathZip\Assemblies\svc\bin\" +New-Item -ItemType Directory -Force -Path $finalWorkspacePath | out-null + +if($signAssembly -eq 1) + { + + $SignTool="$SolutionDir\..\..\Workspaces\Common Code\Tools\signtool.exe" + $TimeStamp="http://rfc3161timestamp.globalsign.com/advanced" + & $SignTool sign /a /tr $TimeStamp /td SHA256 /fd SHA256 /d $ProductName C:\Users\dm134\source\repos\F4SDM42WebApi\F4SDM42WebApi\bin\Release\C4ITF4SDM42WebApi.dll C:\Users\dm134\source\repos\F4SDM42WebApi\F4SDM42WebApi\bin\Release\C4ITF4SDM42WebApiHelper.dll + } + + + + + +Get-ChildItem -Path "$ProjectDir${OutDir}C4ITF4SD*" -Include *.dll | Copy-Item -Destination $finalAssemblyPath + +Remove-Item "$workdirPath\F4SD - M42 v$workspaceVersion.zip" +Compress-Archive -Path "$workdirPathZip\*" -DestinationPath "$workdirPath\F4SD - M42 v$workspaceVersion.zip" + + + + +IF( $ConfigurationName -eq "Release_and_copy" -OR $ConfigurationName -eq "Debug_and_copy" ) +{ + #Copy Assembly + if($copyAssembly -eq 1) + { + Get-ChildItem -Path "$ProjectDir${OutDir}C4ITF4SD*" -Include *.dll | Copy-Item -Destination "$targetPath\svc\bin\" + } + #Copy Workspace + if($copyWorkspace -eq 1) + { + $targetWorkspacePath = "$targetPath\WM\workspaces\C4IT_F4SD" + New-Item -ItemType Directory -Force -Path $targetWorkspacePath | out-null + Get-ChildItem -Path "$finalWorkspacePath\*" -Include * | Copy-Item -Destination $targetWorkspacePath + } + +} \ No newline at end of file diff --git a/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj b/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj new file mode 100644 index 0000000..c2f1147 --- /dev/null +++ b/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj @@ -0,0 +1,17 @@ + + + + c3d52a04-2461-4f35-8edf-de7226add677 + 14.0 + SAK + SAK + SAK + SAK + + + + + + + + \ No newline at end of file diff --git a/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj.vspscc b/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj.vspscc new file mode 100644 index 0000000..feffdec --- /dev/null +++ b/M42F4SDUUXWorkspace/F4SD - M42 UUX Workspace.shproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/M42F4SDUUXWorkspace/F4SD - M42.zip b/M42F4SDUUXWorkspace/F4SD - M42.zip new file mode 100644 index 0000000..ae6fec1 Binary files /dev/null and b/M42F4SDUUXWorkspace/F4SD - M42.zip differ diff --git a/M42F4SDUUXWorkspace/F4SD.js b/M42F4SDUUXWorkspace/F4SD.js new file mode 100644 index 0000000..6844349 --- /dev/null +++ b/M42F4SDUUXWorkspace/F4SD.js @@ -0,0 +1,108 @@ +(function (w) { + 'use strict'; + const icons = 'c4it-f4sd-icons'; + w.mx = w.mx || {}; + w.mx.workspacesConfig = w.mx.workspacesConfig || {}; + w.mx.workspacesConfig.modules = w.mx.workspacesConfig.modules || {}; + w.mx.workspacesConfig.modules.add = w.mx.workspacesConfig.modules.add || function (name, config) { + w.mx.workspacesConfig.modules[name] = config; + }; + + w.mx.workspacesConfig.modules.add('mx.C4IT.F4SD', { + name: 'mx.C4IT.F4SD', + config: ['$mdIconProvider', function ($mdIconProvider) { + $mdIconProvider.iconSet(icons, 'workspaces/C4IT_F4SD/F4SDIcons.svg'); + + w.mx.components = w.mx.components || {}; + w.mx.components.Icons = w.mx.components.Icons || []; + + w.mx.components.Icons.unshift({ + id: icons, + name: 'C4IT F4SD Icons', + icons: [{ + SVG: true, + id: `${icons}:icon-f4sd`, + name: 'F4SD Icon', + keywords: ['c4it', 'custom', 'f4sd'] + }, { + SVG: true, + id: `${icons}:icon-f4sd-coloured`, + name: 'F4SD Icon (coloured)', + keywords: ['c4it', 'custom', 'f4sd'] + }] + }); + }] + }); +})(window); + +(function (w) { + 'use strict'; + + angular.module("mx.C4IT.F4SD") + .controller("mx.C4IT.F4SD.Actions.callF4SD", [ + "mx.shell.Config", + "mx.SolutionBuilderAgent.Http", + "mx.shell.NotificationService", + "mx.internationalization", + function (shellConfig, $http, notificationService, i18n) { + const vm = this; + const F4SD_URL_PREFIX = 'f4sdsend://localhost/'; + const ERROR_INVALID_RESPONSE = "Invalid response from server"; + const ERROR_SERVICE_UNAVAILABLE = "Service not available"; + + vm.restHost = shellConfig.settings.restHosts.default; + vm.messageF4SDOpened = i18n.get('c4it.f4sd.action-open-called') || 'F4SD wird geöffnet...'; + + vm.execute = function (conf, para) { + const eoid = conf[0]['Sys-ObjectId'] || conf[0].ID; + + // Aktionsparameter abrufen + if (para.controllerParams) { + try { + vm.controllerParams = new URLSearchParams(para.controllerParams); + } catch (error) { + console.error('Fehler beim Parsen der Controller-Parameter:', error); + notificationService.error("Formatfehler in Aktionskonfigurationsparametern"); + return; + } + } else { + vm.controllerParams = new URLSearchParams(); + } + + // F4SD-URL abrufen + const uri = new URL("api/c4itf4sdwebapi/getdirectlinkf4sd/", vm.restHost); + uri.searchParams.append("type", vm.controllerParams.get("type") || para.name); + uri.searchParams.append("eoid", eoid); + + $http.get(uri.pathname + uri.search).then((response) => { + try { + const n = response.data || response; + + if (typeof n !== 'string') { + console.error('Unerwartetes Datenformat:', n); + notificationService.error(ERROR_INVALID_RESPONSE); + return; + } + + if (!n.startsWith(F4SD_URL_PREFIX)) { + notificationService.error(ERROR_SERVICE_UNAVAILABLE); + return; + } + + if (vm.controllerParams.get("showNotification") === '1') { + notificationService.info(vm.messageF4SDOpened); + } + window.location.href = n; + + } catch (error) { + console.error(`Fehler beim Verarbeiten der Antwort-URL: ${error}`); + notificationService.error(ERROR_SERVICE_UNAVAILABLE); + } + }).catch((error) => { + console.error('HTTP-Anfrage fehlgeschlagen:', error); + notificationService.error(ERROR_SERVICE_UNAVAILABLE); + }); + }; + } + ]); +})(window); diff --git a/M42F4SDUUXWorkspace/F4SDIcons.svg b/M42F4SDUUXWorkspace/F4SDIcons.svg new file mode 100644 index 0000000..194f4bd --- /dev/null +++ b/M42F4SDUUXWorkspace/F4SDIcons.svg @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems b/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems new file mode 100644 index 0000000..a3bca6d --- /dev/null +++ b/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems @@ -0,0 +1,34 @@ + + + + {5915b31c-bc61-422e-ae50-8f337bf0b998} + SAK + SAK + SAK + SAK + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + c3d52a04-2461-4f35-8edf-de7226add677 + + + M42F4SDUUXWorkspace + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems.vspscc b/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems.vspscc new file mode 100644 index 0000000..feffdec --- /dev/null +++ b/M42F4SDUUXWorkspace/M42F4SDUUXWorkspace.projitems.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/M42F4SDUUXWorkspace/workspace.json b/M42F4SDUUXWorkspace/workspace.json new file mode 100644 index 0000000..816b055 --- /dev/null +++ b/M42F4SDUUXWorkspace/workspace.json @@ -0,0 +1,7 @@ +{ + "description": "C4IT - F4SD - M42 ESM Integration", + "version": "1.0.0", + "resources": [ + "F4SD.min.js" + ] +} \ No newline at end of file diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs new file mode 100644 index 0000000..edd7292 --- /dev/null +++ b/SharedAssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +[assembly: AssemblyCompany("Consulting4IT GmbH, Germany")] +[assembly: AssemblyProduct("C4IT - F4SD - WebApi for M42")] +[assembly: AssemblyCopyright("Copyright © 2023, Consulting4IT GmbH, Germany")] +[assembly: AssemblyTrademark("")] + +[assembly: AssemblyVersion("1.2.0.3")] +[assembly: AssemblyInformationalVersion("1.2.0.3")] \ No newline at end of file