Commits (7)
...@@ -20,7 +20,7 @@ namespace pEp.DPE ...@@ -20,7 +20,7 @@ namespace pEp.DPE
/// <param name="me">The own identity that rejects the patch.</param> /// <param name="me">The own identity that rejects the patch.</param>
/// <exception cref="ArgumentNullException">The post URI is null.</exception> /// <exception cref="ArgumentNullException">The post URI is null.</exception>
/// <exception cref="HttpRequestException">The HTTP request failed.</exception> /// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <returns>The Http response.</returns> /// <returns>The HTTP response.</returns>
public static async Task<HttpResponseMessage> RejectPatch(Patch patch, PEPIdentity me) public static async Task<HttpResponseMessage> RejectPatch(Patch patch, PEPIdentity me)
{ {
// POST http://localhost:port/pEpDPE/patches/patch_id/reject // POST http://localhost:port/pEpDPE/patches/patch_id/reject
...@@ -46,9 +46,9 @@ namespace pEp.DPE ...@@ -46,9 +46,9 @@ namespace pEp.DPE
/// </summary> /// </summary>
/// <param name="patch">The patch to suggest.</param> /// <param name="patch">The patch to suggest.</param>
/// <param name="me">The own identity that suggests the patch.</param> /// <param name="me">The own identity that suggests the patch.</param>
/// <exception cref="ArgumentNullException" /> /// <exception cref="ArgumentNullException">The post URI is null.</exception>
/// <exception cref="HttpRequestException" /> /// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <returns>The Http response.</returns> /// <returns>The HTTP response.</returns>
public static async Task<HttpResponseMessage> SuggestPatch(Patch patch, PEPIdentity me) public static async Task<HttpResponseMessage> SuggestPatch(Patch patch, PEPIdentity me)
{ {
// POST http://localhost:port/pEpDPE/patches/ // POST http://localhost:port/pEpDPE/patches/
...@@ -74,9 +74,9 @@ namespace pEp.DPE ...@@ -74,9 +74,9 @@ namespace pEp.DPE
/// </summary> /// </summary>
/// <param name="patch">The patch to support.</param> /// <param name="patch">The patch to support.</param>
/// <param name="me">The own identity that supports the patch.</param> /// <param name="me">The own identity that supports the patch.</param>
/// <exception cref="ArgumentNullException" /> /// <exception cref="ArgumentNullException">The post URI is null.</exception>
/// <exception cref="HttpRequestException" /> /// <exception cref="HttpRequestException">The HTTP request failed.</exception>
/// <returns>The Http response.</returns> /// <returns>The HTTP response.</returns>
public static async Task<HttpResponseMessage> SupportPatch(Patch patch, PEPIdentity me) public static async Task<HttpResponseMessage> SupportPatch(Patch patch, PEPIdentity me)
{ {
// POST http://localhost:port/pEpDPE/patches/patch_id/support // POST http://localhost:port/pEpDPE/patches/patch_id/support
......
...@@ -156,8 +156,15 @@ namespace pEp.DPE ...@@ -156,8 +156,15 @@ namespace pEp.DPE
Log.Verbose("GetPatchMailItem: " + items.Count + " items found in " + inbox.FolderPath); Log.Verbose("GetPatchMailItem: " + items.Count + " items found in " + inbox.FolderPath);
// First filter out older items // First filter out older items
items = items.Restrict("[ReceivedTime] >= '" + patch.CreationDate.ToString("g") + "'"); try
Log.Verbose("GetPatchMailItem: Items since receival of patch: " + items?.Count ?? "0"); {
items = items.Restrict("[ReceivedTime] >= '" + DateTime.FromFileTimeUtc(patch.CreationDate).ToString("g") + "'");
Log.Verbose("GetPatchMailItem: Items since receival of patch: " + items?.Count ?? "0");
}
catch (Exception ex)
{
Log.Error("GetPatchMailItem: Error restricting items. " + ex);
}
Outlook.UserDefinedProperties up = inbox.UserDefinedProperties; Outlook.UserDefinedProperties up = inbox.UserDefinedProperties;
if (up.Find(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_ID) == null) if (up.Find(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_ID) == null)
...@@ -211,20 +218,27 @@ namespace pEp.DPE ...@@ -211,20 +218,27 @@ namespace pEp.DPE
{ {
// Determine the correct method to execute // Determine the correct method to execute
HttpResponseMessage httpResponseMessage = null; HttpResponseMessage httpResponseMessage = null;
switch (postAction) try
{ {
case PostAction.Reject: switch (postAction)
httpResponseMessage = await DPEWebClient.RejectPatch(patch, me); {
break; case PostAction.Reject:
case PostAction.Suggest: httpResponseMessage = await DPEWebClient.RejectPatch(patch, me);
httpResponseMessage = await DPEWebClient.SuggestPatch(patch, me); break;
break; case PostAction.Suggest:
case PostAction.Support: httpResponseMessage = await DPEWebClient.SuggestPatch(patch, me);
httpResponseMessage = await DPEWebClient.SupportPatch(patch, me); break;
break; case PostAction.Support:
default: httpResponseMessage = await DPEWebClient.SupportPatch(patch, me);
Log.ErrorAndFailInDebugMode("PostAsync: Unknown post action."); break;
break; default:
Log.ErrorAndFailInDebugMode("PostAsync: Unknown post action.");
break;
}
}
catch (HttpRequestException ex)
{
throw ex;
} }
if (httpResponseMessage.StatusCode != HttpStatusCode.OK) if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
...@@ -280,7 +294,7 @@ namespace pEp.DPE ...@@ -280,7 +294,7 @@ namespace pEp.DPE
if (omi != null) if (omi != null)
{ {
omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_STATUS, (int)patchStatus); omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_STATUS, (int)patchStatus);
omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE, DateTime.UtcNow); omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE, DateTime.UtcNow.ToFileTime());
omi.Save(); omi.Save();
} }
else else
......
using pEp.Extensions; using pEp.Extensions;
using System; using System;
using System.Collections.Generic;
namespace pEp.DPE namespace pEp.DPE
{ {
public class Patch public class Patch
{ {
public static readonly string REG_FILE_SCHEME = "microsoft.windows.registry:"; public static readonly string REG_FILE_SCHEME = "file:///microsoft.windows.registry";
public enum RejectReason public enum RejectReason
{ {
None, None,
...@@ -16,48 +17,21 @@ namespace pEp.DPE ...@@ -16,48 +17,21 @@ namespace pEp.DPE
RejectedByUser RejectedByUser
} }
public string Id { get; set; } = Guid.NewGuid().ToString(); internal List<PEPIdentity> ApplyTo { get; } = new List<PEPIdentity>();
public string Uri { get; set; } public string CommitMessage { get; set; }
public string Diff { get; set; } public long CreationDate { get; set; } = -1;
public string CommitMessage { get; set; } public string Diff { get; set; }
public string Tag { get; set; } public string Id { get; set; } = Guid.NewGuid().ToString();
public DateTime CreationDate { get; set; } = DateTime.UtcNow; public Uri RootPath { get; set; }
public string Tag { get; set; }
/// <summary> /// <summary>
/// Primary constructor. Needed for de/serialization. /// Primary constructor.
/// </summary> /// </summary>
public Patch() public Patch()
{ {
} }
/// <summary>
/// Constructor for a new patch.
/// </summary>
/// <param name="uri">The location to apply the patch to.</param>
/// <param name="diff">The diff to apply.</param>
/// <param name="commitMessage">The commit message for this patch.</param>
/// <param name="tag">The tag for this patch (optional).</param>
public Patch(string uri, string diff, string commitMessage, string tag = null)
{
this.CommitMessage = commitMessage;
this.Diff = diff;
this.Tag = tag;
this.Uri = uri;
}
/// <summary>
/// Constructor for an existing patch
/// </summary>
/// <param name="uri">The location to apply the patch to.</param>
/// <param name="diff">The diff to apply.</param>
/// <param name="commitMessage">The commit message for this patch.</param>
/// <param name="creationDate">The date where this patch was created.</param>
/// <param name="tag">The tag for this patch (optional).</param>
public Patch(string uri, string diff, string commitMessage, DateTime creationDate, string tag = null) : this(uri, diff, commitMessage, tag)
{
this.CreationDate = creationDate;
}
/// <summary> /// <summary>
/// Deserializes a JSON string into a Patch object. /// Deserializes a JSON string into a Patch object.
/// </summary> /// </summary>
...@@ -65,7 +39,16 @@ namespace pEp.DPE ...@@ -65,7 +39,16 @@ namespace pEp.DPE
/// <returns>The Patch object or null if an error occured.</returns> /// <returns>The Patch object or null if an error occured.</returns>
public static Patch Deserialize(string json) public static Patch Deserialize(string json)
{ {
return json.Deserialize<Patch>(); try
{
return json.Deserialize<Patch>();
}
catch (Exception ex)
{
Log.Error("Deserialize: Error occured. " + ex);
}
return null;
} }
} }
} }
...@@ -47,15 +47,23 @@ namespace pEp ...@@ -47,15 +47,23 @@ namespace pEp
// Get last edit date if available // Get last edit date if available
if ((omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE) is string editDateString) && if ((omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_DPE_PATCH_EDIT_DATE) is string editDateString) &&
DateTime.TryParse(editDateString, out DateTime savedEditDate)) long.TryParse(editDateString, out long fileTimeUtc))
{ {
editDate = savedEditDate; try
{
editDate = DateTime.FromFileTimeUtc(fileTimeUtc);
}
catch (Exception ex)
{
Log.Error("FormRegionDPE_FormRegionShowing: Error getting file time. " + ex);
editDate = null;
}
} }
// Get the patch submitter and set data context // Get the patch submitter and set data context
if (PEPIdentity.GetFromIdentity(omi, out PEPIdentity submitter) == Globals.ReturnStatus.Success) if (PEPIdentity.GetFromIdentity(omi, out PEPIdentity submitter) == Globals.ReturnStatus.Success)
{ {
this.FormControlPatchView.DataContext = new FormControlPatchViewModel(patch, submitter, patchStatus, editDate); this.FormControlPatchView.DataContext = new PatchViewModel(patch, submitter, patchStatus, editDate);
} }
else else
{ {
......
using pEp.DPE; using pEp.DPE;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace pEp.UI.Models namespace pEp.UI.Models
{ {
...@@ -18,11 +15,6 @@ namespace pEp.UI.Models ...@@ -18,11 +15,6 @@ namespace pEp.UI.Models
SupportOrRejectPatch SupportOrRejectPatch
} }
/// <summary>
/// Gets whether the patch is editable.
/// </summary>
public bool IsEditable { get; } = false;
/// <summary> /// <summary>
/// Get the patch. /// Get the patch.
/// </summary> /// </summary>
...@@ -38,12 +30,10 @@ namespace pEp.UI.Models ...@@ -38,12 +30,10 @@ namespace pEp.UI.Models
/// </summary> /// </summary>
/// <param name="patchAction">The action to perform with this patch.</param> /// <param name="patchAction">The action to perform with this patch.</param>
/// <param name="patch">The patch to be used in this dialog.</param> /// <param name="patch">The patch to be used in this dialog.</param>
/// <param name="isEditable">Whether the patch is editable.</param> public PatchDialog(PatchAction patchAction, Patch patch = null) : base(Dialog.Type.Patch, null)
public PatchDialog(PatchAction patchAction, Patch patch = null, bool isEditable = false) : base(Dialog.Type.Patch, null)
{ {
this.PatchActionType = patchAction; this.PatchActionType = patchAction;
this.Patch = patch; this.Patch = patch ?? new Patch();
this.IsEditable = isEditable;
// Set dialog title // Set dialog title
switch (patchAction) switch (patchAction)
...@@ -69,53 +59,10 @@ namespace pEp.UI.Models ...@@ -69,53 +59,10 @@ namespace pEp.UI.Models
/// <summary> /// <summary>
/// Shows a patch dialog. /// Shows a patch dialog.
/// </summary> /// </summary>
/// <param name="patchAction">The action to perform with the patch.</param> public static void ShowNewPatchDialog()
/// <param name="patch">The patch to manage.</param>
/// <param name="isEditable">Whether the patch can be edited.</param>
public static async Task ShowDialog(PatchDialog.PatchAction patchAction, Patch patch, bool isEditable = false)
{ {
// If needed, make sure we have a valid own identity PatchDialog patchDialog = new PatchDialog(PatchAction.NewPatch);
PEPIdentity me = null; new DialogHost(patchDialog).ShowDialog();
if ((patchAction != PatchDialog.PatchAction.ShowPatch) &&
(PEPIdentity.GetOwnIdentity(Globals.ThisAddIn.Settings.AccountSettingsList.First(),
out me,
true) != Globals.ReturnStatus.Success))
{
Log.Error("ShowDialog: Error getting own identity.");
return;
}
// Open dialog and retrieve result
PatchDialog patchDialog = new PatchDialog(patchAction, patch, isEditable);
if (new DialogHost(patchDialog).ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
// Perform the action the user selected
if (patchAction == PatchDialog.PatchAction.NewPatch)
{
try
{
await Globals.ThisAddIn.DistributedPolicyEngine.Suggest(patchDialog.Patch, me);
}
catch (Exception ex)
{
Log.Error("ShowDialog: Error occured. " + ex);
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
CustomMessageBox.ShowDialog(ex.Message, "Error suggesting patch", "OK");
return;
}
AdapterExtensions.ShowNotification("Patch successfully suggested", patch.CommitMessage);
}
else
{
Log.ErrorAndFailInDebugMode("ShowDialog: Unsupported patch action " + Enum.GetName(typeof(PatchDialog.PatchAction), patchAction));
}
}
} }
} }
} }
...@@ -1360,6 +1360,14 @@ namespace pEp ...@@ -1360,6 +1360,14 @@ namespace pEp
return (Properties.Resources.ImageBackstageCompatibility); return (Properties.Resources.ImageBackstageCompatibility);
} }
/// <summary>
/// Callback to get the image for the ButtonDPE.
/// </summary>
public System.Drawing.Bitmap ButtonDPE_GetImage(Office.IRibbonControl control)
{
return (Properties.Resources.ImageLogoGreen);
}
/// <summary> /// <summary>
/// Callback to get the image for the ButtonCompatibility. /// Callback to get the image for the ButtonCompatibility.
/// </summary> /// </summary>
...@@ -1408,6 +1416,30 @@ namespace pEp ...@@ -1408,6 +1416,30 @@ namespace pEp
return (Properties.Resources.Ribbon_GroupCompatibilityLabel); return (Properties.Resources.Ribbon_GroupCompatibilityLabel);
} }
/// <summary>
/// Callback to get the label text for the GroupDPE.
/// </summary>
public string GroupDPE_GetLabel(Office.IRibbonControl control)
{
return "Patch management";
}
/// <summary>
/// Callback to get the helper text for the GroupDPE.
/// </summary>
public string GroupDPE_GetHelperText(Office.IRibbonControl control)
{
return "Manage patches and change the configuration";
}
/// <summary>
/// Callback to get the visibility of the GroupDPE.
/// </summary>
public bool GroupDPE_GetIsVisible(Office.IRibbonControl control)
{
return true;
}
/// <summary> /// <summary>
/// Callback to get the visibility of the GroupPEPHome. /// Callback to get the visibility of the GroupPEPHome.
/// </summary> /// </summary>
...@@ -1453,6 +1485,14 @@ namespace pEp ...@@ -1453,6 +1485,14 @@ namespace pEp
return (Properties.Resources.Ribbon_ButtonAccountsLabel); return (Properties.Resources.Ribbon_ButtonAccountsLabel);
} }
/// <summary>
/// Callback to get the action for when the DPE button is clicked.
/// </summary>
public void ButtonDPE_Click(Office.IRibbonControl control)
{
PatchDialog.ShowNewPatchDialog();
}
/// <summary> /// <summary>
/// Callback to get the label text for the ButtonCompatibility. /// Callback to get the label text for the ButtonCompatibility.
/// </summary> /// </summary>
...@@ -1461,6 +1501,14 @@ namespace pEp ...@@ -1461,6 +1501,14 @@ namespace pEp
return (Properties.Resources.Ribbon_ButtonCompatibilityLabel); return (Properties.Resources.Ribbon_ButtonCompatibilityLabel);
} }
/// <summary>
/// Callback to get the label text for the ButtonDPE.
/// </summary>
public string ButtonDPE_GetLabel(Office.IRibbonControl control)
{
return "Patch management";
}
/// <summary> /// <summary>
/// Callback to get the label text for the LabelControlName. /// Callback to get the label text for the LabelControlName.
/// </summary> /// </summary>
......
...@@ -118,6 +118,19 @@ ...@@ -118,6 +118,19 @@
getLabel="ButtonCompatibility_GetLabel"/> getLabel="ButtonCompatibility_GetLabel"/>
</primaryItem> </primaryItem>
</group> </group>
<!--Patch Management-->
<group id="GroupDPE"
getLabel="GroupDPE_GetLabel"
getHelperText="GroupDPE_GetHelperText"
getVisible="GroupDPE_GetIsVisible">
<primaryItem>
<button id="ButtonDPE"
isDefinitive="true"
onAction="ButtonDPE_Click"
getImage="ButtonDPE_GetImage"
getLabel="ButtonDPE_GetLabel"/>
</primaryItem>
</group>
</firstColumn> </firstColumn>
<secondColumn> <secondColumn>
<!-- About --> <!-- About -->
...@@ -153,13 +166,13 @@ ...@@ -153,13 +166,13 @@
</tab> </tab>
</backstage> </backstage>
<contextMenus> <contextMenus>
<!--The Contact context menu to reset trust--> <!--The Contact context menu to reset trust-->
<contextMenu idMso="ContextMenuContactItem"> <contextMenu idMso="ContextMenuContactItem">
<button id="ButtonContextMenuResetContactTrust" <button id="ButtonContextMenuResetContactTrust"
getImage="ButtonContextMenuContactResetTrust_GetImage" getImage="ButtonContextMenuContactResetTrust_GetImage"
getLabel="ButtonContextMenuContactResetTrust_GetLabel" getLabel="ButtonContextMenuContactResetTrust_GetLabel"
onAction="ButtonContextMenuContactResetTrust_Click"/> onAction="ButtonContextMenuContactResetTrust_Click"/>
</contextMenu> </contextMenu>
</contextMenus> </contextMenus>
</customUI> </customUI>
......
...@@ -60,6 +60,19 @@ ...@@ -60,6 +60,19 @@
getLabel="ButtonCompatibility_GetLabel"/> getLabel="ButtonCompatibility_GetLabel"/>
</primaryItem> </primaryItem>
</group> </group>
<!--Patch Management-->
<group id="GroupDPE"
getLabel="GroupDPE_GetLabel"
getHelperText="GroupDPE_GetHelperText"
getVisible="GroupDPE_GetIsVisible">
<primaryItem>
<button id="ButtonDPE"
isDefinitive="true"
onAction="ButtonDPE_Click"
getImage="ButtonDPE_GetImage"
getLabel="ButtonDPE_GetLabel"/>
</primaryItem>
</group>
</firstColumn> </firstColumn>
<secondColumn> <secondColumn>
<!-- About --> <!-- About -->
......
using pEp.DPE;
using pEp.UI.Models;
using System;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace pEp.UI.ViewModels
{
internal class FormControlPatchViewModel : ViewModelBase
{
#region Fields
private readonly Patch patch;
private Brush _Background = Brushes.White;
private FlowDocument _DisplayDiff = null;
private DateTime? _EditDate = null;
private string _Explanation = null;
private bool _IsLoading = false;
private DistributedPolicyEngine.PatchStatus _Status = DistributedPolicyEngine.PatchStatus.Open;
#endregion
#region Properties
/// <summary>
/// Gets or sets the background.
/// </summary>
public Brush Background { get => this._Background; set => this.SetProperty(ref this._Background, value); }
/// <summary>
/// The commit message of the patch.
/// </summary>
public string CommitMessage
{
get => this.patch?.CommitMessage;
set
{
this.patch.CommitMessage = value;
this.OnPropertyChanged();
}
}
/// <summary>
/// Gets the creation date as string.
/// </summary>
public string CreationDateString => this.patch?.CreationDate.ToString("F");
/// <summary>
/// The diff of this patch.
/// </summary>
public string Diff
{
get => this.patch?.Diff;
set
{
this.patch.Diff = value;
this.OnPropertyChanged();
}
}
/// <summary>
/// Gets or sets the diff of this patch as formatted flow document.
/// </summary>
public FlowDocument DisplayDiff { get => this._DisplayDiff; set => this.SetProperty(ref this._DisplayDiff, value); }
/// <summary>
/// Gets or sets the last date the patch was edited.
/// </summary>
public DateTime? EditDate { get => this._EditDate; set => this.SetProperty(ref this._EditDate, value); }
/// <summary>
/// The explanation shown in the UI regarding this patch.
/// </summary>
public string Explanation { get => this._Explanation; set => this.SetProperty(ref this._Explanation, value); }
/// <summary>
/// Gets or sets whether the screen is loading.
/// </summary>
public bool IsLoading { get => this._IsLoading; set => this.SetProperty(ref this._IsLoading, value); }
/// <summary>
/// Gets whether the OK button is visible.
/// </summary>
public bool IsOKButtonVisible { get; } = true;
/// <summary>
/// Gets whether the Reject button is visible.
/// </summary>
public bool IsRejectButtonVisible { get; } = false;
/// <summary>
/// The command to accept the patch dialog.
/// </summary>
public RelayCommand OKButtonCommand => new RelayCommand(this.SupportPatch, p => (this.Status == DistributedPolicyEngine.PatchStatus.Open));
/// <summary>
/// Gets the OK button text.
/// </summary>
public string OKButtonText { get; } = Properties.Resources.Options_OKText;
/// <summary>
/// The command to reject the dialog.
/// </summary>
public RelayCommand RejectButtonCommand => new RelayCommand(this.RejectPatch, p => (this.Status == DistributedPolicyEngine.PatchStatus.Open));
/// <summary>
/// Gets the Reject button text.
/// </summary>
public string RejectButtonText { get; } = Properties.Resources.SyncWizard_RejectButton;
/// <summary>
/// Gets the submitter of this patch.
/// </summary>
public PEPIdentity Submitter { get; }
/// <summary>
/// Gets or sets the status of this patch.
/// </summary>
public DistributedPolicyEngine.PatchStatus Status { get => this._Status; set => this.SetProperty(ref this._Status, value); }
/// <summary>
/// Gets or sets the tag of the patch.
/// </summary>
public string Tag
{
get => this.patch?.Tag;
set
{
this.patch.Tag = value;
this.OnPropertyChanged();
}
}
/// <summary>
/// Gets or sets the URI of the patch.
/// </summary>
public string Uri
{
get => this.patch?.Uri;
set
{
this.patch.Uri = value;
this.OnPropertyChanged();
}
}
#endregion
#region Constructors
/// <summary>
/// Primary constructor.
/// </summary>
/// <param name="patch">The patch to create the form region with.</param>
/// <param name="submitter">The submitter of the patch.</param>
/// <param name="status">The status of the patch.</param>
/// <param name="editDate">The last time this patch was edited (accepted/supported/rejected).</param>
public FormControlPatchViewModel(Patch patch, PEPIdentity submitter, DistributedPolicyEngine.PatchStatus status = DistributedPolicyEngine.PatchStatus.Open, DateTime? editDate = null)
{
this.EditDate = editDate;
this.patch = patch;
this.Submitter = submitter;
this.IsRejectButtonVisible = true;
this.OKButtonText = "Support";
this.UpdateView(status);
}
#endregion
#region Methods
/// <summary>
/// Executes a patch task (support/reject) and shows an error message if necessary.
/// </summary>
/// <param name="patchTask">The task to execute.</param>
/// <returns>True if the patch task was executed successfully, otherwise false.</returns>
private async Task<bool> ExecutePatchTask(Task patchTask)
{
try
{
// Block UI and show loading cursor
this.IsLoading = true;
Mouse.OverrideCursor = Cursors.Wait;
// Execute the task
await patchTask;
// Unblock UI again
this.IsLoading = false;
Mouse.OverrideCursor = null;
}
catch (Exception ex)
{
this.IsLoading = false;
Mouse.OverrideCursor = null;
Log.Error("Error occured. " + ex);
// Get the innermost exception message and show message box
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
CustomMessageBox.ShowDialog(ex.Message, "Error", "OK");
return false;
}
return true;
}
/// <summary>
/// Rejects this patch.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void RejectPatch(object parameter)
{
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.patch, new PEPIdentity())))
{
this.UpdateView(DistributedPolicyEngine.PatchStatus.Rejected);
}
}
/// <summary>
/// Sets the explanation text.
/// </summary>
private void SetExplanation()
{
string editDate = "<n/a>";
if (this.EditDate != null)
{
editDate = ((DateTime)this.EditDate).ToLocalTime().ToString("F");
}
switch (this.Status)
{
case DistributedPolicyEngine.PatchStatus.Accepted:
this.Explanation = $"Accepted on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Supported:
this.Explanation = $"Supported on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Rejected:
this.Explanation = $"Rejected on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Open:
default:
this.Explanation = "New configuration changes pending approval";
break;
}
}
/// <summary>
/// Supports this patch.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void SupportPatch(object parameter)
{
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Support(this.patch, new PEPIdentity())))
{
this.UpdateView(DistributedPolicyEngine.PatchStatus.Supported);
}
}
/// <summary>
/// Updates the view according to a new patch status.
/// </summary>
/// <param name="patchStatus">The new patch status.</param>
private void UpdateView(DistributedPolicyEngine.PatchStatus patchStatus)
{
this.Status = patchStatus;
this.EditDate = DateTime.UtcNow;
this.SetExplanation();
this.DisplayDiff = PatchDialogViewModel.FormatDiff(this.Diff, this.Status == DistributedPolicyEngine.PatchStatus.Open);
this.Background = (patchStatus == DistributedPolicyEngine.PatchStatus.Open) ? Brushes.White : Brushes.WhiteSmoke;
}
#endregion
}
}
...@@ -40,7 +40,6 @@ namespace pEp.UI.ViewModels ...@@ -40,7 +40,6 @@ namespace pEp.UI.ViewModels
private RelayCommand _CommandButtonDeleteGroup = null; private RelayCommand _CommandButtonDeleteGroup = null;
private RelayCommand _CommandButtonExportKeys = null; private RelayCommand _CommandButtonExportKeys = null;
private RelayCommand _CommandButtonImportKeys = null; private RelayCommand _CommandButtonImportKeys = null;
private RelayCommand _CommandButtonNewPatch = null;
private RelayCommand _CommandButtonOK = null; private RelayCommand _CommandButtonOK = null;
private RelayCommand _CommandButtonRefreshLogs = null; private RelayCommand _CommandButtonRefreshLogs = null;
private RelayCommand _CommandButtonResetPEPStore = null; private RelayCommand _CommandButtonResetPEPStore = null;
...@@ -205,22 +204,6 @@ namespace pEp.UI.ViewModels ...@@ -205,22 +204,6 @@ namespace pEp.UI.ViewModels
} }
} }
/// <summary>
/// Gets the command to execute when the New patch button is clicked.
/// </summary>
public RelayCommand CommandButtonNewPatch
{
get
{
if (this._CommandButtonNewPatch == null)
{
this._CommandButtonNewPatch = new RelayCommand(async p => await PatchDialog.ShowDialog(PatchDialog.PatchAction.NewPatch, new Patch(), true));
}
return this._CommandButtonNewPatch;
}
}
/// <summary> /// <summary>
/// Gets the command to execute when the OK button is clicked. /// Gets the command to execute when the OK button is clicked.
/// </summary> /// </summary>
......
...@@ -9,23 +9,31 @@ using System.Collections.ObjectModel; ...@@ -9,23 +9,31 @@ using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
namespace pEp.UI.ViewModels namespace pEp.UI.ViewModels
{ {
internal class PatchDialogViewModel : DialogHostViewModel internal class PatchViewModel : DialogHostViewModel
{ {
#region Fields #region Fields
private RelayCommand _AddOrEditFileCommand = null; private RelayCommand _AddOrEditFileCommand = null;
private bool _IsCommitMessageValid = false; private Brush _Background = Brushes.White;
private bool _IsDiffValid = false; private DateTime? _EditDate = null;
private bool _IsValid = false; private string _Explanation = null;
private ConfigFile _SelectedFile = null; private FlowDocument _FormattedDiff = null;
private FlowDocument _VisibleDiff = null; private bool _IsCommitMessageValid = false;
private bool _IsDiffValid = false;
private bool _IsLoading = false;
private bool _IsValid = false;
private bool _Loaded = false;
private ConfigFile _SelectedFile = null;
private DistributedPolicyEngine.PatchStatus _Status = DistributedPolicyEngine.PatchStatus.Open;
private const string FILE_A_PREFIX = "--- "; private const string FILE_A_PREFIX = "--- ";
private const string FILE_B_PREFIX = "+++ "; private const string FILE_B_PREFIX = "+++ ";
...@@ -51,14 +59,19 @@ namespace pEp.UI.ViewModels ...@@ -51,14 +59,19 @@ namespace pEp.UI.ViewModels
} }
/// <summary> /// <summary>
/// Command to cancel the dialog. /// Gets the identities this patch will be applied to.
/// </summary> /// </summary>
public RelayCommand CancelButtonCommand => new RelayCommand(p => this.Close(null)); public ObservableCollection<PEPIdentity> ApplyTo => new ObservableCollection<PEPIdentity>(this.Dialog.Patch.ApplyTo);
/// <summary>
/// Gets or sets the background.
/// </summary>
public Brush Background { get => this._Background; set => this.SetProperty(ref this._Background, value); }
/// <summary> /// <summary>
/// Gets the Cancel button text. /// Command to cancel the dialog.
/// </summary> /// </summary>
public string CancelButtonText { get; } = Properties.Resources.Options_CancelText; public RelayCommand CancelButtonCommand => new RelayCommand(p => this.Close(null));
/// <summary> /// <summary>
/// The commit message of the patch. /// The commit message of the patch.
...@@ -74,15 +87,47 @@ namespace pEp.UI.ViewModels ...@@ -74,15 +87,47 @@ namespace pEp.UI.ViewModels
} }
} }
/// <summary>
/// Gets the collection of config files that are being modified.
/// </summary>
public ObservableCollection<ConfigFile> ConfigFiles { get; } = new ObservableCollection<ConfigFile>();
/// <summary>
/// Gets the creation date as string.
/// </summary>
public string CreationDateString
{
get
{
long? fileTime = this.Dialog?.Patch?.CreationDate;
return (fileTime != null) ? DateTime.FromFileTimeUtc((long)fileTime).ToLocalTime().ToString("F") : null;
}
}
/// <summary>
/// The own identity that acts as sender of DPE calls.
/// </summary>
public PEPIdentity From { get; }
/// <summary> /// <summary>
/// The dialog object. /// The dialog object.
/// </summary> /// </summary>
public new PatchDialog Dialog => base.Dialog as PatchDialog; public new PatchDialog Dialog => base.Dialog as PatchDialog;
/// <summary>
/// Gets or sets the last date the patch was edited.
/// </summary>
public DateTime? EditDate { get => this._EditDate; set => this.SetProperty(ref this._EditDate, value); }
/// <summary> /// <summary>
/// The explanation shown in the UI regarding this patch. /// The explanation shown in the UI regarding this patch.
/// </summary> /// </summary>
public string Explanation { get; } public string Explanation { get => this._Explanation; set => this.SetProperty(ref this._Explanation, value); }
/// <summary>
/// Gets the diff of the selected file as formatted flow document.
/// </summary>
public FlowDocument FormattedDiff { get => this._FormattedDiff; set => this.SetProperty(ref this._FormattedDiff, value); }
/// <summary> /// <summary>
/// Gets whether the Cancel button is visible. /// Gets whether the Cancel button is visible.
...@@ -92,80 +137,51 @@ namespace pEp.UI.ViewModels ...@@ -92,80 +137,51 @@ namespace pEp.UI.ViewModels
/// <summary> /// <summary>
/// Gets or sets whether the commit message is valid. /// Gets or sets whether the commit message is valid.
/// </summary> /// </summary>
public bool IsCommitMessageValid public bool IsCommitMessageValid { get => this._IsCommitMessageValid; set => this.SetProperty(ref this._IsCommitMessageValid, value); }
{
get => this._IsCommitMessageValid;
set
{
if (value != this._IsCommitMessageValid)
{
this._IsCommitMessageValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary> /// <summary>
/// Gets or sets whether the diff is valid. /// Gets or sets whether the diff is valid.
/// </summary> /// </summary>
public bool IsDiffValid public bool IsDiffValid { get => this._IsDiffValid; set => this.SetProperty(ref this._IsDiffValid, value); }
{
get => this._IsDiffValid;
set
{
if (value != this._IsDiffValid)
{
this._IsDiffValid = value;
this.OnPropertyChanged();
}
}
}
/// <summary> /// <summary>
/// Gets whether the patch in this dialog is editable. /// Gets or sets whether the view is currently loading.
/// </summary> /// </summary>
public bool IsEditable => this.Dialog.IsEditable; public bool IsLoading { get => this._IsLoading; set => this.SetProperty(ref this._IsLoading, value); }
/// <summary> /// <summary>
/// Gets whether the OK button is visible. /// Gets or sets whether this patch is valid.
/// </summary> /// </summary>
public bool IsOKButtonVisible { get; } = true; public bool IsValid { get => this._IsValid; set => this.SetProperty(ref this._IsValid, value); }
/// <summary> /// <summary>
/// Gets whether the Reject button is visible. /// Gets or sets whether the screen is fully loaded.
/// </summary> /// </summary>
public bool IsRejectButtonVisible { get; } = false; public bool Loaded { get => this._Loaded; set => this.SetProperty(ref this._Loaded, value); }
/// <summary> /// <summary>
/// Gets or sets whether this patch is valid. /// The command to accept the patch dialog.
/// </summary> /// </summary>
public bool IsValid public RelayCommand OKButtonCommand
{ {
get => this._IsValid; get
set
{ {
if (value != this._IsValid) if ((this.Dialog.PatchActionType == PatchDialog.PatchAction.NewPatch) ||
(this.Dialog.PatchActionType == PatchDialog.PatchAction.EditPatch))
{ {
this._IsValid = value; return new RelayCommand(this.CreateAndSuggestPatch, p => this.IsValid);
this.OnPropertyChanged(); }
else
{
return new RelayCommand(this.SupportPatch, p => (this.Status == DistributedPolicyEngine.PatchStatus.Open));
} }
} }
} }
/// <summary> /// <summary>
/// Gets the collection of config files that are being modified. /// The command to reject the dialog.
/// </summary>
public ObservableCollection<ConfigFile> ConfigFiles { get; } = new ObservableCollection<ConfigFile>();
/// <summary>
/// The command to accept the patch dialog.
/// </summary>
public RelayCommand OKButtonCommand => new RelayCommand(this.CreateAndSuggestPatch, p => this.IsValid);
/// <summary>
/// Gets the OK button text.
/// </summary> /// </summary>
public string OKButtonText { get; } = Properties.Resources.Options_OKText; public RelayCommand RejectButtonCommand => new RelayCommand(this.RejectPatch, p => (this.Status == DistributedPolicyEngine.PatchStatus.Open));
/// <summary> /// <summary>
/// Gets the Remove files button command /// Gets the Remove files button command
...@@ -185,6 +201,24 @@ namespace pEp.UI.ViewModels ...@@ -185,6 +201,24 @@ namespace pEp.UI.ViewModels
} }
} }
/// <summary>
/// Gets or sets the status of this patch.
/// </summary>
public DistributedPolicyEngine.PatchStatus Status { get => this._Status; set => this.SetProperty(ref this._Status, value); }
/// <summary>
/// Gets or sets the root path of the patch.
/// </summary>
public string RootPath
{
get => this.Dialog.Patch?.RootPath?.OriginalString;
set
{
this.Dialog.Patch.RootPath = new Uri(value);
this.OnPropertyChanged();
}
}
/// <summary> /// <summary>
/// Gets or sets the tag of the patch. /// Gets or sets the tag of the patch.
/// </summary> /// </summary>
...@@ -198,21 +232,16 @@ namespace pEp.UI.ViewModels ...@@ -198,21 +232,16 @@ namespace pEp.UI.ViewModels
} }
} }
/// <summary>
/// Gets the diff of the selected file as formatted flow document.
/// </summary>
public FlowDocument VisibleDiff { get => this._VisibleDiff; set => this.SetProperty(ref this._VisibleDiff, value); }
#endregion #endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Primary constructor. /// Constructor for a patch dialog.
/// </summary> /// </summary>
/// <param name="dialog">The dialog object.</param> /// <param name="dialog">The dialog object.</param>
/// <param name="closeDialogAction">The action to close the dialog.</param> /// <param name="closeDialogAction">The action to close the dialog.</param>
public PatchDialogViewModel(PatchDialog dialog, Action closeDialogAction) : base(dialog, closeDialogAction) public PatchViewModel(PatchDialog dialog, Action closeDialogAction) : base(dialog, closeDialogAction)
{ {
switch (dialog.PatchActionType) switch (dialog.PatchActionType)
{ {
...@@ -224,8 +253,6 @@ namespace pEp.UI.ViewModels ...@@ -224,8 +253,6 @@ namespace pEp.UI.ViewModels
case PatchDialog.PatchAction.SupportOrRejectPatch: case PatchDialog.PatchAction.SupportOrRejectPatch:
{ {
this.Explanation = "Support or reject patch"; this.Explanation = "Support or reject patch";
this.IsRejectButtonVisible = true;
this.OKButtonText = "Support";
} }
break; break;
case PatchDialog.PatchAction.EditPatch: case PatchDialog.PatchAction.EditPatch:
...@@ -234,9 +261,28 @@ namespace pEp.UI.ViewModels ...@@ -234,9 +261,28 @@ namespace pEp.UI.ViewModels
break; break;
} }
this.From = new PEPIdentity();
this.ValidatePatch(); this.ValidatePatch();
} }
/// <summary>
/// Constructor for the DPE form region.
/// </summary>
/// <param name="patch">The patch to create the form region with.</param>
/// <param name="submitter">The submitter of the patch.</param>
/// <param name="status">The status of the patch.</param>
/// <param name="editDate">The last time this patch was edited (accepted/supported/rejected).</param>
public PatchViewModel(Patch patch,
PEPIdentity submitter,
DistributedPolicyEngine.PatchStatus status = DistributedPolicyEngine.PatchStatus.Open,
DateTime? editDate = null) : this(new PatchDialog(PatchDialog.PatchAction.ShowPatch, patch), null)
{
this.EditDate = editDate;
this.From = submitter;
this.UpdateView(status);
this.Loaded = true;
}
#endregion #endregion
#region Methods #region Methods
...@@ -289,36 +335,29 @@ namespace pEp.UI.ViewModels ...@@ -289,36 +335,29 @@ namespace pEp.UI.ViewModels
/// Creates a patch from the view model and suggests it. /// Creates a patch from the view model and suggests it.
/// </summary> /// </summary>
/// <param name="parameter">The command parameter.</param> /// <param name="parameter">The command parameter.</param>
private void CreateAndSuggestPatch(object parameter) private async void CreateAndSuggestPatch(object parameter)
{ {
// The patch was created right now // The patch was created right now
this.Dialog.Patch.CreationDate = DateTime.UtcNow; this.Dialog.Patch.CreationDate = DateTime.UtcNow.ToFileTimeUtc();
// Get a common root URI of all diff files // Get a common root URI of all diff files
this.Dialog.Patch.Uri = this.GetRootUri(); this.Dialog.Patch.RootPath = this.GetRootPath();
// Concatenate all diffs from the selected files // Concatenate all diffs from the selected files
this.Dialog.Patch.Diff = this.UnifyDiffs(); this.Dialog.Patch.Diff = this.UnifyDiffs();
// Close with result True. // Add Apply identities
this.Close(true); this.Dialog.Patch.ApplyTo.Clear();
} foreach (PEPIdentity identity in this.ApplyTo)
/// <summary>
/// Deletes the temporary directory.
/// </summary>
private void DeleteTempDirectory()
{
try
{ {
if (Directory.Exists(DistributedPolicyEngine.DPE_TEMP_LOCATION)) this.Dialog.Patch.ApplyTo.Add(identity);
{
Directory.Delete(DistributedPolicyEngine.DPE_TEMP_LOCATION, true);
}
} }
catch (Exception ex)
// Send patch to DPE and close if successful
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Suggest(this.Dialog.Patch, this.From)))
{ {
Log.Error("DeleteTempDirectory: Error deleting temporary directory. " + ex); AdapterExtensions.ShowNotification("Patch successfully suggested", this.Dialog.Patch.CommitMessage);
this.Close(true);
} }
} }
...@@ -463,7 +502,76 @@ namespace pEp.UI.ViewModels ...@@ -463,7 +502,76 @@ namespace pEp.UI.ViewModels
return null; return null;
} }
return diff.Insert(0, $"{ PatchDialogViewModel.FILE_A_PREFIX }{ fileNameB }\n{ PatchDialogViewModel.FILE_B_PREFIX }{ fileNameB }\n").TrimEnd('\n'); return diff.Insert(0, $"{ PatchViewModel.FILE_A_PREFIX }{ fileNameB }\n{ PatchViewModel.FILE_B_PREFIX }{ fileNameB }\n").TrimEnd('\n');
}
/// <summary>
/// Deletes the temporary directory.
/// </summary>
private void DeleteTempDirectory()
{
try
{
if (Directory.Exists(DistributedPolicyEngine.DPE_TEMP_LOCATION))
{
Directory.Delete(DistributedPolicyEngine.DPE_TEMP_LOCATION, true);
}
}
catch (Exception ex)
{
Log.Error("DeleteTempDirectory: Error deleting temporary directory. " + ex);
}
}
/// <summary>
/// Executes a patch task (support/reject) and shows an error message if necessary.
/// </summary>
/// <param name="patchTask">The task to execute.</param>
/// <returns>True if the patch task was executed successfully, otherwise false.</returns>
private async Task<bool> ExecutePatchTask(Task patchTask)
{
string errorMessage = null;
try
{
// Block UI and show loading cursor
this.IsLoading = true;
Mouse.OverrideCursor = Cursors.Wait;
// Execute the task
await patchTask;
// Unblock UI again
this.IsLoading = false;
Mouse.OverrideCursor = null;
}
catch (Exception ex)
{
Log.Error("Error occured. " + ex);
// Get the innermost exception message and show message box
while (ex.InnerException != null)
{
ex = ex.InnerException;
}
errorMessage = ex.Message;
}
finally
{
this.IsLoading = false;
Mouse.OverrideCursor = null;
}
if (string.IsNullOrEmpty(errorMessage))
{
return true;
}
else
{
CustomMessageBox.ShowDialog(errorMessage, "Error", "OK");
return false;
}
} }
/// <summary> /// <summary>
...@@ -532,11 +640,12 @@ namespace pEp.UI.ViewModels ...@@ -532,11 +640,12 @@ namespace pEp.UI.ViewModels
Log.Error($"EditFileAndCreateDiff: Error reading file content from { fileName }: { ex }"); Log.Error($"EditFileAndCreateDiff: Error reading file content from { fileName }: { ex }");
} }
} }
/// <summary> /// <summary>
/// Gets a common root URI from all selected files in the dialog. /// Gets a common root URI from all selected files in the dialog.
/// </summary> /// </summary>
/// <returns>The root URI</returns> /// <returns>The root URI</returns>
private string GetRootUri() private Uri GetRootPath()
{ {
string firstFileName = this.ConfigFiles.First()?.FileName; string firstFileName = this.ConfigFiles.First()?.FileName;
if (string.IsNullOrEmpty(firstFileName)) if (string.IsNullOrEmpty(firstFileName))
...@@ -545,16 +654,16 @@ namespace pEp.UI.ViewModels ...@@ -545,16 +654,16 @@ namespace pEp.UI.ViewModels
return null; return null;
} }
string rootUri = Directory.GetParent(firstFileName).FullName.Replace('\\', '/'); string rootPath = Directory.GetParent(firstFileName).FullName.Replace('\\', '/');
foreach (ConfigFile modifiedFile in this.ConfigFiles) foreach (ConfigFile modifiedFile in this.ConfigFiles)
{ {
string folderName = Directory.GetParent(modifiedFile.FileName).FullName.Replace('\\', '/'); string folderName = Directory.GetParent(modifiedFile.FileName).FullName.Replace('\\', '/');
int i = 0; int i = 0;
while ((i < folderName.Length) && while ((i < folderName.Length) &&
(i < rootUri.Length)) (i < rootPath.Length))
{ {
if (folderName[i] != rootUri[i]) if (folderName[i] != rootPath[i])
{ {
break; break;
} }
...@@ -562,10 +671,23 @@ namespace pEp.UI.ViewModels ...@@ -562,10 +671,23 @@ namespace pEp.UI.ViewModels
i++; i++;
} }
rootUri = rootUri.Substring(0, i); rootPath = rootPath.Substring(0, i);
} }
return rootUri; return new Uri(rootPath);
}
/// <summary>
/// Rejects this patch.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void RejectPatch(object parameter)
{
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Reject(this.Dialog.Patch, this.From)))
{
this.EditDate = DateTime.UtcNow;
this.UpdateView(DistributedPolicyEngine.PatchStatus.Rejected);
}
} }
/// <summary> /// <summary>
...@@ -577,6 +699,48 @@ namespace pEp.UI.ViewModels ...@@ -577,6 +699,48 @@ namespace pEp.UI.ViewModels
this.ConfigFiles.Remove(this.SelectedFile); this.ConfigFiles.Remove(this.SelectedFile);
} }
/// <summary>
/// Sets the explanation text.
/// </summary>
private void SetExplanation()
{
string editDate = "<n/a>";
if (this.EditDate != null)
{
editDate = ((DateTime)this.EditDate).ToLocalTime().ToString("F");
}
switch (this.Status)
{
case DistributedPolicyEngine.PatchStatus.Accepted:
this.Explanation = $"Accepted on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Supported:
this.Explanation = $"Supported on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Rejected:
this.Explanation = $"Rejected on { editDate }";
break;
case DistributedPolicyEngine.PatchStatus.Open:
default:
this.Explanation = "New configuration changes pending approval";
break;
}
}
/// <summary>
/// Supports this patch.
/// </summary>
/// <param name="parameter">The command parameter.</param>
private async void SupportPatch(object parameter)
{
if (await this.ExecutePatchTask(Globals.ThisAddIn.DistributedPolicyEngine.Support(this.Dialog.Patch, this.From)))
{
this.EditDate = DateTime.UtcNow;
this.UpdateView(DistributedPolicyEngine.PatchStatus.Supported);
}
}
/// <summary> /// <summary>
/// Concatenates all diffs from the selected files into one string. /// Concatenates all diffs from the selected files into one string.
/// </summary> /// </summary>
...@@ -592,18 +756,30 @@ namespace pEp.UI.ViewModels ...@@ -592,18 +756,30 @@ namespace pEp.UI.ViewModels
} }
// Remove root parts of URI // Remove root parts of URI
diff = diff.Replace(PatchDialogViewModel.FILE_A_PREFIX + this.Dialog.Patch.Uri + "/", PatchDialogViewModel.FILE_A_PREFIX); diff = diff.Replace(PatchViewModel.FILE_A_PREFIX + this.Dialog.Patch.RootPath + "/", PatchViewModel.FILE_A_PREFIX);
diff = diff.Replace(PatchDialogViewModel.FILE_B_PREFIX + this.Dialog.Patch.Uri + "/", PatchDialogViewModel.FILE_B_PREFIX); diff = diff.Replace(PatchViewModel.FILE_B_PREFIX + this.Dialog.Patch.RootPath + "/", PatchViewModel.FILE_B_PREFIX);
return diff.TrimEnd('\n'); return diff.TrimEnd('\n');
} }
/// <summary>
/// Updates the view according to a new patch status.
/// </summary>
/// <param name="patchStatus">The new patch status.</param>
private void UpdateView(DistributedPolicyEngine.PatchStatus patchStatus)
{
this.Status = patchStatus;
this.SetExplanation();
this.FormattedDiff = PatchViewModel.FormatDiff(this.Dialog.Patch.Diff, this.Status == DistributedPolicyEngine.PatchStatus.Open);
this.Background = (patchStatus == DistributedPolicyEngine.PatchStatus.Open) ? Brushes.White : Brushes.WhiteSmoke;
}
/// <summary> /// <summary>
/// Updates the diff that is visible in the view pane. /// Updates the diff that is visible in the view pane.
/// </summary> /// </summary>
private void UpdateVisibleDiff() private void UpdateVisibleDiff()
{ {
this.VisibleDiff = PatchDialogViewModel.FormatDiff(this.SelectedFile?.Diff); this.FormattedDiff = PatchViewModel.FormatDiff(this.SelectedFile?.Diff);
} }
/// <summary> /// <summary>
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<DataTemplate DataType="{x:Type vm:InputMessageBoxViewModel}"> <DataTemplate DataType="{x:Type vm:InputMessageBoxViewModel}">
<v:InputMessageBoxView /> <v:InputMessageBoxView />
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type vm:PatchDialogViewModel}"> <DataTemplate DataType="{x:Type vm:PatchViewModel}">
<v:PatchDialogView /> <v:PatchDialogView />
</DataTemplate> </DataTemplate>
</UserControl.Resources> </UserControl.Resources>
......
...@@ -81,7 +81,7 @@ namespace pEp.UI.Views ...@@ -81,7 +81,7 @@ namespace pEp.UI.Views
case Dialog.Type.Patch: case Dialog.Type.Patch:
{ {
Debug.Assert(dialog is PatchDialog); Debug.Assert(dialog is PatchDialog);
this.Content = new PatchDialogViewModel(dialog as PatchDialog, closeDialogAction); this.Content = new PatchViewModel(dialog as PatchDialog, closeDialogAction);
} }
break; break;
default: default:
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
FontFamily="Segoe UI" FontFamily="Segoe UI"
FontSize="12" FontSize="12"
Background="{Binding Background}" Background="{Binding Background}"
d:DataContext="{d:DesignInstance Type=vm:FormControlPatchViewModel}" d:DataContext="{d:DesignInstance Type=vm:PatchViewModel}"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
...@@ -41,16 +41,60 @@ ...@@ -41,16 +41,60 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!--Hides all other content while the form region is being loaded-->
<Rectangle Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
Grid.RowSpan="8"
Grid.ZIndex="1000"
Fill="White"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding Loaded, Converter={StaticResource InvertBoolToVisibility}}" />
<!--Overlay during API calls-->
<Rectangle Grid.Column="0" <Rectangle Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="8" Grid.RowSpan="8"
Grid.ZIndex="100" Grid.ZIndex="100"
Fill="White" Fill="White"
Opacity="0.6" Opacity="0.7"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}" /> Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}" />
<!--Loading animation during API calls-->
<Image Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
Grid.RowSpan="8"
Grid.ZIndex="100"
Width="100"
Height="41"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}"
Source="pack://application:,,,/pEp;component/Resources/ImageLogoMedium.png"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.1"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Grid.Row="0" Grid.Row="0"
...@@ -60,7 +104,7 @@ ...@@ -60,7 +104,7 @@
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Grid.Row="1" Grid.Row="1"
Content="{Binding Submitter.DisplayString}" Content="{Binding From.DisplayString}"
Margin="10,1" /> Margin="10,1" />
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
...@@ -87,7 +131,7 @@ ...@@ -87,7 +131,7 @@
BorderBrush="Black" BorderBrush="Black"
BorderThickness="1" BorderThickness="1"
Margin="5,10,10,10" Margin="5,10,10,10"
Document="{Binding DisplayDiff}" /> Document="{Binding FormattedDiff}" />
<Label Grid.Column="0" <Label Grid.Column="0"
Grid.Row="4" Grid.Row="4"
Content="Uri" Content="Uri"
...@@ -98,7 +142,7 @@ ...@@ -98,7 +142,7 @@
Background="{Binding Background}" Background="{Binding Background}"
VerticalAlignment="Center" VerticalAlignment="Center"
Padding="2" Padding="2"
Text="{Binding Uri}" Text="{Binding RootPath}"
IsReadOnly="True" IsReadOnly="True"
Margin="5,10,10,10"/> Margin="5,10,10,10"/>
<Label Grid.Column="0" <Label Grid.Column="0"
...@@ -132,15 +176,13 @@ ...@@ -132,15 +176,13 @@
Grid.Row="7" Grid.Row="7"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Button Content="{Binding OKButtonText}" <Button Content="Support"
Command="{Binding OKButtonCommand}" Command="{Binding OKButtonCommand}"
IsDefault="True"
Visibility="{Binding IsOKButtonVisible, Converter={StaticResource BoolToVisibility}}"
Style="{StaticResource StyleButtonGray}" Style="{StaticResource StyleButtonGray}"
IsDefault="True"
Margin="10" /> Margin="10" />
<Button Content="{Binding RejectButtonText}" <Button Content="{x:Static p:Resources.SyncWizard_RejectButton}"
Command="{Binding RejectButtonCommand}" Command="{Binding RejectButtonCommand}"
Visibility="{Binding IsRejectButtonVisible, Converter={StaticResource BoolToVisibility}}"
Style="{StaticResource StyleButtonGray}" Style="{StaticResource StyleButtonGray}"
Margin="10" /> Margin="10" />
</StackPanel> </StackPanel>
......
...@@ -157,20 +157,6 @@ ...@@ -157,20 +157,6 @@
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</StackPanel> </StackPanel>
</ListBoxItem>--> </ListBoxItem>-->
<!--<ListBoxItem Visibility="{Binding Path=IsDPEEnabled, Converter={StaticResource BoolToVisibility}}">-->
<ListBoxItem>
<StackPanel Orientation="Horizontal"
Height="{StaticResource PageSelectorHeight}">
<Image Source="pack://application:,,,/pEp;component/Resources/ImageOptionsGroupEncryption.png"
Width="26"
Height="13"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBlock Text="Patch management"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
<ListBoxItem Visibility="{Binding Path=IsDeveloperModeEnabled, Converter={StaticResource BoolToVisibility}}"> <ListBoxItem Visibility="{Binding Path=IsDeveloperModeEnabled, Converter={StaticResource BoolToVisibility}}">
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Height="{StaticResource PageSelectorHeight}"> Height="{StaticResource PageSelectorHeight}">
...@@ -965,26 +951,6 @@ ...@@ -965,26 +951,6 @@
</StackPanel> </StackPanel>
</TabItem>--> </TabItem>-->
<!--Distributed Policy Management-->
<TabItem>
<StackPanel Margin="{StaticResource PagePadding}">
<!-- DPE title -->
<TextBlock Text="Distributed Policy Management"
Margin="6,6,0,0"
FontWeight="Bold"
FontSize="16" />
<Separator />
<!--New patch button-->
<Button Content="New patch"
Style="{StaticResource StyleButtonGray}"
Margin="10,5,5,5"
Command="{Binding CommandButtonNewPatch}"
HorizontalAlignment="Left"/>
</StackPanel>
</TabItem>
<!-- Developer --> <!-- Developer -->
<TabItem> <TabItem>
<Grid Margin="{StaticResource PagePadding}"> <Grid Margin="{StaticResource PagePadding}">
......
...@@ -3,10 +3,11 @@ ...@@ -3,10 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p="clr-namespace:pEp.Properties"
xmlns:ui="clr-namespace:pEp.UI" xmlns:ui="clr-namespace:pEp.UI"
xmlns:vm="clr-namespace:pEp.UI.ViewModels" xmlns:vm="clr-namespace:pEp.UI.ViewModels"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:PatchDialogViewModel}" d:DataContext="{d:DesignInstance Type=vm:PatchViewModel}"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
...@@ -34,6 +35,48 @@ ...@@ -34,6 +35,48 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!--Overlay during API calls-->
<Rectangle Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
Grid.RowSpan="5"
Grid.ZIndex="99"
Fill="White"
Opacity="0.7"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}" />
<!--Loading animation during API calls-->
<Image Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="0"
Grid.RowSpan="5"
Grid.ZIndex="100"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibility}}"
Source="pack://application:,,,/pEp;component/Resources/ImageLogoMedium.png"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.1"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Label Grid.Row="0" <Label Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
...@@ -73,38 +116,35 @@ ...@@ -73,38 +116,35 @@
BorderThickness="1" BorderThickness="1"
Margin="5,10,10,10" Margin="5,10,10,10"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
Document="{Binding VisibleDiff}"/> Document="{Binding FormattedDiff}"/>
<ui:TextBoxWithPlaceholder Grid.Column="0" <ui:TextBoxWithPlaceholder Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Grid.Row="2" Grid.Row="2"
Text="{Binding CommitMessage, UpdateSourceTrigger=PropertyChanged}" Text="{Binding CommitMessage, UpdateSourceTrigger=PropertyChanged}"
IsValidInput="{Binding IsCommitMessageValid}" IsValidInput="{Binding IsCommitMessageValid}"
Placeholder="Commit message" Placeholder="Commit message"
Margin="5,10,10,10" Margin="5,10,10,10" />
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<ui:TextBoxWithPlaceholder Grid.Column="0" <ui:TextBoxWithPlaceholder Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Grid.Row="3" Grid.Row="3"
Text="{Binding Tag, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Tag, UpdateSourceTrigger=PropertyChanged}"
Placeholder="Tag (optional)" Placeholder="Tag (optional)"
Margin="5,10,10,10" Margin="5,10,10,10" />
Visibility="{Binding IsEditable, Converter={StaticResource BoolToVisibility}}"/>
<StackPanel Grid.Column="0" <StackPanel Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Grid.Row="4" Grid.Row="4"
Orientation="Horizontal" Orientation="Horizontal"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="10"> Margin="10">
<Button Content="{Binding OKButtonText}" <Button Content="{x:Static p:Resources.Options_OKText}"
Command="{Binding OKButtonCommand}" Command="{Binding OKButtonCommand}"
IsEnabled="{Binding IsValid}"
IsDefault="True" IsDefault="True"
Margin="10" Margin="10"
Style="{StaticResource StyleButtonGray}"/> Style="{StaticResource StyleButtonGray}"/>
<Button Content="{Binding CancelButtonText}" <Button Content="{x:Static p:Resources.Options_CancelText}"
Margin="10" Margin="10"
Command="{Binding CancelButtonCommand}" Command="{Binding CancelButtonCommand}"
Style="{StaticResource StyleButtonGray}"/> Style="{StaticResource StyleButtonGray}"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>
...@@ -24,7 +24,7 @@ namespace pEp.UI.Views ...@@ -24,7 +24,7 @@ namespace pEp.UI.Views
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{ {
// Edit the file that is being double-clicked // Edit the file that is being double-clicked
(this.DataContext as PatchDialogViewModel)?.AddOrEditFileCommand?.Execute(((sender as ContentControl)?.DataContext as ConfigFile)?.FileName); (this.DataContext as PatchViewModel)?.AddOrEditFileCommand?.Execute(((sender as ContentControl)?.DataContext as ConfigFile)?.FileName);
} }
} }
} }
...@@ -440,14 +440,13 @@ ...@@ -440,14 +440,13 @@
<Compile Include="UI\TextBoxWithPlaceholder.xaml.cs"> <Compile Include="UI\TextBoxWithPlaceholder.xaml.cs">
<DependentUpon>TextBoxWithPlaceholder.xaml</DependentUpon> <DependentUpon>TextBoxWithPlaceholder.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\ViewModels\FormControlPatchViewModel.cs" />
<Compile Include="UI\ViewModels\GroupWizardAddMembersViewModel.cs" /> <Compile Include="UI\ViewModels\GroupWizardAddMembersViewModel.cs" />
<Compile Include="UI\ViewModels\GroupWizardNewListViewModel.cs" /> <Compile Include="UI\ViewModels\GroupWizardNewListViewModel.cs" />
<Compile Include="UI\ViewModels\GroupWizardViewModel.cs" /> <Compile Include="UI\ViewModels\GroupWizardViewModel.cs" />
<Compile Include="UI\ViewModels\CustomMessageBoxViewModel.cs" /> <Compile Include="UI\ViewModels\CustomMessageBoxViewModel.cs" />
<Compile Include="UI\ViewModels\InputMessageBoxViewModel.cs" /> <Compile Include="UI\ViewModels\InputMessageBoxViewModel.cs" />
<Compile Include="UI\ViewModels\MessageBoxBaseViewModel.cs" /> <Compile Include="UI\ViewModels\MessageBoxBaseViewModel.cs" />
<Compile Include="UI\ViewModels\PatchDialogViewModel.cs" /> <Compile Include="UI\ViewModels\PatchViewModel.cs" />
<Compile Include="UI\Views\CustomMessageBoxView.xaml.cs"> <Compile Include="UI\Views\CustomMessageBoxView.xaml.cs">
<DependentUpon>CustomMessageBoxView.xaml</DependentUpon> <DependentUpon>CustomMessageBoxView.xaml</DependentUpon>
</Compile> </Compile>
......