public ScriptTriggersPage(ScriptProbe probe) { Title = "Script Triggers"; ScriptProbe scriptProbe = probe as ScriptProbe; ListView triggerList = new ListView(); triggerList.ItemTemplate = new DataTemplate(typeof(TextCell)); triggerList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}")); triggerList.ItemsSource = scriptProbe.Triggers; Content = triggerList; ToolbarItems.Add(new ToolbarItem("+", null, async() => { if (scriptProbe.Protocol.Probes.Where(p => p != scriptProbe && p.Enabled).Count() > 0) { await Navigation.PushAsync(new AddScriptProbeTriggerPage(scriptProbe)); } else { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("You must enable probes before adding triggers."); } })); ToolbarItems.Add(new ToolbarItem("-", null, async() => { if (triggerList.SelectedItem != null && await DisplayAlert("Confirm Delete", "Are you sure you want to delete the selected trigger?", "Yes", "Cancel")) { scriptProbe.Triggers.Remove(triggerList.SelectedItem as SensusService.Probes.User.Trigger); triggerList.SelectedItem = null; } })); }
public ScriptRunnersPage(ScriptProbe probe) { Title = "Scripts"; ListView scriptRunnersList = new ListView(ListViewCachingStrategy.RecycleElement); scriptRunnersList.ItemTemplate = new DataTemplate(typeof(TextCell)); scriptRunnersList.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(ScriptRunner.Caption)); scriptRunnersList.ItemsSource = probe.ScriptRunners; scriptRunnersList.ItemTapped += async(o, e) => { if (scriptRunnersList.SelectedItem == null) { return; } ScriptRunner selectedScriptRunner = scriptRunnersList.SelectedItem as ScriptRunner; string selectedAction = await DisplayActionSheet(selectedScriptRunner.Name, "Cancel", null, "Edit", "Copy", "Delete"); if (selectedAction == "Edit") { await Navigation.PushAsync(new ScriptRunnerPage(selectedScriptRunner)); } else if (selectedAction == "Copy") { ScriptRunner copy = selectedScriptRunner.Copy(); //MakeNameUnique(copy, probe); probe.ScriptRunners.Add(copy); } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedScriptRunner.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { await selectedScriptRunner.StopAsync(); selectedScriptRunner.Enabled = false; selectedScriptRunner.Triggers.Clear(); probe.ScriptRunners.Remove(selectedScriptRunner); scriptRunnersList.SelectedItem = null; // reset manually since it's not done automatically } } }; ToolbarItems.Add(new ToolbarItem(null, "plus.png", () => { probe.ScriptRunners.Add(new ScriptRunner("New Script", probe)); })); Content = scriptRunnersList; }
/// <summary> /// Initializes a new instance of the <see cref="ScriptRunnersPage"/> class. /// </summary> /// <param name="probe">Probe to display.</param> public ScriptRunnersPage(ScriptProbe probe) { _probe = probe; Title = "Scripts"; _scriptRunnersList = new ListView(); _scriptRunnersList.ItemTemplate = new DataTemplate(typeof(TextCell)); _scriptRunnersList.ItemTemplate.SetBinding(TextCell.TextProperty, "Name"); _scriptRunnersList.ItemTapped += async(o, e) => { if (_scriptRunnersList.SelectedItem == null) { return; } ScriptRunner selectedScriptRunner = _scriptRunnersList.SelectedItem as ScriptRunner; string selectedAction = await DisplayActionSheet(selectedScriptRunner.Name, "Cancel", null, "Edit", "Delete"); if (selectedAction == "Edit") { ScriptRunnerPage scriptRunnerPage = new ScriptRunnerPage(selectedScriptRunner); scriptRunnerPage.Disappearing += (oo, ee) => { Bind(); }; await Navigation.PushAsync(scriptRunnerPage); } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedScriptRunner.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { selectedScriptRunner.Stop(); selectedScriptRunner.Enabled = false; selectedScriptRunner.Triggers.Clear(); _probe.ScriptRunners.Remove(selectedScriptRunner); _scriptRunnersList.SelectedItem = null; // reset manually since it's not done automatically } } }; ToolbarItems.Add(new ToolbarItem(null, "plus.png", () => { _probe.ScriptRunners.Add(new ScriptRunner("New Script", _probe)); })); Bind(); Content = _scriptRunnersList; }
public void MakeNameUnique(ScriptRunner runner, ScriptProbe probe) { try { string pattern = @"\s*-\s*Copy\s*(?<number>\d*)$"; runner.Name = Regex.Replace(runner.Name, pattern, ""); string countString = probe.ScriptRunners.Max(x => Regex.Match(x.Name, $@"(?<={runner.Name}){pattern}").Groups["number"].Value); int.TryParse(countString, out int count); count = Math.Max(count, probe.ScriptRunners.Count(x => Regex.IsMatch(x.Name, $@"(?<={runner.Name})({pattern})?$")) - 1); runner.Name += $" - Copy {count + 1}"; } catch { SensusServiceHelper.Get().Logger.Log("Error creating unique script runner name", LoggingLevel.Normal, GetType()); } }
public void ScriptCopyNewIdTest() { ScriptProbe probe = new ScriptProbe(); ScriptRunner runner = new ScriptRunner("test", probe); Script script = new Script(runner); InputGroup group = new InputGroup(); Input input1 = new SliderInput(); Input input2 = new SliderInput(); group.Inputs.Add(input1); group.Inputs.Add(input2); script.InputGroups.Add(group); Script copy = script.Copy(true); Assert.AreSame(script.Runner, copy.Runner); Assert.AreNotEqual(script.Id, copy.Id); Assert.AreEqual(script.InputGroups.Single().Id, copy.InputGroups.Single().Id); Assert.AreEqual(script.InputGroups.Single().Inputs.First().Id, copy.InputGroups.Single().Inputs.First().Id); Assert.AreEqual(script.InputGroups.Count, copy.InputGroups.Count); Assert.AreEqual(script.InputGroups.Single().Inputs.Count, copy.InputGroups.Single().Inputs.Count); }
public ProbePage(Probe probe) { Title = "Probe"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (StackLayout stack in UiProperty.GetPropertyStacks(probe)) { contentLayout.Children.Add(stack); } #region script probes if (probe is ScriptProbe) { ScriptProbe scriptProbe = probe as ScriptProbe; Button editScriptButton = new Button { Text = "Edit Script", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(editScriptButton); editScriptButton.Clicked += async(oo, e) => { await Navigation.PushAsync(new ScriptPage(scriptProbe.Script)); }; Button viewScriptTriggersButton = new Button { Text = "Edit Triggers", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(viewScriptTriggersButton); viewScriptTriggersButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ScriptTriggersPage(scriptProbe)); }; } #endregion #region anonymization List <PropertyInfo> anonymizableProperties = probe.DatumType.GetProperties().Where(property => property.GetCustomAttribute <Anonymizable>() != null).ToList(); if (anonymizableProperties.Count > 0) { contentLayout.Children.Add(new Label { Text = "Anonymization", FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }); foreach (PropertyInfo anonymizableProperty in anonymizableProperties) { Anonymizable anonymizableAttribute = anonymizableProperty.GetCustomAttribute <Anonymizable>(true); Label propertyLabel = new Label { Text = (anonymizableAttribute.PropertyDisplayName ?? anonymizableProperty.Name) + ":", FontSize = 20, HorizontalOptions = LayoutOptions.Start }; // populate a picker of anonymizers for the current property Picker anonymizerPicker = new Picker { HorizontalOptions = LayoutOptions.FillAndExpand }; anonymizerPicker.Items.Add("None"); foreach (Anonymizer anonymizer in anonymizableAttribute.AvailableAnonymizers) { anonymizerPicker.Items.Add(anonymizer.DisplayText); } anonymizerPicker.SelectedIndexChanged += (o, e) => { Anonymizer selectedAnonymizer = null; if (anonymizerPicker.SelectedIndex > 0) { selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1]; // subtract one from the selected index since the JsonAnonymizer's collection of anonymizers start after the "None" option within the picker. } probe.Protocol.JsonAnonymizer.SetAnonymizer(anonymizableProperty, selectedAnonymizer); }; // set the picker's index to the current anonymizer (or "None" if there is no current) Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty); int currentIndex = 0; if (currentAnonymizer != null) { currentIndex = anonymizableAttribute.AvailableAnonymizers.IndexOf(currentAnonymizer) + 1; } anonymizerPicker.SelectedIndex = currentIndex; StackLayout anonymizablePropertyStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { propertyLabel, anonymizerPicker } }; contentLayout.Children.Add(anonymizablePropertyStack); } } #endregion Content = new ScrollView { Content = contentLayout }; }
public ScriptRunner(string name, ScriptProbe probe) : this() { Name = name; Probe = probe; }
/// <summary> /// Initializes a new instance of the <see cref="ProbePage"/> class. /// </summary> /// <param name="probe">Probe to display.</param> public ProbePage(Probe probe) { Title = "Probe"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; string type = ""; if (probe is ListeningProbe) { type = "Listening"; } else if (probe is PollingProbe) { type = "Polling"; } contentLayout.Children.Add(new ContentView { Content = new Label { Text = probe.DisplayName + (type == "" ? "" : " (" + type + ")"), FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }, Padding = new Thickness(0, 10, 0, 10) }); foreach (StackLayout stack in UiProperty.GetPropertyStacks(probe)) { contentLayout.Children.Add(stack); } #region script probes if (probe is ScriptProbe) { ScriptProbe scriptProbe = probe as ScriptProbe; Button editScriptsButton = new Button { Text = "Edit Scripts", FontSize = 20 }; contentLayout.Children.Add(editScriptsButton); editScriptsButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ScriptRunnersPage(scriptProbe)); }; Button shareScriptButton = new Button { Text = "Share Definition", FontSize = 20 }; contentLayout.Children.Add(shareScriptButton); shareScriptButton.Clicked += (o, e) => { string sharePath = SensusServiceHelper.Get().GetSharePath(".json"); using (StreamWriter shareFile = new StreamWriter(sharePath)) { shareFile.WriteLine(JsonConvert.SerializeObject(probe, SensusServiceHelper.JSON_SERIALIZER_SETTINGS)); } SensusServiceHelper.Get().ShareFileAsync(sharePath, "Probe Definition", "application/json"); }; } #endregion #region proximity probe if (probe is IPointsOfInterestProximityProbe) { Button editTriggersButton = new Button { Text = "Edit Triggers", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(editTriggersButton); editTriggersButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProximityTriggersPage(probe as IPointsOfInterestProximityProbe)); }; } #endregion #region anonymization List <PropertyInfo> anonymizableProperties = probe.DatumType.GetProperties().Where(property => property.GetCustomAttribute <Anonymizable>() != null).ToList(); if (anonymizableProperties.Count > 0) { contentLayout.Children.Add(new Label { Text = "Anonymization", FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }); List <StackLayout> anonymizablePropertyStacks = new List <StackLayout>(); foreach (PropertyInfo anonymizableProperty in anonymizableProperties) { Anonymizable anonymizableAttribute = anonymizableProperty.GetCustomAttribute <Anonymizable>(true); Label propertyLabel = new Label { Text = anonymizableAttribute.PropertyDisplayName ?? anonymizableProperty.Name + ":", FontSize = 20, HorizontalOptions = LayoutOptions.Start }; // populate a picker of anonymizers for the current property Picker anonymizerPicker = new Picker { Title = "Select Anonymizer", HorizontalOptions = LayoutOptions.FillAndExpand }; anonymizerPicker.Items.Add("Do Not Anonymize"); foreach (Anonymizer anonymizer in anonymizableAttribute.AvailableAnonymizers) { anonymizerPicker.Items.Add(anonymizer.DisplayText); } anonymizerPicker.SelectedIndexChanged += (o, e) => { Anonymizer selectedAnonymizer = null; if (anonymizerPicker.SelectedIndex > 0) { selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1]; // subtract one from the selected index since the JsonAnonymizer's collection of anonymizers start after the "None" option within the picker. } probe.Protocol.JsonAnonymizer.SetAnonymizer(anonymizableProperty, selectedAnonymizer); }; // set the picker's index to the current anonymizer (or "Do Not Anonymize" if there is no current) Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty); int currentIndex = 0; if (currentAnonymizer != null) { currentIndex = anonymizableAttribute.AvailableAnonymizers.IndexOf(currentAnonymizer) + 1; } anonymizerPicker.SelectedIndex = currentIndex; StackLayout anonymizablePropertyStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { propertyLabel, anonymizerPicker } }; anonymizablePropertyStacks.Add(anonymizablePropertyStack); } foreach (StackLayout anonymizablePropertyStack in anonymizablePropertyStacks.OrderBy(s => (s.Children[0] as Label).Text)) { contentLayout.Children.Add(anonymizablePropertyStack); } } #endregion Content = new ScrollView { Content = contentLayout }; }
public AddScriptProbeTriggerPage(ScriptProbe scriptProbe) { _scriptProbe = scriptProbe; Title = "Add Trigger"; List <Probe> enabledProbes = scriptProbe.Protocol.Probes.Where(p => p != _scriptProbe && p.Enabled).ToList(); if (enabledProbes.Count == 0) { Content = new Label { Text = "No enabled probes. Please enable them before creating triggers.", FontSize = 20 }; return; } StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; Label probeLabel = new Label { Text = "Probe:", FontSize = 20 }; Picker probePicker = new Picker { Title = "Select Probe", HorizontalOptions = LayoutOptions.FillAndExpand }; foreach (Probe enabledProbe in enabledProbes) { probePicker.Items.Add(enabledProbe.DisplayName); } contentLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { probeLabel, probePicker } }); StackLayout triggerDefinitionLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(triggerDefinitionLayout); Switch changeSwitch = new Switch(); Switch regexSwitch = new Switch(); Switch fireRepeatedlySwitch = new Switch(); Switch ignoreFirstDatumSwitch = new Switch(); TimePicker startTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand }; TimePicker endTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand }; probePicker.SelectedIndexChanged += (o, e) => { if (probePicker.SelectedIndex < 0) { return; } _selectedProbe = enabledProbes[probePicker.SelectedIndex]; triggerDefinitionLayout.Children.Clear(); #region datum property picker Label datumPropertyLabel = new Label { Text = "Property:", FontSize = 20 }; Picker datumPropertyPicker = new Picker { Title = "Select Datum Property", HorizontalOptions = LayoutOptions.FillAndExpand }; PropertyInfo[] datumProperties = _selectedProbe.DatumType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttributes <ProbeTriggerProperty>().Count() > 0).ToArray(); foreach (PropertyInfo triggerProperty in datumProperties) { datumPropertyPicker.Items.Add(triggerProperty.Name); } triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { datumPropertyLabel, datumPropertyPicker } }); #endregion #region condition picker (same for all datum types) Label conditionLabel = new Label { Text = "Condition:", FontSize = 20 }; Picker conditionPicker = new Picker { Title = "Select Condition", HorizontalOptions = LayoutOptions.FillAndExpand }; TriggerValueCondition[] conditions = Enum.GetValues(typeof(TriggerValueCondition)) as TriggerValueCondition[]; foreach (TriggerValueCondition condition in conditions) { conditionPicker.Items.Add(condition.ToString()); } conditionPicker.SelectedIndexChanged += (oo, ee) => { if (conditionPicker.SelectedIndex < 0) { return; } _selectedCondition = conditions[conditionPicker.SelectedIndex]; }; triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { conditionLabel, conditionPicker } }); #endregion #region condition value for comparison, based on selected datum property -- includes change calculation (for double datum) and regex (for string datum) StackLayout conditionValueStack = new StackLayout { Orientation = StackOrientation.Vertical, HorizontalOptions = LayoutOptions.FillAndExpand }; triggerDefinitionLayout.Children.Add(conditionValueStack); datumPropertyPicker.SelectedIndexChanged += (oo, ee) => { if (datumPropertyPicker.SelectedIndex < 0) { return; } _selectedDatumProperty = datumProperties[datumPropertyPicker.SelectedIndex]; conditionValueStack.Children.Clear(); ProbeTriggerProperty datumTriggerAttribute = _selectedDatumProperty.GetCustomAttribute <ProbeTriggerProperty>(); View conditionValueStackView = null; bool allowChangeCalculation = false; bool allowRegularExpression = false; if (datumTriggerAttribute is ListProbeTriggerProperty) { Picker conditionValuePicker = new Picker { Title = "Select Condition Value", HorizontalOptions = LayoutOptions.FillAndExpand }; object[] items = (datumTriggerAttribute as ListProbeTriggerProperty).Items; foreach (object item in items) { conditionValuePicker.Items.Add(item.ToString()); } conditionValuePicker.SelectedIndexChanged += (ooo, eee) => { if (conditionValuePicker.SelectedIndex < 0) { return; } _conditionValue = items[conditionValuePicker.SelectedIndex]; }; conditionValueStackView = conditionValuePicker; } else if (datumTriggerAttribute is NumberProbeTriggerProperty) { Entry entry = new Entry { Keyboard = Keyboard.Numeric, HorizontalOptions = LayoutOptions.FillAndExpand }; entry.TextChanged += (ooo, eee) => { double value; if (double.TryParse(eee.NewTextValue, out value)) { _conditionValue = value; } }; conditionValueStackView = entry; allowChangeCalculation = true; } else if (datumTriggerAttribute is TextProbeTriggerProperty) { Entry entry = new Entry { Keyboard = Keyboard.Default, HorizontalOptions = LayoutOptions.FillAndExpand }; entry.TextChanged += (ooo, eee) => _conditionValue = eee.NewTextValue; conditionValueStackView = entry; allowRegularExpression = true; } else if (datumTriggerAttribute is BooleanProbeTriggerProperty) { Switch booleanSwitch = new Switch(); booleanSwitch.Toggled += (ooo, eee) => _conditionValue = eee.Value; conditionValueStackView = booleanSwitch; } Label conditionValueStackLabel = new Label { Text = "Value:", FontSize = 20 }; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { conditionValueStackLabel, conditionValueStackView } }); #region change calculation if (allowChangeCalculation) { Label changeLabel = new Label { Text = "Change:", FontSize = 20 }; changeSwitch.IsToggled = false; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { changeLabel, changeSwitch } }); } #endregion #region regular expression if (allowRegularExpression) { Label regexLabel = new Label { Text = "Regular Expression:", FontSize = 20 }; regexSwitch.IsToggled = false; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { regexLabel, regexSwitch } }); } #endregion #region fire repeatedly Label fireRepeatedlyLabel = new Label { Text = "Fire Repeatedly:", FontSize = 20 }; fireRepeatedlySwitch.IsToggled = false; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { fireRepeatedlyLabel, fireRepeatedlySwitch } }); #endregion }; datumPropertyPicker.SelectedIndex = 0; #endregion #region ignore first datum Label ignoreFirstDatumLabel = new Label { Text = "Ignore First Datum:", FontSize = 20 }; ignoreFirstDatumSwitch.IsToggled = false; triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { ignoreFirstDatumLabel, ignoreFirstDatumSwitch } }); #endregion #region start/end times Label startTimeLabel = new Label { Text = "Start Time:", FontSize = 20 }; startTimePicker.Time = new TimeSpan(8, 0, 0); triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { startTimeLabel, startTimePicker } }); Label endTimeLabel = new Label { Text = "End Time:", FontSize = 20 }; endTimePicker.Time = new TimeSpan(21, 0, 0); triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { endTimeLabel, endTimePicker } }); #endregion }; probePicker.SelectedIndex = 0; Button okButton = new Button { Text = "OK", FontSize = 20 }; okButton.Clicked += async(o, e) => { try { _scriptProbe.Triggers.Add(new SensusService.Probes.User.Trigger(_selectedProbe, _selectedDatumProperty.Name, _selectedCondition, _conditionValue, changeSwitch.IsToggled, fireRepeatedlySwitch.IsToggled, regexSwitch.IsToggled, ignoreFirstDatumSwitch.IsToggled, startTimePicker.Time, endTimePicker.Time)); await Navigation.PopAsync(); } catch (Exception ex) { string message = "Failed to add trigger: " + ex.Message; UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync(message); UiBoundSensusServiceHelper.Get(true).Logger.Log(message, LoggingLevel.Normal, GetType()); } }; contentLayout.Children.Add(okButton); Content = new ScrollView { Content = contentLayout }; }
/// <summary> /// Initializes a new instance of the <see cref="ProbePage"/> class. /// </summary> /// <param name="probe">Probe to display.</param> public ProbePage(Probe probe) { Title = "Probe"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; string type = ""; if (probe is ListeningProbe) { type = "Listening"; } else if (probe is PollingProbe) { type = "Polling"; } contentLayout.Children.Add(new ContentView { Content = new Label { Text = probe.DisplayName + (type == "" ? "" : " (" + type + ")"), FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }, Padding = new Thickness(0, 10, 0, 10) }); foreach (StackLayout stack in UiProperty.GetPropertyStacks(probe)) { contentLayout.Children.Add(stack); } #region script probes if (probe is ScriptProbe) { ScriptProbe scriptProbe = probe as ScriptProbe; Button editScriptsButton = new Button { Text = "Edit Scripts", FontSize = 20 }; contentLayout.Children.Add(editScriptsButton); editScriptsButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ScriptRunnersPage(scriptProbe)); }; Button shareScriptButton = new Button { Text = "Share Definition", FontSize = 20 }; contentLayout.Children.Add(shareScriptButton); shareScriptButton.Clicked += async(o, e) => { string sharePath = SensusServiceHelper.Get().GetSharePath(".json"); using (StreamWriter shareFile = new StreamWriter(sharePath)) { shareFile.WriteLine(JsonConvert.SerializeObject(probe, SensusServiceHelper.JSON_SERIALIZER_SETTINGS)); } await SensusServiceHelper.Get().ShareFileAsync(sharePath, "Probe Definition", "application/json"); }; Button setAgentButton = new Button { Text = "Set Agent" + (scriptProbe.Agent == null ? "" : ": " + scriptProbe.Agent.Id), FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; setAgentButton.Clicked += async(sender, e) => { await SetAgentButton_Clicked(setAgentButton, scriptProbe); }; contentLayout.Children.Add(setAgentButton); Button clearAgentButton = new Button { Text = "Clear Agent", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; clearAgentButton.Clicked += async(sender, e) => { if (scriptProbe.Agent != null) { if (await DisplayAlert("Confirm", "Are you sure you wish to clear the survey agent?", "Yes", "No")) { scriptProbe.Agent = null; setAgentButton.Text = "Set Agent"; } } await SensusServiceHelper.Get().FlashNotificationAsync("Survey agent cleared."); }; contentLayout.Children.Add(clearAgentButton); } #endregion #region proximity probe if (probe is IPointsOfInterestProximityProbe) { Button editTriggersButton = new Button { Text = "Edit Triggers", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(editTriggersButton); editTriggersButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProximityTriggersPage(probe as IPointsOfInterestProximityProbe)); }; } #endregion #region estimote probe if (probe is EstimoteBeaconProbe) { EstimoteBeaconProbe estimoteBeaconProbe = probe as EstimoteBeaconProbe; Button editBeaconsButton = new Button { Text = "Edit Beacons", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(editBeaconsButton); editBeaconsButton.Clicked += async(sender, e) => { await Navigation.PushAsync(new EstimoteBeaconProbeBeaconsPage(estimoteBeaconProbe)); }; Button setLocationButton = new Button { Text = estimoteBeaconProbe.Location == null ? "Set Location" : "Location: " + estimoteBeaconProbe.Location, FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(setLocationButton); setLocationButton.Clicked += async(sender, e) => { List <EstimoteLocation> locations; try { locations = await estimoteBeaconProbe.GetLocationsFromCloudAsync(TimeSpan.FromSeconds(10)); if (locations.Count == 0) { throw new Exception("No locations present within Estimote Cloud."); } } catch (Exception ex) { await SensusServiceHelper.Get().FlashNotificationAsync("Failed to set location: " + ex.Message); return; } List <Input> inputs = await SensusServiceHelper.Get().PromptForInputsAsync("Set Location", new Input[] { new ItemPickerDialogInput("Location:", null, locations.Select(location => location.Name + " (" + location.Identifier + ")").ToList()) { AllowClearSelection = true } }, null, true, null, null, null, null, false); if (inputs == null || inputs[0]?.Value == null) { estimoteBeaconProbe.Location = null; await SensusServiceHelper.Get().FlashNotificationAsync("Location cleared."); setLocationButton.Text = "Set Location"; } else { try { string locationIdentifier = inputs[0].Value.ToString().Split(new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries)[1]; estimoteBeaconProbe.Location = locations.Single(location => location.Identifier == locationIdentifier); setLocationButton.Text = "Location: " + estimoteBeaconProbe.Location; } catch (Exception) { await SensusServiceHelper.Get().FlashNotificationAsync("Failed to set location."); } } }; } #endregion #region anonymization List <PropertyInfo> anonymizableProperties = probe.DatumType.GetProperties().Where(property => property.GetCustomAttribute <Anonymizable>() != null).ToList(); if (anonymizableProperties.Count > 0) { contentLayout.Children.Add(new Label { Text = "Anonymization", FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }); List <StackLayout> anonymizablePropertyStacks = new List <StackLayout>(); foreach (PropertyInfo anonymizableProperty in anonymizableProperties) { Anonymizable anonymizableAttribute = anonymizableProperty.GetCustomAttribute <Anonymizable>(true); Label propertyLabel = new Label { Text = anonymizableAttribute.PropertyDisplayName ?? anonymizableProperty.Name + ":", FontSize = 20, HorizontalOptions = LayoutOptions.Start, VerticalTextAlignment = TextAlignment.Center }; // populate a picker of anonymizers for the current property Picker anonymizerPicker = new Picker { Title = "Select Anonymizer", HorizontalOptions = LayoutOptions.FillAndExpand }; Button configureButton = new Button { Text = "Settings", FontSize = 20 }; // get the current anonymizer for this property Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty); for (int index = 0; index < anonymizableAttribute.AvailableAnonymizers.Count; index++) { if (currentAnonymizer != null && anonymizableAttribute.AvailableAnonymizers[index].GetType() == currentAnonymizer.GetType()) { anonymizableAttribute.AvailableAnonymizers[index] = currentAnonymizer; } } anonymizerPicker.Items.Add("Do Not Anonymize"); foreach (Anonymizer anonymizer in anonymizableAttribute.AvailableAnonymizers) { anonymizerPicker.Items.Add(anonymizer.DisplayText); } anonymizerPicker.SelectedIndexChanged += (o, e) => { Anonymizer selectedAnonymizer = null; if (anonymizerPicker.SelectedIndex > 0) { selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1]; // subtract one from the selected index since the JsonAnonymizer's collection of anonymizers start after the "None" option within the picker. configureButton.IsVisible = UiProperty.HasUiProperties(selectedAnonymizer); } else { configureButton.IsVisible = false; } probe.Protocol.JsonAnonymizer.SetAnonymizer(anonymizableProperty, selectedAnonymizer); }; configureButton.Clicked += async(o, e) => { Anonymizer selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1]; await ShowAnonymizerSettings(selectedAnonymizer, anonymizerPicker); }; // set the picker's index to the current anonymizer (or "Do Not Anonymize" if there is no current) //Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty); int currentIndex = 0; if (currentAnonymizer != null) { currentIndex = anonymizableAttribute.AvailableAnonymizers.IndexOf(currentAnonymizer) + 1; } anonymizerPicker.SelectedIndex = currentIndex; StackLayout anonymizablePropertyStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { propertyLabel, anonymizerPicker, configureButton } }; anonymizablePropertyStacks.Add(anonymizablePropertyStack); } foreach (StackLayout anonymizablePropertyStack in anonymizablePropertyStacks.OrderBy(s => (s.Children[0] as Label).Text)) { contentLayout.Children.Add(anonymizablePropertyStack); } } #endregion Content = new ScrollView { Content = contentLayout }; }
private async Task SetAgentButton_Clicked(Button setAgentButton, ScriptProbe scriptProbe) { List <Input> agentSelectionInputs = new List <Input>(); // show any existing agents List <IScriptProbeAgent> currentAgents = null; // android allows us to dynamically load code assemblies, but iOS does not. so, the current approach // is to only support dynamic loading on android and force compile-time assembly inclusion on ios. #if __ANDROID__ // try to extract agents from a previously loaded assembly try { currentAgents = ScriptProbe.GetAgents(scriptProbe.AgentAssemblyBytes); } catch (Exception) { } #elif __IOS__ currentAgents = ScriptProbe.GetAgents(); // display warning message, as there is no other option to load agents. if (currentAgents.Count == 0) { await SensusServiceHelper.Get().FlashNotificationAsync("No agents available."); return; } #endif // let the user pick from currently available agents ItemPickerPageInput currentAgentsPicker = null; if (currentAgents != null && currentAgents.Count > 0) { currentAgentsPicker = new ItemPickerPageInput("Available agent" + (currentAgents.Count > 1 ? "s" : "") + ":", currentAgents.Cast <object>().ToList()) { Required = false }; agentSelectionInputs.Add(currentAgentsPicker); } #if __ANDROID__ // add option to scan qr code to import a new assembly QrCodeInput agentAssemblyUrlQrCodeInput = new QrCodeInput(QrCodePrefix.SURVEY_AGENT, "URL:", false, "Agent URL:") { Required = false }; agentSelectionInputs.Add(agentAssemblyUrlQrCodeInput); #endif List <Input> completedInputs = await SensusServiceHelper.Get().PromptForInputsAsync("Survey Agent", agentSelectionInputs, null, true, "Set", null, null, null, false); if (completedInputs == null) { return; } // check for QR code on android. this doesn't exist on ios. string agentURL = null; #if __ANDROID__ agentURL = agentAssemblyUrlQrCodeInput.Value?.ToString(); #endif // if there is no URL, check if the user has selected an agent. if (string.IsNullOrWhiteSpace(agentURL)) { if (currentAgentsPicker != null) { IScriptProbeAgent selectedAgent = (currentAgentsPicker.Value as List <object>).FirstOrDefault() as IScriptProbeAgent; // set the selected agent, watching out for a null (clearing) selection that needs to be confirmed if (selectedAgent != null || await DisplayAlert("Confirm", "Are you sure you wish to clear the survey agent?", "Yes", "No")) { scriptProbe.Agent = selectedAgent; setAgentButton.Text = "Set Agent" + (scriptProbe.Agent == null ? "" : ": " + scriptProbe.Agent.Id); if (scriptProbe.Agent == null) { await SensusServiceHelper.Get().FlashNotificationAsync("Survey agent cleared."); } } } } #if __ANDROID__ else { // download agent assembly from scanned QR code byte[] downloadedBytes = null; string downloadErrorMessage = null; try { // download the assembly and extract agents downloadedBytes = scriptProbe.AgentAssemblyBytes = await new WebClient().DownloadDataTaskAsync(new Uri(agentURL)); List <IScriptProbeAgent> qrCodeAgents = ScriptProbe.GetAgents(downloadedBytes); if (qrCodeAgents.Count == 0) { throw new Exception("No agents were present in the specified file."); } } catch (Exception ex) { downloadErrorMessage = ex.Message; } // if error message is null, then we have 1 or more agents in the downloaded assembly. if (downloadErrorMessage == null) { // redisplay the current input prompt including the agents we just downloaded scriptProbe.AgentAssemblyBytes = downloadedBytes; await SetAgentButton_Clicked(setAgentButton, scriptProbe); } else { SensusServiceHelper.Get().Logger.Log(downloadErrorMessage, LoggingLevel.Normal, typeof(Protocol)); await SensusServiceHelper.Get().FlashNotificationAsync(downloadErrorMessage); } } #endif }