Skip to content

Commit 139bd91

Browse files
Control Panel: full Rules editor parity - per-item criterion/action edit dialogs, per-item remove, action reorder (up/down), AND/OR match mode, all 10 action types parameterized
1 parent d5ac3a2 commit 139bd91

4 files changed

Lines changed: 854 additions & 180 deletions

File tree

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using hMailServer.ControlPanel.Services;
5+
6+
namespace hMailServer.ControlPanel.Views
7+
{
8+
/// <summary>Add/edit a single rule action, with the contextual fields for each action type.</summary>
9+
public class RuleActionDialog : Window
10+
{
11+
private readonly int ruleId_;
12+
private readonly int actionId_; // 0 = new
13+
14+
private readonly ComboBox type_ = new() { FontSize = 13, Margin = new Thickness(0, 0, 0, 12) };
15+
16+
private readonly TextBox to_ = new();
17+
private readonly CheckBox abortSpam_ = new() { Content = "Abort on messages marked as spam", FontSize = 13, Margin = new Thickness(0, 4, 0, 0) };
18+
private readonly TextBox fromName_ = new();
19+
private readonly TextBox fromAddress_ = new();
20+
private readonly TextBox subject_ = new();
21+
private readonly TextBox body_ = new() { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, Height = 90, VerticalScrollBarVisibility = ScrollBarVisibility.Auto };
22+
private readonly TextBox imapFolder_ = new();
23+
private readonly TextBox scriptFunction_ = new();
24+
private readonly TextBox headerName_ = new();
25+
private readonly TextBox value_ = new();
26+
private readonly ComboBox route_ = new() { FontSize = 13, Margin = new Thickness(0, 0, 0, 8) };
27+
private readonly TextBox bindAddress_ = new();
28+
29+
private readonly StackPanel forwardPanel_ = new();
30+
private readonly StackPanel replyPanel_ = new();
31+
private readonly StackPanel folderPanel_ = new();
32+
private readonly StackPanel scriptPanel_ = new();
33+
private readonly StackPanel headerPanel_ = new();
34+
private readonly StackPanel routePanel_ = new();
35+
private readonly StackPanel bindPanel_ = new();
36+
private readonly TextBlock noParams_ = new() { Text = "This action has no additional parameters.", FontSize = 12.5, Margin = new Thickness(0, 4, 0, 0) };
37+
38+
public RuleActionDialog(Window owner, int ruleId, int actionId)
39+
{
40+
ruleId_ = ruleId;
41+
actionId_ = actionId;
42+
Owner = owner;
43+
Title = actionId == 0 ? "Add action" : "Edit action";
44+
Width = 520;
45+
Height = 540;
46+
WindowStartupLocation = WindowStartupLocation.CenterOwner;
47+
SetResourceReference(BackgroundProperty, "ApplicationBackgroundBrush");
48+
49+
var root = new Grid { Margin = new Thickness(18) };
50+
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
51+
root.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
52+
root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
53+
54+
var header = new TextBlock { Text = "THEN", FontSize = 20, FontWeight = FontWeights.SemiBold, Margin = new Thickness(2, 0, 0, 12) };
55+
header.SetResourceReference(Control.ForegroundProperty, "TextFillColorPrimaryBrush");
56+
Grid.SetRow(header, 0);
57+
root.Children.Add(header);
58+
59+
var body = new StackPanel();
60+
body.Children.Add(Label("Action"));
61+
type_.Items.Add(Combo("Delete e-mail", 1));
62+
type_.Items.Add(Combo("Forward e-mail", 2));
63+
type_.Items.Add(Combo("Reply", 3));
64+
type_.Items.Add(Combo("Move to IMAP folder", 4));
65+
type_.Items.Add(Combo("Run script function", 5));
66+
type_.Items.Add(Combo("Stop rule processing", 6));
67+
type_.Items.Add(Combo("Set header value", 7));
68+
type_.Items.Add(Combo("Send using route", 8));
69+
type_.Items.Add(Combo("Create copy", 9));
70+
type_.Items.Add(Combo("Bind to address", 10));
71+
type_.SelectionChanged += (s, e) => UpdateVisibility();
72+
body.Children.Add(type_);
73+
74+
// Forward
75+
forwardPanel_.Children.Add(Label("To"));
76+
forwardPanel_.Children.Add(Input(to_));
77+
forwardPanel_.Children.Add(abortSpam_);
78+
body.Children.Add(forwardPanel_);
79+
80+
// Reply
81+
replyPanel_.Children.Add(Label("From (name)"));
82+
replyPanel_.Children.Add(Input(fromName_));
83+
replyPanel_.Children.Add(Label("From (address)"));
84+
replyPanel_.Children.Add(Input(fromAddress_));
85+
replyPanel_.Children.Add(Label("Subject"));
86+
replyPanel_.Children.Add(Input(subject_));
87+
replyPanel_.Children.Add(Label("Body"));
88+
Input(body_);
89+
replyPanel_.Children.Add(body_);
90+
body.Children.Add(replyPanel_);
91+
92+
// Move to folder
93+
folderPanel_.Children.Add(Label("IMAP folder (e.g. INBOX.Archive)"));
94+
folderPanel_.Children.Add(Input(imapFolder_));
95+
body.Children.Add(folderPanel_);
96+
97+
// Script
98+
scriptPanel_.Children.Add(Label("Script function"));
99+
scriptPanel_.Children.Add(Input(scriptFunction_));
100+
body.Children.Add(scriptPanel_);
101+
102+
// Set header
103+
headerPanel_.Children.Add(Label("Header name"));
104+
headerPanel_.Children.Add(Input(headerName_));
105+
headerPanel_.Children.Add(Label("Value"));
106+
headerPanel_.Children.Add(Input(value_));
107+
body.Children.Add(headerPanel_);
108+
109+
// Route
110+
routePanel_.Children.Add(Label("Route"));
111+
routePanel_.Children.Add(route_);
112+
body.Children.Add(routePanel_);
113+
114+
// Bind to address
115+
bindPanel_.Children.Add(Label("IP address"));
116+
bindPanel_.Children.Add(Input(bindAddress_));
117+
body.Children.Add(bindPanel_);
118+
119+
body.Children.Add(noParams_);
120+
121+
var scroll = new ScrollViewer { Content = body, VerticalScrollBarVisibility = ScrollBarVisibility.Auto };
122+
Grid.SetRow(scroll, 1);
123+
root.Children.Add(scroll);
124+
125+
var buttons = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(0, 12, 0, 0) };
126+
var save = new Wpf.Ui.Controls.Button { Content = "Save", Appearance = Wpf.Ui.Controls.ControlAppearance.Primary, Margin = new Thickness(0, 0, 8, 0) };
127+
save.Click += (s, e) => Save();
128+
var cancel = new Wpf.Ui.Controls.Button { Content = "Cancel" };
129+
cancel.Click += (s, e) => Close();
130+
buttons.Children.Add(save);
131+
buttons.Children.Add(cancel);
132+
Grid.SetRow(buttons, 2);
133+
root.Children.Add(buttons);
134+
135+
Content = root;
136+
Loaded += (s, e) => Load();
137+
}
138+
139+
private void UpdateVisibility()
140+
{
141+
int type = ComboValue(type_);
142+
forwardPanel_.Visibility = type == 2 ? Visibility.Visible : Visibility.Collapsed;
143+
replyPanel_.Visibility = type == 3 ? Visibility.Visible : Visibility.Collapsed;
144+
folderPanel_.Visibility = type == 4 ? Visibility.Visible : Visibility.Collapsed;
145+
scriptPanel_.Visibility = type == 5 ? Visibility.Visible : Visibility.Collapsed;
146+
headerPanel_.Visibility = type == 7 ? Visibility.Visible : Visibility.Collapsed;
147+
routePanel_.Visibility = type == 8 ? Visibility.Visible : Visibility.Collapsed;
148+
bindPanel_.Visibility = type == 10 ? Visibility.Visible : Visibility.Collapsed;
149+
noParams_.Visibility = (type == 1 || type == 6 || type == 9) ? Visibility.Visible : Visibility.Collapsed;
150+
}
151+
152+
private void LoadRoutes()
153+
{
154+
route_.Items.Clear();
155+
dynamic routes = ServerSession.Current.Application.Settings.Routes;
156+
try
157+
{
158+
int count = (int) routes.Count;
159+
for (int i = 0; i < count; i++)
160+
{
161+
dynamic r = routes.Item[i];
162+
route_.Items.Add(new ComboBoxItem { Content = (string) r.DomainName, Tag = (int) r.ID });
163+
ServerSession.Release(r);
164+
}
165+
}
166+
finally
167+
{
168+
ServerSession.Release(routes);
169+
}
170+
}
171+
172+
private dynamic FindRule(dynamic rules) => rules.ItemByDBID[ruleId_];
173+
174+
private void Load()
175+
{
176+
LoadRoutes();
177+
178+
if (actionId_ == 0)
179+
{
180+
SelectCombo(type_, 1);
181+
UpdateVisibility();
182+
return;
183+
}
184+
185+
dynamic rules = ServerSession.Current.Application.Rules;
186+
try
187+
{
188+
dynamic rule = FindRule(rules);
189+
if (rule == null) { Close(); return; }
190+
dynamic actions = rule.Actions;
191+
try
192+
{
193+
dynamic a = actions.ItemByDBID[actionId_];
194+
int type = (int) a.Type;
195+
SelectCombo(type_, type);
196+
to_.Text = (string) a.To ?? "";
197+
abortSpam_.IsChecked = (bool) a.AbortSpamFlagged;
198+
fromName_.Text = (string) a.FromName ?? "";
199+
fromAddress_.Text = (string) a.FromAddress ?? "";
200+
subject_.Text = (string) a.Subject ?? "";
201+
body_.Text = (string) a.Body ?? "";
202+
imapFolder_.Text = (string) a.IMAPFolder ?? "";
203+
scriptFunction_.Text = (string) a.ScriptFunction ?? "";
204+
headerName_.Text = (string) a.HeaderName ?? "";
205+
value_.Text = (string) a.Value ?? "";
206+
bindAddress_.Text = (string) a.Value ?? "";
207+
SelectCombo(route_, (int) a.RouteID);
208+
ServerSession.Release(a);
209+
}
210+
finally
211+
{
212+
ServerSession.Release(actions);
213+
}
214+
ServerSession.Release(rule);
215+
}
216+
catch (Exception ex)
217+
{
218+
MessageBox.Show("Could not load the action: " + ex.Message, "Control Panel");
219+
Close();
220+
return;
221+
}
222+
finally
223+
{
224+
ServerSession.Release(rules);
225+
}
226+
227+
UpdateVisibility();
228+
}
229+
230+
private void Save()
231+
{
232+
int type = ComboValue(type_);
233+
234+
dynamic rules = ServerSession.Current.Application.Rules;
235+
try
236+
{
237+
dynamic rule = FindRule(rules);
238+
if (rule == null) { Close(); return; }
239+
dynamic actions = rule.Actions;
240+
try
241+
{
242+
dynamic a = actionId_ == 0 ? actions.Add() : actions.ItemByDBID[actionId_];
243+
a.RuleID = ruleId_;
244+
a.Type = type;
245+
switch (type)
246+
{
247+
case 2:
248+
a.To = to_.Text.Trim();
249+
a.AbortSpamFlagged = abortSpam_.IsChecked == true;
250+
break;
251+
case 3:
252+
a.FromName = fromName_.Text.Trim();
253+
a.FromAddress = fromAddress_.Text.Trim();
254+
a.Subject = subject_.Text;
255+
a.Body = body_.Text;
256+
a.AbortSpamFlagged = abortSpam_.IsChecked == true;
257+
break;
258+
case 4:
259+
a.IMAPFolder = imapFolder_.Text.Trim();
260+
break;
261+
case 5:
262+
a.ScriptFunction = scriptFunction_.Text.Trim();
263+
break;
264+
case 7:
265+
a.HeaderName = headerName_.Text.Trim();
266+
a.Value = value_.Text;
267+
break;
268+
case 8:
269+
a.RouteID = ComboValue(route_);
270+
break;
271+
case 10:
272+
a.Value = bindAddress_.Text.Trim();
273+
break;
274+
}
275+
a.Save();
276+
ServerSession.Release(a);
277+
}
278+
finally
279+
{
280+
ServerSession.Release(actions);
281+
}
282+
rule.Save();
283+
ServerSession.Release(rule);
284+
Close();
285+
}
286+
catch (Exception ex)
287+
{
288+
MessageBox.Show("Could not save the action: " + ex.Message, "Control Panel");
289+
}
290+
finally
291+
{
292+
ServerSession.Release(rules);
293+
}
294+
}
295+
296+
// ---- UI helpers ----
297+
298+
private static TextBlock Label(string text)
299+
{
300+
var t = new TextBlock { Text = text, FontSize = 12.5, Margin = new Thickness(0, 8, 0, 4) };
301+
t.SetResourceReference(Control.ForegroundProperty, "TextFillColorSecondaryBrush");
302+
return t;
303+
}
304+
305+
private static TextBox Input(TextBox box)
306+
{
307+
box.FontSize = 13;
308+
box.Padding = new Thickness(6);
309+
box.Margin = new Thickness(0, 0, 0, 8);
310+
box.Background = System.Windows.Media.Brushes.Transparent;
311+
box.SetResourceReference(Control.ForegroundProperty, "TextFillColorPrimaryBrush");
312+
return box;
313+
}
314+
315+
private static ComboBoxItem Combo(string text, int value) => new() { Content = text, Tag = value };
316+
317+
private static void SelectCombo(ComboBox combo, int value)
318+
{
319+
foreach (ComboBoxItem item in combo.Items)
320+
if ((int) item.Tag == value) { combo.SelectedItem = item; return; }
321+
}
322+
323+
private static int ComboValue(ComboBox combo) => combo.SelectedItem is ComboBoxItem item ? (int) item.Tag : 0;
324+
}
325+
}

0 commit comments

Comments
 (0)