wrapping up

This commit is contained in:
Trevi Awater
2022-03-15 00:48:10 +01:00
parent 2a0f83a397
commit 9e6b1136ea
23 changed files with 360 additions and 70 deletions

View File

@@ -27,7 +27,7 @@
<key>CFBundleIdentifier</key>
<string>nl.resoftware.pocketgraph</string>
<key>CFBundleVersion</key>
<string>1.0.1</string>
<string>1.0.2</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleName</key>

View File

@@ -0,0 +1,34 @@
//
// NativeTrustManager.cs
//
// Trevi Awater
// 14-03-2022
//
// © Xamarin.Neo4j.iOS
//
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Neo4j.Driver;
using Security;
namespace Xamarin.Neo4j
{
public class NativeTrustManager : TrustManager
{
public override bool ValidateServerCertificate(Uri uri, X509Certificate2 certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
using (var cert = new SecCertificate(certificate))
using (var policy = SecPolicy.CreateSslPolicy(true, uri.Host))
using (var trustHandler = new SecTrust(cert, policy))
{
if (!trustHandler.Evaluate(out var error))
throw new Exception(error.ToString());
return true;
}
}
}
}

View File

@@ -0,0 +1,25 @@
//
// TrustManagerService.cs
//
// Trevi Awater
// 14-03-2022
//
// © Xamarin.Neo4j.iOS
//
using Neo4j.Driver;
using Xamarin.Forms;
using Xamarin.Neo4j.iOS.Services;
using Xamarin.Neo4j.Services;
[assembly: Dependency(typeof(TrustManagerService))]
namespace Xamarin.Neo4j.iOS.Services
{
public class TrustManagerService : ITrustManagerService
{
public TrustManager GetNativeTrustManager()
{
return new NativeTrustManager();
}
}
}

View File

@@ -97,7 +97,9 @@
<Compile Include="CustomRenderers\QueryEditorRenderer.cs" />
<Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" />
<Compile Include="Security\NativeTrustManager.cs" />
<Compile Include="Services\ScreenSizeService.cs" />
<Compile Include="Services\TrustManagerService.cs" />
<None Include="Entitlements.plist" />
<None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Xamarin.Neo4j.Controls.LicenseCell">
<StackLayout Padding="15, 15, 15, 0">
<Label FontAttributes="Bold" TextColor="#4169FF" Text="{Binding Name}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OpenRepo}" />
</Label.GestureRecognizers>
</Label>
<ContentView Padding="10" BackgroundColor="#D8D8D8">
<Label Text="{Binding LicenseText}" />
</ContentView>
</StackLayout>
</ViewCell>

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Xamarin.Neo4j.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LicenseCell : ViewCell
{
public LicenseCell()
{
InitializeComponent();
}
}
}

View File

@@ -52,14 +52,16 @@ namespace Xamarin.Neo4j.Controls
using (var stream = assembly.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream))
{
var (url, isEncrypted, ignoreTrust) = QueryResult.ConnectionString.ParseHost();
var result = reader.ReadToEnd();
result = result.Replace("{{host}}", QueryResult.ConnectionString.Host);
result = result.Replace("{{port}}", QueryResult.ConnectionString.Port.ToString());
result = result.Replace("{{host}}", url);
result = result.Replace("{{database}}", QueryResult.ConnectionString.Database);
result = result.Replace("{{username}}", QueryResult.ConnectionString.Username);
result = result.Replace("{{password}}", QueryResult.ConnectionString.Password);
result = result.Replace("{{encryption}}", QueryResult.ConnectionString.Encrypted ? "ENCRYPTION_ON" : "ENCRYPTION_OFF");
result = result.Replace("{{encryption}}", isEncrypted ? "ENCRYPTION_ON" : "ENCRYPTION_OFF");
result = result.Replace("{{trust}}", ignoreTrust ? "TRUST_ALL_CERTIFICATES" : "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES");
result = result.Replace("{{query}}", QueryResult.Query);
result = result.Replace("{{backgroundColor}}", App.Current.RequestedTheme == OSAppTheme.Dark ? "#292C31" : "#FFFFFF");

View File

@@ -0,0 +1,30 @@
//
// License.cs
//
// Trevi Awater
// 15-03-2022
//
// © Xamarin.Neo4j
//
using System;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Xamarin.Neo4j.Models
{
public class License
{
public string Name { get; set; }
public string Repo { get; set; }
public string LicenseText { get; set; }
/// <summary>
/// Opens the repository in the browser.
/// </summary>
public ICommand OpenRepo => new Command(() => Launcher.OpenAsync(new Uri(Repo)));
}
}

View File

@@ -21,12 +21,25 @@ namespace Xamarin.Neo4j.Models
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool Encrypted { get; set; }
public Tuple<string, bool, bool> ParseHost()
{
if (Host.StartsWith("neo4j://"))
return new Tuple<string, bool, bool>(Host, false, false);
if (Host.StartsWith("bolt://"))
return new Tuple<string, bool, bool>(Host.Replace("bolt://", "neo4j://"), false, false);
if (Host.StartsWith("neo4j+s://"))
return new Tuple<string, bool, bool>(Host.Replace("neo4j+s://", "neo4j://"), true, false);
if (Host.StartsWith("neo4j+ssc://"))
return new Tuple<string, bool, bool>(Host.Replace("neo4j+ssc://", "neo4j://"), true, true);
throw new NotSupportedException("Unknown protocol.");
}
}
}

View File

@@ -12,16 +12,6 @@
<Entry Text="{Binding Host}" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Port:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Port}" Keyboard="Numeric" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Encryption:" FontAttributes="Bold" FontSize="Small" />
<Switch IsToggled="{Binding Encryption}" />
</StackLayout>
<StackLayout Spacing="4">
<Label Text="Username:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Username}" Keyboard="Numeric" />

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xamarin.Neo4j.Controls"
Title="Software Licenses"
x:Class="Xamarin.Neo4j.Pages.LicensesPage">
<ContentPage.Content>
<ListView SeparatorVisibility="None" SelectionMode="None" HasUnevenRows="True" ItemsSource="{Binding Licenses}">
<ListView.ItemTemplate>
<DataTemplate>
<controls:LicenseCell />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Footer>
<ContentView Padding="0, 15, 0, 0" />
</ListView.Footer>
</ListView>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Neo4j.ViewModels;
namespace Xamarin.Neo4j.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LicensesPage : ContentPage
{
public LicensesPage()
{
InitializeComponent();
BindingContext = new LicensesViewModel(Navigation);
}
}
}

View File

@@ -26,7 +26,7 @@
</NavigationPage.TitleView>
<ContentPage.ToolbarItems>
<ToolbarItem Command="{Binding Commands[ExecuteQuery]}">
<ToolbarItem Command="{Binding Commands[ExecuteQuery]}" IsEnabled="{Binding CanExecuteQuery}">
<ToolbarItem.IconImageSource>
<FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}"

View File

@@ -13,9 +13,16 @@
</ContentPage.IconImageSource>
<ContentPage.Content>
<ScrollView>
<StackLayout Spacing="15" Padding="25">
<StackLayout Spacing="15" VerticalOptions="EndAndExpand">
<StackLayout VerticalOptions="FillAndExpand">
<TableView VerticalOptions="StartAndExpand" Intent="Settings" Background="Transparent">
<TableRoot>
<TableSection>
<TextCell Text="Software Licenses" Command="{Binding Commands[OpenLicensesPage]}" />
</TableSection>
</TableRoot>
</TableView>
<StackLayout Spacing="15" Margin="25" VerticalOptions="EndAndExpand">
<Label Text="A product of" HorizontalOptions="Center" FontAttributes="Italic" FontSize="12" TextColor="{StaticResource SecondaryTextColor}" />
<Image Source="resoftware.png" HorizontalOptions="Center" WidthRequest="100">
<Image.GestureRecognizers>
@@ -24,6 +31,5 @@
</Image>
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
using Neo4j.Driver;
namespace Xamarin.Neo4j.Services
{
public interface ITrustManagerService
{
TrustManager GetNativeTrustManager();
}
}

View File

@@ -16,26 +16,37 @@ using Neo4jClient.Cypher;
using Xamarin.Forms;
using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Services;
[assembly: Dependency(typeof(Neo4jService))]
namespace Xamarin.Neo4j.Services
{
public class Neo4jService
{
private readonly TrustManager _nativeTrustManager;
public Neo4jService()
{
var trustManagerService = DependencyService.Get<ITrustManagerService>();
_nativeTrustManager = trustManagerService.GetNativeTrustManager();
}
private BoltGraphClient GraphClient { get; set; }
public async Task<bool> EstablishConnection(Neo4jConnectionString connectionString)
{
try
{
var boltUri = $"bolt://{connectionString.Host}:{connectionString.Port}";
var (url, isEncrypted, ignoreTrust) = connectionString.ParseHost();
var driver = GraphDatabase.Driver(boltUri,
AuthTokens.Basic(connectionString.Username, connectionString.Password),
builder =>
var driver = GraphDatabase.Driver(url,
AuthTokens.Basic(connectionString.Username, connectionString.Password), (config) =>
{
builder.WithEncryptionLevel(connectionString.Encrypted
? EncryptionLevel.Encrypted
: EncryptionLevel.None);
config.WithEncryptionLevel(isEncrypted ? EncryptionLevel.Encrypted : EncryptionLevel.None);
if (!ignoreTrust && Device.RuntimePlatform == Device.iOS)
config.WithTrustManager(_nativeTrustManager);
});
GraphClient = new BoltGraphClient(driver);

View File

@@ -25,12 +25,8 @@ namespace Xamarin.Neo4j.ViewModels
{
public class AddConnectionViewModel : ViewModelBase, INotifyPropertyChanged
{
private bool _encrypted;
private string _host, _username, _password;
private int _port;
private readonly Neo4jService _neo4jService;
public event PropertyChangedEventHandler PropertyChanged;
@@ -83,7 +79,6 @@ namespace Xamarin.Neo4j.ViewModels
private void InitializeDefaultValues()
{
Username = "neo4j";
Port = 7687;
}
[NotifyPropertyChangedInvocator]
@@ -98,27 +93,13 @@ namespace Xamarin.Neo4j.ViewModels
{
Id = Guid.NewGuid(),
Host = Host,
Port = Port,
Username = Username,
Password = Password,
Encrypted = Encrypted
Password = Password
};
}
#region Bindable Properties
public bool Encrypted
{
get => _encrypted;
set
{
_encrypted = value;
OnPropertyChanged(nameof(Encrypted));
}
}
public string Host
{
get => _host;
@@ -131,18 +112,6 @@ namespace Xamarin.Neo4j.ViewModels
}
}
public int Port
{
get => _port;
set
{
_port = value;
OnPropertyChanged(nameof(Port));
}
}
public string Username
{
get => _username;

View File

@@ -0,0 +1,66 @@
//
// LicensesViewModel.cs
//
// Trevi Awater
// 15-03-2022
//
// © Xamarin.Neo4j
//
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using License = Xamarin.Neo4j.Models.License;
namespace Xamarin.Neo4j.ViewModels
{
public class LicensesViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<License> _licenses;
public LicensesViewModel(INavigation navigation) : base(navigation)
{
LoadLicenses();
}
private void LoadLicenses()
{
var assembly = GetType().GetTypeInfo().Assembly;
string licenseFile = $"Xamarin.Neo4j.Resources.licenses.json";
using (var stream = assembly.GetManifestResourceStream(licenseFile))
using (var reader = new StreamReader(stream))
Licenses = JsonConvert.DeserializeObject<List<License>>(reader.ReadToEnd());
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region Bindable Properties
public List<License> Licenses
{
get => _licenses;
set
{
_licenses = value;
OnPropertyChanged();
}
}
#endregion
}
}

View File

@@ -55,6 +55,9 @@ namespace Xamarin.Neo4j.ViewModels
Commands.Add("ExecuteQuery", new Command(async () =>
{
if (CanExecuteQuery == false)
return;
_connectionString.Database = CurrentDatabase.Name;
var result = await _neo4jService.ExecuteQuery(Query, _connectionString);
@@ -149,6 +152,8 @@ namespace Xamarin.Neo4j.ViewModels
}
}
public bool CanExecuteQuery => !string.IsNullOrWhiteSpace(Query) && CurrentDatabase != null;
#endregion
}
}

View File

@@ -13,6 +13,7 @@ using System.Runtime.CompilerServices;
using Xamarin.Essentials;
using Xamarin.Forms;
using Xamarin.Neo4j.Annotations;
using Xamarin.Neo4j.Pages;
namespace Xamarin.Neo4j.ViewModels
{
@@ -26,6 +27,11 @@ namespace Xamarin.Neo4j.ViewModels
{
await Launcher.OpenAsync(new Uri("https://resoftware.nl/"));
}));
Commands.Add("OpenLicensesPage", new Command(async () =>
{
await Navigation.PushAsync(new LicensesPage());
}));
}
[NotifyPropertyChangedInvocator]

View File

@@ -30,11 +30,12 @@
function draw() {
const config = {
container_id: "viz",
server_url: `bolt://{{host}}:{{port}}`,
server_url: `{{host}}`,
server_user: `{{username}}`,
server_database: `{{database}}`,
server_password: `{{password}}`,
encrypted: `{{encryption}}`,
trust: `{{trust}}`,
initial_cypher: `{{query}}`
};

View File

@@ -21,5 +21,7 @@
<ItemGroup>
<None Remove="Visualization\neovis.html" />
<EmbeddedResource Include="Visualization\neovis.html" />
<None Remove="Resources\licenses.json" />
<EmbeddedResource Include="Resources\licenses.json" />
</ItemGroup>
</Project>