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> <key>CFBundleIdentifier</key>
<string>nl.resoftware.pocketgraph</string> <string>nl.resoftware.pocketgraph</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0.1</string> <string>1.0.2</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>CFBundleName</key> <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="CustomRenderers\QueryEditorRenderer.cs" />
<Compile Include="Main.cs" /> <Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" /> <Compile Include="AppDelegate.cs" />
<Compile Include="Security\NativeTrustManager.cs" />
<Compile Include="Services\ScreenSizeService.cs" /> <Compile Include="Services\ScreenSizeService.cs" />
<Compile Include="Services\TrustManagerService.cs" />
<None Include="Entitlements.plist" /> <None Include="Entitlements.plist" />
<None Include="Info.plist" /> <None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" /> <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 stream = assembly.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
{ {
var (url, isEncrypted, ignoreTrust) = QueryResult.ConnectionString.ParseHost();
var result = reader.ReadToEnd(); var result = reader.ReadToEnd();
result = result.Replace("{{host}}", QueryResult.ConnectionString.Host); result = result.Replace("{{host}}", url);
result = result.Replace("{{port}}", QueryResult.ConnectionString.Port.ToString());
result = result.Replace("{{database}}", QueryResult.ConnectionString.Database); result = result.Replace("{{database}}", QueryResult.ConnectionString.Database);
result = result.Replace("{{username}}", QueryResult.ConnectionString.Username); result = result.Replace("{{username}}", QueryResult.ConnectionString.Username);
result = result.Replace("{{password}}", QueryResult.ConnectionString.Password); 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("{{query}}", QueryResult.Query);
result = result.Replace("{{backgroundColor}}", App.Current.RequestedTheme == OSAppTheme.Dark ? "#292C31" : "#FFFFFF"); 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 string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; } public string Username { get; set; }
public string Password { 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}" /> <Entry Text="{Binding Host}" />
</StackLayout> </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"> <StackLayout Spacing="4">
<Label Text="Username:" FontAttributes="Bold" FontSize="Small" /> <Label Text="Username:" FontAttributes="Bold" FontSize="Small" />
<Entry Text="{Binding Username}" Keyboard="Numeric" /> <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> </NavigationPage.TitleView>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Command="{Binding Commands[ExecuteQuery]}"> <ToolbarItem Command="{Binding Commands[ExecuteQuery]}" IsEnabled="{Binding CanExecuteQuery}">
<ToolbarItem.IconImageSource> <ToolbarItem.IconImageSource>
<FontImageSource <FontImageSource
FontFamily="{StaticResource FontAwesomeSolid}" FontFamily="{StaticResource FontAwesomeSolid}"

View File

@@ -13,9 +13,16 @@
</ContentPage.IconImageSource> </ContentPage.IconImageSource>
<ContentPage.Content> <ContentPage.Content>
<ScrollView> <StackLayout VerticalOptions="FillAndExpand">
<StackLayout Spacing="15" Padding="25"> <TableView VerticalOptions="StartAndExpand" Intent="Settings" Background="Transparent">
<StackLayout Spacing="15" VerticalOptions="EndAndExpand"> <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}" /> <Label Text="A product of" HorizontalOptions="Center" FontAttributes="Italic" FontSize="12" TextColor="{StaticResource SecondaryTextColor}" />
<Image Source="resoftware.png" HorizontalOptions="Center" WidthRequest="100"> <Image Source="resoftware.png" HorizontalOptions="Center" WidthRequest="100">
<Image.GestureRecognizers> <Image.GestureRecognizers>
@@ -24,6 +31,5 @@
</Image> </Image>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView>
</ContentPage.Content> </ContentPage.Content>
</ContentPage> </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.Forms;
using Xamarin.Neo4j.Models; using Xamarin.Neo4j.Models;
using Xamarin.Neo4j.Services; using Xamarin.Neo4j.Services;
[assembly: Dependency(typeof(Neo4jService))] [assembly: Dependency(typeof(Neo4jService))]
namespace Xamarin.Neo4j.Services namespace Xamarin.Neo4j.Services
{ {
public class Neo4jService public class Neo4jService
{ {
private readonly TrustManager _nativeTrustManager;
public Neo4jService()
{
var trustManagerService = DependencyService.Get<ITrustManagerService>();
_nativeTrustManager = trustManagerService.GetNativeTrustManager();
}
private BoltGraphClient GraphClient { get; set; } private BoltGraphClient GraphClient { get; set; }
public async Task<bool> EstablishConnection(Neo4jConnectionString connectionString) public async Task<bool> EstablishConnection(Neo4jConnectionString connectionString)
{ {
try try
{ {
var boltUri = $"bolt://{connectionString.Host}:{connectionString.Port}"; var (url, isEncrypted, ignoreTrust) = connectionString.ParseHost();
var driver = GraphDatabase.Driver(boltUri, var driver = GraphDatabase.Driver(url,
AuthTokens.Basic(connectionString.Username, connectionString.Password), AuthTokens.Basic(connectionString.Username, connectionString.Password), (config) =>
builder =>
{ {
builder.WithEncryptionLevel(connectionString.Encrypted config.WithEncryptionLevel(isEncrypted ? EncryptionLevel.Encrypted : EncryptionLevel.None);
? EncryptionLevel.Encrypted
: EncryptionLevel.None); if (!ignoreTrust && Device.RuntimePlatform == Device.iOS)
config.WithTrustManager(_nativeTrustManager);
}); });
GraphClient = new BoltGraphClient(driver); GraphClient = new BoltGraphClient(driver);

View File

@@ -25,12 +25,8 @@ namespace Xamarin.Neo4j.ViewModels
{ {
public class AddConnectionViewModel : ViewModelBase, INotifyPropertyChanged public class AddConnectionViewModel : ViewModelBase, INotifyPropertyChanged
{ {
private bool _encrypted;
private string _host, _username, _password; private string _host, _username, _password;
private int _port;
private readonly Neo4jService _neo4jService; private readonly Neo4jService _neo4jService;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
@@ -83,7 +79,6 @@ namespace Xamarin.Neo4j.ViewModels
private void InitializeDefaultValues() private void InitializeDefaultValues()
{ {
Username = "neo4j"; Username = "neo4j";
Port = 7687;
} }
[NotifyPropertyChangedInvocator] [NotifyPropertyChangedInvocator]
@@ -98,27 +93,13 @@ namespace Xamarin.Neo4j.ViewModels
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Host = Host, Host = Host,
Port = Port,
Username = Username, Username = Username,
Password = Password, Password = Password
Encrypted = Encrypted
}; };
} }
#region Bindable Properties #region Bindable Properties
public bool Encrypted
{
get => _encrypted;
set
{
_encrypted = value;
OnPropertyChanged(nameof(Encrypted));
}
}
public string Host public string Host
{ {
get => _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 public string Username
{ {
get => _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 () => Commands.Add("ExecuteQuery", new Command(async () =>
{ {
if (CanExecuteQuery == false)
return;
_connectionString.Database = CurrentDatabase.Name; _connectionString.Database = CurrentDatabase.Name;
var result = await _neo4jService.ExecuteQuery(Query, _connectionString); var result = await _neo4jService.ExecuteQuery(Query, _connectionString);
@@ -149,6 +152,8 @@ namespace Xamarin.Neo4j.ViewModels
} }
} }
public bool CanExecuteQuery => !string.IsNullOrWhiteSpace(Query) && CurrentDatabase != null;
#endregion #endregion
} }
} }

View File

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

View File

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

View File

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