feat: expand cockpit integrations and support workflows

Add Phoenix remote desktop, action connector, documentation engine, and gamification support. Refactor support-case processing and search/action UI, and update installer assets and dependencies.
This commit is contained in:
Meik
2026-07-13 11:16:53 +02:00
parent bbedbf71c8
commit 0b52999b1d
303 changed files with 11235 additions and 3580 deletions

View File

@@ -0,0 +1,12 @@
using F4SD.ActionConnector.Enumerations;
using F4SD.ActionConnector.Payloads;
namespace F4SD.ActionConnector.Models
{
public sealed class ActionContext
{
public TriggerEvent TriggerEvent { get; set; }
public bool AwaitResult { get; set; }
public PayloadBase Payload { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using C4IT.XML;
using F4SD.ActionConnector.Enumerations;
using System;
using System.Xml;
using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
public abstract class ActionDefinitionBase
{
public string Id { get; set; }
public abstract ActionType Type { get; }
public bool AwaitResult { get; set; }
public bool IsEnabled { get; set; }
public string Description { get; set; }
public bool IsValid { get; private protected set; }
protected ActionDefinitionBase(XmlElement xNode, cXmlParser parser)
{
try
{
Id = cXmlParser.GetStringFromXmlAttribute(xNode, "Id");
AwaitResult = cXmlParser.GetBoolFromXmlAttribute(xNode, "AwaitResult");
IsEnabled = cXmlParser.GetBoolFromXmlAttribute(xNode, "IsEnabled");
XmlNode descriptionNode = xNode.SelectSingleNode("Description");
if (descriptionNode != null)
Description = cXmlParser.GetInnerTextFromXmlElement(descriptionNode);
IsValid = true;
}
catch (Exception ex)
{
LogException(ex);
}
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Collections.Generic;
using F4SD.ActionConnector.Enumerations;
namespace F4SD.ActionConnector.Models
{
public sealed class ActionResult
{
public string ActionId { get; set; }
public ActionResultStatus Status { get; set; }
/// <summary>
/// HTTP status code for HttpCall actions; null for other types.
/// </summary>
public int? HttpStatusCode { get; set; }
/// <summary>
/// Raw response body (HttpCall) or serialized output (QuickAction).
/// </summary>
public string RawResponse { get; set; }
/// <summary>
/// Named fields extracted from the response via jsonPath mappings.
/// Populated when DisplayInCockpit is true.
/// </summary>
public IDictionary<string, string> MappedFields { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Human-readable error message; set when Status is Failure.
/// </summary>
public string ErrorMessage { get; set; }
public static ActionResult Ok(string actionId, string rawResponse = null)
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Success, RawResponse = rawResponse };
public static ActionResult Fail(string actionId, string errorMessage)
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Failure, ErrorMessage = errorMessage };
public static ActionResult Skip(string actionId)
=> new ActionResult { ActionId = actionId, Status = ActionResultStatus.Skipped };
}
}

View File

@@ -0,0 +1,74 @@
using C4IT.XML;
using F4SD.ActionConnector.Enumerations;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Xml;
using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
internal class ExternalCommunicationConfiguration
{
private const string FileNameConfig = "F4SD-ExternalCommunication-Configuration.xml";
private const string FileNameConfigSchema = "F4SD-ExternalCommunication-Configuration.xsd";
private const string ConfigRootElement = "F4SD-ExternalCommunication-Configuration";
public IDictionary<TriggerEvent, Trigger> Triggers { get; } = new Dictionary<TriggerEvent, Trigger>();
private const string TriggersNodeName = "Triggers";
private const string TriggerNodeName = "Trigger";
public void Initiate(XmlElement rootElement, cXmlParser parser)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var triggersNode = rootElement.SelectSingleNode(TriggersNodeName);
parser.EnterElement(TriggersNodeName);
var triggerNodes = triggersNode.SelectNodes(TriggerNodeName);
if (triggerNodes is null || triggerNodes.Count == 0)
return;
parser.EnterElement(TriggerNodeName);
foreach (XmlElement triggerNodeElement in triggerNodes)
{
try
{
Trigger trigger = new Trigger(triggerNodeElement, parser);
if (trigger is null || !Triggers.ContainsKey(trigger.Event))
continue;
Triggers.Add(trigger.Event, trigger);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.SelectElementNext();
}
}
parser.LeaveElement(TriggerNodeName);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.LeaveElement(TriggersNodeName);
LogMethodEnd(CM);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using C4IT.XML;
using F4SD.ActionConnector.Enumerations;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Xml;
namespace F4SD.ActionConnector.Models
{
public sealed class HttpCallDefinition : ActionDefinitionBase
{
public override ActionType Type => ActionType.HttpCall;
public string Url { get; set; }
public HttpMethod Method { get; set; } = HttpMethod.Post;
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
public string Body { get; set; }
/// <summary>
/// jsonPath expressions keyed by field name, used to extract values from the response.
/// </summary>
public IDictionary<string, string> MappedFieldJsonPaths { get; set; } = new Dictionary<string, string>();
internal HttpCallDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
{
}
}
}

View File

@@ -0,0 +1,105 @@
using C4IT.XML;
using F4SD.ActionConnector.Enumerations;
using System;
using System.Collections.Generic;
using System.Xml;
using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
public sealed class QuickActionDefinition : ActionDefinitionBase
{
private const string ParametersNodeName = "Parameters";
private const string ParameterNodeName = "Parameter";
public override ActionType Type => ActionType.QuickAction;
/// <summary>
/// Reference to the Quick Action to invoke, e.g. "Protocol case in JIRA".
/// </summary>
public string QuickActionRef { get; set; }
public IDictionary<string, string> Parameters { get; private set; } = new Dictionary<string, string>();
internal QuickActionDefinition(XmlElement xNode, cXmlParser parser) : base(xNode, parser)
{
try
{
if (!IsValid) return; else IsValid = false;
XmlNode quickActionRefNode = xNode.SelectSingleNode("QuickActionRef");
if (quickActionRefNode is null)
return;
QuickActionRef = cXmlParser.GetInnerTextFromXmlElement(quickActionRefNode);
if (QuickActionRef is null)
return;
XmlNode parametersNode = xNode.SelectSingleNode(ParametersNodeName);
if (parametersNode != null && parametersNode is XmlElement paramtersNodeElement)
ParseParametersElement(paramtersNodeElement, parser);
IsValid = true;
}
catch (Exception ex)
{
LogException(ex);
}
}
private void ParseParametersElement(XmlElement parametersNodeElement, cXmlParser parser)
{
try
{
parser.EnterElement(ParametersNodeName);
XmlNodeList parameterNodes = parametersNodeElement.SelectNodes(ParameterNodeName);
if (parameterNodes?.Count > 0)
parser.EnterElement(ParameterNodeName);
foreach (XmlElement parameterNode in parameterNodes)
{
ParseParameterElement(parameterNode, parser);
}
if (parameterNodes?.Count > 0)
parser.LeaveElement(ParameterNodeName);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.LeaveElement(ParametersNodeName);
}
}
private void ParseParameterElement(XmlElement parameterNode, cXmlParser parser)
{
try
{
string parameterName = cXmlParser.GetStringFromXmlAttribute(parameterNode, "name");
string parameterVariable = cXmlParser.GetInnerTextFromXmlElement(parameterNode);
if (string.IsNullOrEmpty(parameterName) || string.IsNullOrEmpty(parameterVariable))
return;
if (Parameters.ContainsKey(parameterName))
LogEntry($"A paramter with name '{parameterName}' does allready exist.", C4IT.Logging.LogLevels.Warning);
else
Parameters.Add(parameterName, parameterVariable);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.SelectElementNext();
}
}
}
}

View File

@@ -0,0 +1,101 @@
using C4IT.XML;
using F4SD.ActionConnector.Enumerations;
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using static C4IT.Logging.cLogManager;
namespace F4SD.ActionConnector.Models
{
internal class Trigger
{
public TriggerEvent Event { get; set; }
public IDictionary<string, ActionDefinitionBase> Actions { get; } = new Dictionary<string, ActionDefinitionBase>();
public bool IsValid { get; private set; }
private const string ActionsNodeName = "Actions";
private const string ActionNodeName = "Action";
internal Trigger(XmlElement xNode, cXmlParser parser)
{
try
{
Event = cXmlParser.GetEnumFromAttribute(xNode, "action", TriggerEvent.Unknown);
XmlNode actionsNode = xNode.SelectSingleNode(ActionsNodeName);
if (actionsNode is null || !(actionsNode is XmlElement actionsNodeElement))
return;
ParseActionsNode(actionsNodeElement, parser);
IsValid = true; ;
}
catch (Exception ex)
{
LogException(ex);
}
}
private void ParseActionsNode(XmlElement actionsNode, cXmlParser parser)
{
try
{
parser.EnterElement(ActionsNodeName);
var actionNodes = actionsNode.SelectNodes(ActionNodeName);
if (actionNodes is null || actionNodes.Count == 0)
return;
parser.EnterElement(ActionNodeName);
foreach (XmlElement actionNode in actionNodes)
{
try
{
ActionType type = cXmlParser.GetEnumFromAttribute(actionNode, "type", ActionType.Unknown);
ActionDefinitionBase actionDefinition = null;
switch (type)
{
case ActionType.QuickAction:
actionDefinition = new QuickActionDefinition(actionNode, parser);
break;
case ActionType.HttpCall:
break;
case ActionType.Unknown:
case ActionType.Webhook:
default:
break;
}
if (actionDefinition?.Id != null && !Actions.ContainsKey(actionDefinition.Id))
Actions.Add(actionDefinition.Id, actionDefinition);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.SelectElementNext();
}
}
parser.LeaveElement(ActionNodeName);
}
catch (Exception ex)
{
LogException(ex);
}
finally
{
parser.LeaveElement(ActionsNodeName);
}
}
}
}