Added simple GUI for ArmClient

This commit is contained in:
Xiaomin Wu 2015-02-03 11:58:37 -08:00
Родитель 7182c14beb
Коммит a8600cd2f8
23 изменённых файлов: 1462 добавлений и 64 удалений

Просмотреть файл

@ -1,7 +1,7 @@

namespace ARMClient.Authentication.Contracts
{
internal class TenantDetails
public class TenantDetails
{
public string objectId { get; set; }
public string displayName { get; set; }

Просмотреть файл

@ -1,7 +1,7 @@

namespace ARMClient.Authentication.Contracts
{
internal class VerifiedDomain
public class VerifiedDomain
{
public bool @default { get; set; }
public string name { get; set; }

Просмотреть файл

@ -1,5 +1,10 @@
using System;
using ARMClient.Authentication.AADAuthentication;
using ARMClient.Authentication.Contracts;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace ARMClient.Authentication.Utilities
{
@ -31,5 +36,96 @@ namespace ARMClient.Authentication.Utilities
System.Diagnostics.Trace.WriteLine(message);
}
}
public static async Task<int> HttpInvoke(Uri uri, TokenCacheInfo cacheInfo, string verb, DelegatingHandler handler, HttpContent content)
{
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add("Authorization", cacheInfo.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
client.DefaultRequestHeaders.Add("Accept", Constants.JsonContentType);
if (Utils.IsRdfe(uri))
{
client.DefaultRequestHeaders.Add("x-ms-version", "2013-10-01");
}
client.DefaultRequestHeaders.Add("x-ms-request-id", Guid.NewGuid().ToString());
HttpResponseMessage response = null;
if (String.Equals(verb, "get", StringComparison.OrdinalIgnoreCase))
{
response = await client.GetAsync(uri);
}
else if (String.Equals(verb, "delete", StringComparison.OrdinalIgnoreCase))
{
response = await client.DeleteAsync(uri);
}
else if (String.Equals(verb, "post", StringComparison.OrdinalIgnoreCase))
{
response = await client.PostAsync(uri, content);
}
else if (String.Equals(verb, "put", StringComparison.OrdinalIgnoreCase))
{
response = await client.PutAsync(uri, content);
}
else
{
throw new InvalidOperationException(String.Format("Invalid http verb {0}!", verb));
}
using (response)
{
if (response.IsSuccessStatusCode)
{
return 0;
}
return (-1) * (int)response.StatusCode;
}
}
}
public static Uri EnsureAbsoluteUri(string path, PersistentAuthHelper persistentAuthHelper)
{
Uri ret;
if (Uri.TryCreate(path, UriKind.Absolute, out ret))
{
return ret;
}
var env = persistentAuthHelper.IsCacheValid() ? persistentAuthHelper.AzureEnvironments : AzureEnvironments.Prod;
var parts = path.Split(new[] { '/', '?' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length <= 0
|| String.Equals(parts[0], "tenants", StringComparison.OrdinalIgnoreCase)
|| String.Equals(parts[0], "subscriptions", StringComparison.OrdinalIgnoreCase)
|| String.Equals(parts[0], "providers", StringComparison.OrdinalIgnoreCase))
{
return new Uri(new Uri(ARMClient.Authentication.Constants.CSMUrls[(int)env]), path);
}
Guid guid;
if (Guid.TryParse(parts[0], out guid))
{
if (path.Length > 1 && String.Equals(parts[1], "services", StringComparison.OrdinalIgnoreCase))
{
return new Uri(new Uri(ARMClient.Authentication.Constants.RdfeUrls[(int)env]), path);
}
}
return new Uri(new Uri(ARMClient.Authentication.Constants.AADGraphUrls[(int)env]), path);
}
public static bool IsRdfe(Uri uri)
{
var host = uri.Host;
return Constants.RdfeUrls.Any(url => url.IndexOf(host, StringComparison.OrdinalIgnoreCase) > 0);
}
public static bool IsGraphApi(Uri uri)
{
var host = uri.Host;
return Constants.AADGraphUrls.Any(url => url.IndexOf(host, StringComparison.OrdinalIgnoreCase) > 0);
}
}
}

Просмотреть файл

@ -41,7 +41,6 @@ namespace ARMClient
else if (String.Equals(verb, "listcache", StringComparison.OrdinalIgnoreCase))
{
_parameters.ThrowIfUnknown();
EnsureTokenCache(persistentAuthHelper);
foreach (var line in persistentAuthHelper.DumpTokenCache())
@ -53,7 +52,6 @@ namespace ARMClient
else if (String.Equals(verb, "clearcache", StringComparison.OrdinalIgnoreCase))
{
_parameters.ThrowIfUnknown();
persistentAuthHelper.ClearTokenCache();
return 0;
}
@ -87,7 +85,6 @@ namespace ARMClient
{
var tenantId = _parameters.Get(1, keyName: "tenant");
EnsureGuidFormat(tenantId);
var appId = _parameters.Get(2, keyName: "appId");
EnsureGuidFormat(appId);
@ -339,51 +336,8 @@ namespace ARMClient
static async Task<int> HttpInvoke(Uri uri, TokenCacheInfo cacheInfo, string verb, bool verbose, HttpContent content)
{
using (var client = new HttpClient(new HttpLoggingHandler(new HttpClientHandler(), verbose)))
{
client.DefaultRequestHeaders.Add("Authorization", cacheInfo.CreateAuthorizationHeader());
client.DefaultRequestHeaders.Add("User-Agent", Constants.UserAgent.Value);
client.DefaultRequestHeaders.Add("Accept", Constants.JsonContentType);
if (IsRdfe(uri))
{
client.DefaultRequestHeaders.Add("x-ms-version", "2013-10-01");
}
client.DefaultRequestHeaders.Add("x-ms-request-id", Guid.NewGuid().ToString());
HttpResponseMessage response = null;
if (String.Equals(verb, "get", StringComparison.OrdinalIgnoreCase))
{
response = await client.GetAsync(uri);
}
else if (String.Equals(verb, "delete", StringComparison.OrdinalIgnoreCase))
{
response = await client.DeleteAsync(uri);
}
else if (String.Equals(verb, "post", StringComparison.OrdinalIgnoreCase))
{
response = await client.PostAsync(uri, content);
}
else if (String.Equals(verb, "put", StringComparison.OrdinalIgnoreCase))
{
response = await client.PutAsync(uri, content);
}
else
{
throw new InvalidOperationException(String.Format("Invalid http verb {0}!", verb));
}
using (response)
{
if (response.IsSuccessStatusCode)
{
return 0;
}
return (-1) * (int)response.StatusCode;
}
}
var logginerHandler = new HttpLoggingHandler(new HttpClientHandler(), verbose);
return await Utils.HttpInvoke(uri, cacheInfo, verb, logginerHandler, content);
}
//http://stackoverflow.com/questions/4810841/how-can-i-pretty-print-json-using-javascript
@ -438,24 +392,12 @@ namespace ARMClient
}
}
static bool IsRdfe(Uri uri)
{
var host = uri.Host;
return Constants.RdfeUrls.Any(url => url.IndexOf(host, StringComparison.OrdinalIgnoreCase) > 0);
}
static bool IsGraphApi(Uri uri)
{
var host = uri.Host;
return Constants.AADGraphUrls.Any(url => url.IndexOf(host, StringComparison.OrdinalIgnoreCase) > 0);
}
static string GetTenantOrSubscription(Uri uri)
{
try
{
var paths = uri.AbsolutePath.Split(new[] { '/', '?' }, StringSplitOptions.RemoveEmptyEntries);
if (IsGraphApi(uri))
if (Utils.IsGraphApi(uri))
{
return Guid.Parse(paths[0]).ToString();
}

Просмотреть файл

@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARMClient.Authentication",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ARMClient.Library.Runner", "ARMClient.Library.Runner\ARMClient.Library.Runner.csproj", "{CFCAF2B0-6844-419E-9739-50528D35D2B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArmClient.Gui", "ArmClient.Gui\ArmClient.Gui.csproj", "{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -33,6 +35,10 @@ Global
{CFCAF2B0-6844-419E-9739-50528D35D2B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFCAF2B0-6844-419E-9739-50528D35D2B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFCAF2B0-6844-419E-9739-50528D35D2B1}.Release|Any CPU.Build.0 = Release|Any CPU
{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

8
ArmClient.Gui/App.config Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup>
<runtime>
</runtime>
</configuration>

8
ArmClient.Gui/App.xaml Normal file
Просмотреть файл

@ -0,0 +1,8 @@
<Application x:Class="ArmGuiClient.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

17
ArmClient.Gui/App.xaml.cs Normal file
Просмотреть файл

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace ArmGuiClient
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

Просмотреть файл

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E3542A27-0F79-4B24-AC34-8DD7B62A46B9}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ArmGuiClient</RootNamespace>
<AssemblyName>ArmGuiClient</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.14.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.14.201151115\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms">
<HintPath>..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.14.201151115\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="GuiPersistentAuthHelper.cs" />
<Compile Include="Models\ConfigSettingFactory.cs" />
<Compile Include="Utils\HttpLoggingHandler.cs" />
<Compile Include="Utils\Logger.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Models\ConfigSettings.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ARMClient.Authentication\ARMClient.Authentication.csproj">
<Project>{2824485e-0ead-4f72-b897-ffdaff346528}</Project>
<Name>ARMClient.Authentication</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,19 @@
using ARMClient.Authentication.AADAuthentication;
using ARMClient.Authentication.Contracts;
using System.Collections.Generic;
namespace ArmGuiClient
{
public class GuiPersistentAuthHelper : PersistentAuthHelper
{
public GuiPersistentAuthHelper(AzureEnvironments azureEnvironment = AzureEnvironments.Prod)
: base(azureEnvironment)
{
}
public Dictionary<string, TenantCacheInfo> GetTenants()
{
return this.TenantStorage.GetCache();
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib" x:Class="ArmGuiClient.MainWindow"
Title="ARM GUI Client" Height="665.5" Width="809" ResizeMode="NoResize">
<Grid>
<ComboBox x:Name="SubscriptionCB" HorizontalAlignment="Left" Margin="201,10,0,0" VerticalAlignment="Top" Width="298" Height="22" SelectionChanged="SubscriptionCB_SelectionChanged"/>
<Button x:Name="LoginBtn" Content="Login" HorizontalAlignment="Left" Margin="651,10,0,0" VerticalAlignment="Top" Width="67" Click="LoginBtn_Click" Height="22"/>
<Button x:Name="LogoutBtn" Content="Logout" HorizontalAlignment="Left" Margin="723,10,0,0" VerticalAlignment="Top" Width="67" Click="LogoutBtn_Click" Height="22"/>
<RichTextBox x:Name="OutputRTB" IsReadOnly="True" HorizontalAlignment="Left" Height="534" Margin="201,39,0,0"
VerticalAlignment="Top" Width="589" ScrollViewer.VerticalScrollBarVisibility="Auto" Padding="0">
<FlowDocument>
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</FlowDocument.Resources>
<Paragraph>
</Paragraph>
</FlowDocument>
</RichTextBox>
<TextBox x:Name="CmdText" HorizontalAlignment="Left" Height="46" Margin="0,579,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="694"/>
<Button x:Name="ExecuteBtn" Margin="710,579,0,0" VerticalAlignment="Top" Height="46" HorizontalAlignment="Left" Width="80" Click="ExecuteBtn_Click">
<StackPanel>
<TextBlock Text="Execute" HorizontalAlignment="Center"/>
<TextBlock Text="(Ctrl + Enter)" HorizontalAlignment="Center"/>
</StackPanel>
</Button>
<ComboBox x:Name="ApiVersionCB" HorizontalAlignment="Left" Margin="504,10,0,0" VerticalAlignment="Top" Width="100" Height="22" SelectedIndex="0" SelectionChanged="ApiVersionCB_SelectionChanged">
<System:String>2014-11-01</System:String>
</ComboBox>
<ComboBox x:Name="TenantCB" HorizontalAlignment="Left" Margin="6,10,0,0" VerticalAlignment="Top" Width="190" SelectionChanged="TenantCB_SelectionChanged"/>
<ComboBox x:Name="ActionCB" HorizontalAlignment="Left" Margin="6,39,0,0" VerticalAlignment="Top" Width="190" SelectionChanged="ActionCB_SelectionChanged"/>
<ListView x:Name="ParamLV" HorizontalAlignment="Left" Height="507" Margin="0,66,0,0" VerticalAlignment="Top" Width="201">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>

Просмотреть файл

@ -0,0 +1,386 @@
using ARMClient.Authentication;
using ARMClient.Authentication.Contracts;
using ArmGuiClient.Models;
using ArmGuiClient.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using AuthUtils = ARMClient.Authentication.Utilities.Utils;
namespace ArmGuiClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static readonly string _tmpPayloadFile = System.IO.Path.Combine(Environment.CurrentDirectory, "ArmGuiClient.Payload.json");
private GuiPersistentAuthHelper _authHelper;
public MainWindow()
{
InitializeComponent();
this.Dispatcher.ShutdownStarted += this.OnApplicationShutdown;
Logger.Init(this.OutputRTB);
ConfigSettingFactory.Init();
try
{
this._authHelper = new GuiPersistentAuthHelper(ConfigSettingFactory.ConfigSettings.GetAzureEnvironments());
this.InitUI();
if (this.CheckIsLogin())
{
this.PopulateTenant();
}
}
catch (Exception ex)
{
Logger.ErrorLn("{0} {1}", ex.Message, ex.StackTrace);
this.ExecuteBtn.IsEnabled = false;
}
}
private void InitUI()
{
// populate api version
if (ConfigSettingFactory.ConfigSettings.ApiVersions == null
|| ConfigSettingFactory.ConfigSettings.ApiVersions.Length == 0)
{
Logger.ErrorLn("Missing api version from config.json");
return;
}
this.ApiVersionCB.Items.Clear();
foreach (var str in ConfigSettingFactory.ConfigSettings.ApiVersions)
{
ApiVersionCB.Items.Add(new ComboBoxItem()
{
Content = str
});
}
this.ApiVersionCB.SelectedIndex = 0;
// populate actions
if (ConfigSettingFactory.ConfigSettings.Actioins == null
|| ConfigSettingFactory.ConfigSettings.Actioins.Length == 0)
{
Logger.ErrorLn("No action define in config.json");
return;
}
this.ActionCB.Items.Clear();
foreach (var item in ConfigSettingFactory.ConfigSettings.Actioins)
{
this.ActionCB.Items.Add(new ComboBoxItem()
{
Content = item.Name
});
}
this.ActionCB.SelectedIndex = 0;
this.OutputRTB.Document.Blocks.Clear();
// bind keyboard short cuts
var editPayloadCommand = new RoutedCommand();
editPayloadCommand.InputGestures.Add(new KeyGesture(Key.W, ModifierKeys.Control, "Ctrl + E"));
this.CommandBindings.Add(new CommandBinding(editPayloadCommand, this.ExecuteEditPayloadCommand));
var executeArmCommand = new RoutedCommand();
executeArmCommand.InputGestures.Add(new KeyGesture(Key.Enter, ModifierKeys.Control, "Ctrl + Enter"));
this.CommandBindings.Add(new CommandBinding(executeArmCommand, this.ExecuteRunArmRequestCommand));
var editConfigCommand = new RoutedCommand();
editConfigCommand.InputGestures.Add(new KeyGesture(Key.P, ModifierKeys.Control, "Ctrl + P"));
this.CommandBindings.Add(new CommandBinding(editConfigCommand, this.ExecuteEditConfigCommand));
}
private void PopulateParamsUI(ConfigActioin action)
{
this.ParamLV.Items.Clear();
double textboxWidth = this.ParamLV.Width * 0.7;
if (action == null || action.Params == null || action.Params.Length == 0)
{
return;
}
for (int i = 0; i < action.Params.Length; i++)
{
ActionParam param = action.Params[i];
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Vertical;
Label label = new Label();
label.Content = param.Name + (param.Required ? "*" : string.Empty);
TextBox textbox = new TextBox();
textbox.Name = param.PlaceHolder;
textbox.Width = textboxWidth;
textbox.KeyUp += new KeyEventHandler((object sender, KeyEventArgs e) =>
{
this.UpdateCmdText();
});
sp.Children.Add(label);
sp.Children.Add(textbox);
this.ParamLV.Items.Add(sp);
}
}
private bool CheckIsLogin()
{
if (this._authHelper != null && this._authHelper.IsCacheValid())
{
this.LoginBtn.IsEnabled = false;
this.LogoutBtn.IsEnabled = true;
this.ExecuteBtn.IsEnabled = true;
return true;
}
else
{
this.LoginBtn.IsEnabled = true;
this.LogoutBtn.IsEnabled = false;
this.ExecuteBtn.IsEnabled = false;
this.TenantCB.ItemsSource = new object[0];
this.SubscriptionCB.ItemsSource = new object[0];
return false;
}
}
private void UpdateCmdText()
{
ConfigActioin action = this.GetSelectedAction();
if (action == null)
{
return;
}
string cmd = action.Template;
foreach (var item in this.ParamLV.Items)
{
StackPanel wrapperPanel = item as StackPanel;
if (wrapperPanel != null && wrapperPanel.Children.Count == 2)
{
TextBox tb = wrapperPanel.Children[1] as TextBox;
if (tb != null && !string.IsNullOrWhiteSpace(tb.Text))
{
cmd = cmd.Replace("{" + tb.Name + "}", tb.Text);
}
}
}
// api version
ComboBoxItem apiItem = this.ApiVersionCB.SelectedValue as ComboBoxItem;
cmd = cmd.Replace("{apiVersion}", apiItem.Content as string);
// subscription
string subscriptionId = this.SubscriptionCB.SelectedValue as string;
if (!string.IsNullOrWhiteSpace(subscriptionId))
{
cmd = cmd.Replace("{subscription}", subscriptionId);
}
this.CmdText.Text = cmd;
}
private void PopulateTenant()
{
Dictionary<string, TenantCacheInfo> tenants = this._authHelper.GetTenants();
this.TenantCB.ItemsSource = tenants.Values;
this.TenantCB.DisplayMemberPath = "displayName";
this.TenantCB.SelectedValuePath = "tenantId";
Logger.InfoLn("{0} tenant found.", tenants.Count);
if (tenants.Count > 0)
{
this.TenantCB.SelectedIndex = 0;
}
}
private ConfigActioin GetSelectedAction()
{
if (this.ActionCB == null)
{
return null;
}
int actionIdx = this.ActionCB.SelectedIndex;
if (actionIdx > -1)
{
return ConfigSettingFactory.ConfigSettings.Actioins[actionIdx];
}
else
{
return null;
}
}
private async Task RunArmRequest()
{
try
{
this.ExecuteBtn.IsEnabled = false;
string path = this.CmdText.Text;
string subscriptionId = this.SubscriptionCB.SelectedValue as string;
ConfigActioin action = this.GetSelectedAction();
Uri uri = AuthUtils.EnsureAbsoluteUri(path, this._authHelper);
var cacheInfo = await this._authHelper.GetToken(subscriptionId, null);
var handler = new HttpLoggingHandler(new HttpClientHandler(), ConfigSettingFactory.ConfigSettings.Verbose);
HttpContent payload = null;
if (!string.Equals("get", action.HttpMethod, StringComparison.OrdinalIgnoreCase)
&& !string.Equals("delete", action.HttpMethod, StringComparison.OrdinalIgnoreCase))
{
payload = new StringContent(File.ReadAllText(_tmpPayloadFile), Encoding.UTF8, Constants.JsonContentType);
}
await AuthUtils.HttpInvoke(uri, cacheInfo, action.HttpMethod, handler, payload);
}
catch (Exception ex)
{
Logger.ErrorLn("{0} {1}", ex.Message, ex.StackTrace);
}
finally
{
this.ExecuteBtn.IsEnabled = true;
}
}
private void InvokeEditorToEditPayload()
{
try
{
ConfigActioin action = this.GetSelectedAction();
if (string.Equals("get", action.HttpMethod, StringComparison.OrdinalIgnoreCase) ||
string.Equals("delete", action.HttpMethod, StringComparison.OrdinalIgnoreCase))
{
return;
}
string payload = action.Payload;
File.WriteAllText(_tmpPayloadFile, string.IsNullOrWhiteSpace(payload) ? "" : payload);
Process.Start(ConfigSettingFactory.ConfigSettings.Editor, _tmpPayloadFile);
Logger.InfoLn("Editing payload in {0} (Ctrl + W)", _tmpPayloadFile);
}
catch (Exception ex)
{
Logger.ErrorLn("Editor: '{0}'", ConfigSettingFactory.ConfigSettings.Editor);
Logger.ErrorLn("{0} {1}", ex.Message, ex.StackTrace);
}
}
private async void LoginBtn_Click(object sender, RoutedEventArgs e)
{
await this._authHelper.AcquireTokens();
if (this.CheckIsLogin())
{
this.PopulateTenant();
}
}
private void ActionCB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ConfigActioin action = this.GetSelectedAction();
this.PopulateParamsUI(action);
this.UpdateCmdText();
if (ConfigSettingFactory.ConfigSettings.AutoPromptEditor)
{
this.InvokeEditorToEditPayload();
}
else
{
Logger.WarnLn("Ctrl + W to edit payload.");
}
}
private void TenantCB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string selectedTenantId = this.TenantCB.SelectedValue as string;
if (!string.IsNullOrWhiteSpace(selectedTenantId))
{
Dictionary<string, TenantCacheInfo> tenants = this._authHelper.GetTenants();
TenantCacheInfo tenant = tenants[selectedTenantId];
this.SubscriptionCB.ItemsSource = tenant.subscriptions;
this.SubscriptionCB.DisplayMemberPath = "displayName";
this.SubscriptionCB.SelectedValuePath = "subscriptionId";
if (tenant.subscriptions.Length > 0)
{
this.SubscriptionCB.SelectedIndex = 0;
}
Logger.InfoLn("{0} subscription found under tenant '{1}'", tenant.subscriptions.Length, tenant.displayName);
}
this.UpdateCmdText();
}
private void SubscriptionCB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.UpdateCmdText();
}
private void LogoutBtn_Click(object sender, RoutedEventArgs e)
{
this._authHelper.ClearTokenCache();
this.CheckIsLogin();
Logger.WarnLn("Goodbye!");
}
private async void ExecuteBtn_Click(object sender, RoutedEventArgs e)
{
await this.RunArmRequest();
}
private void ApiVersionCB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.UpdateCmdText();
}
private void ExecuteEditPayloadCommand(object sender, ExecutedRoutedEventArgs e)
{
ConfigActioin action = this.GetSelectedAction();
if (string.Equals("get", action.HttpMethod, StringComparison.OrdinalIgnoreCase) ||
string.Equals("delete", action.HttpMethod, StringComparison.OrdinalIgnoreCase))
{
Logger.WarnLn("{0} request doesn`t require any payload", action.HttpMethod);
return;
}
this.InvokeEditorToEditPayload();
}
private async void ExecuteRunArmRequestCommand(object sender, ExecutedRoutedEventArgs e)
{
await this.RunArmRequest();
}
private void ExecuteEditConfigCommand(object sender, ExecutedRoutedEventArgs e)
{
try
{
Process.Start(ConfigSettingFactory.ConfigSettings.Editor, ConfigSettingFactory.ConfigFilePath);
Logger.InfoLn("Editing {0} (Ctrl + P)", ConfigSettingFactory.ConfigFilePath);
}
catch (Exception ex)
{
Logger.ErrorLn("Editor: '{0}'", ConfigSettingFactory.ConfigSettings.Editor);
Logger.ErrorLn("Expecting Config.json in: '{0}'", ConfigSettingFactory.ConfigFilePath);
Logger.ErrorLn("{0} {1}", ex.Message, ex.StackTrace);
}
}
private void OnApplicationShutdown(object sender, EventArgs arg)
{
ConfigSettingFactory.Shutdown();
}
}
}

Просмотреть файл

@ -0,0 +1,88 @@

using ArmGuiClient.Utils;
using System;
using System.IO;
using System.Timers;
using System.Web.Script.Serialization;
using System.Windows;
namespace ArmGuiClient.Models
{
internal class ConfigSettingFactory
{
public static readonly string ConfigFilePath = System.IO.Path.Combine(Environment.CurrentDirectory, "config.json");
private static ConfigSettings _settingInstance;
private static FileSystemWatcher _configWatcher;
private static Timer _refreshTimer;
public static void Init()
{
_configWatcher = new FileSystemWatcher();
_configWatcher.Path = System.IO.Path.GetDirectoryName(ConfigFilePath);
_configWatcher.Filter = System.IO.Path.GetFileName(ConfigFilePath);
_configWatcher.NotifyFilter = NotifyFilters.LastWrite;
_configWatcher.Changed += new FileSystemEventHandler(AutoUpdateConfigOnChanged);
_configWatcher.EnableRaisingEvents = true;
// https://msdn.microsoft.com/en-us/library/xcc1t119(v=vs.71).aspx
// FileSystemWatcher changed event will be raised multiple time
// delay action by 1 seconds to wait till all events have been raised
_refreshTimer = new Timer();
_refreshTimer.AutoReset = false;
_refreshTimer.Interval = TimeSpan.FromSeconds(1).TotalMilliseconds;
_refreshTimer.Elapsed += (object sender, ElapsedEventArgs e) =>
{
try
{
_settingInstance = GetConfigSettings();
Logger.InfoLn("Changes are detected from config.json. Setting updated.");
}
catch (Exception ex)
{
Logger.ErrorLn("Changes are detected from config.json. {0} {1}", ex.Message, ex.StackTrace);
}
};
}
public static void Shutdown()
{
if (_configWatcher != null)
{
_configWatcher.Dispose();
}
if (_refreshTimer != null)
{
_refreshTimer.Dispose();
}
}
public static ConfigSettings ConfigSettings
{
get
{
if (_settingInstance == null)
{
_settingInstance = GetConfigSettings();
}
return _settingInstance;
}
}
public static void Refresh()
{
_refreshTimer.Start();
}
private static ConfigSettings GetConfigSettings()
{
var ser = new JavaScriptSerializer();
return ser.Deserialize<ConfigSettings>(File.ReadAllText(ConfigFilePath));
}
private static void AutoUpdateConfigOnChanged(object source, FileSystemEventArgs e)
{
Refresh();
}
}
}

Просмотреть файл

@ -0,0 +1,91 @@
using ARMClient.Authentication.Contracts;
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
namespace ArmGuiClient.Models
{
public class ActionParam
{
public string Name { get; set; }
public string PlaceHolder { get; set; }
public bool Required { get; set; }
}
public class ConfigActioin
{
private static readonly JavaScriptSerializer _ser = new JavaScriptSerializer();
private dynamic _payload;
public string Name { get; set; }
public string Template { get; set; }
public ActionParam[] Params { get; set; }
public string HttpMethod { get; set; }
public dynamic Payload
{
get
{
if (this._payload == null)
{
return "";
}
return _ser.Serialize(this._payload);
}
set
{
this._payload = value;
}
}
}
public class ConfigSettings
{
private string _editor;
public string TargetEnvironment { get; set; }
public bool Verbose { get; set; }
public string Editor
{
get
{
if (string.IsNullOrWhiteSpace(this._editor))
{
this._editor = @"%windir%\system32\notepad.exe";
}
return Environment.ExpandEnvironmentVariables(this._editor);
}
set
{
this._editor = value;
}
}
public string[] ApiVersions { get; set; }
public bool AutoPromptEditor { get; set; }
public Dictionary<string, object> DefaultValues { get; set; }
public ConfigActioin[] Actioins { get; set; }
public AzureEnvironments GetAzureEnvironments()
{
AzureEnvironments env;
if (Enum.TryParse<AzureEnvironments>(this.TargetEnvironment, true, out env))
{
return env;
}
return AzureEnvironments.Prod;
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ArmGuiClient")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Hewlett-Packard Company")]
[assembly: AssemblyProduct("ArmGuiClient")]
[assembly: AssemblyCopyright("Copyright © Hewlett-Packard Company 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

71
ArmClient.Gui/Properties/Resources.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ArmGuiClient.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ArmGuiClient.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

Просмотреть файл

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

30
ArmClient.Gui/Properties/Settings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ArmGuiClient.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Просмотреть файл

@ -0,0 +1,92 @@
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace ArmGuiClient.Utils
{
class HttpLoggingHandler : DelegatingHandler
{
private readonly bool _verbose;
public HttpLoggingHandler(HttpMessageHandler innerHandler, bool verbose)
: base(innerHandler)
{
this._verbose = verbose;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_verbose)
{
Logger.InfoLn("---------- Request -----------------------");
Logger.InfoLn("{0} {1} HTTP/{2}", request.Method, request.RequestUri.PathAndQuery, request.Version);
Logger.InfoLn("Host: {0}", request.RequestUri.Host);
foreach (var header in request.Headers)
{
string headerVal = string.Empty;
if (String.Equals("Authorization", header.Key))
{
headerVal = header.Value.First().Substring(0, 70) + "...";
}
else
{
headerVal = String.Join("; ", header.Value);
}
Logger.InfoLn("{0}: {1}", header.Key, headerVal);
}
await DumpContent(request.Content);
}
var watch = new Stopwatch();
watch.Start();
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
watch.Stop();
int responseStatusCocde = (int)response.StatusCode;
if (_verbose)
{
Logger.InfoLn("---------- Response ({0} ms) ------------", watch.ElapsedMilliseconds);
Logger.InfoLn("HTTP/{0} {1} {2}", response.Version, responseStatusCocde, response.StatusCode);
foreach (var header in response.Headers)
{
Logger.InfoLn("{0}: {1}", header.Key, String.Join("; ", header.Value));
}
}
await DumpContent(response.Content, this.isErrorRequest(responseStatusCocde));
return response;
}
private async Task DumpContent(HttpContent content, bool isErrorContent = false)
{
if (content == null || content.Headers.ContentType == null)
{
return;
}
var result = await content.ReadAsStringAsync();
Logger.InfoLn(string.Empty);
if (!string.IsNullOrWhiteSpace(result))
{
dynamic parsedJson = JsonConvert.DeserializeObject(result);
string indentedResult = JsonConvert.SerializeObject(parsedJson, Formatting.Indented);
Logger.WriteLn(indentedResult, isErrorContent ? Logger.ErrorBrush : Logger.InfoBrush);
}
}
private bool isErrorRequest(int responseStatusCode)
{
return responseStatusCode >= 400 && responseStatusCode < 600;
}
}
}

Просмотреть файл

@ -0,0 +1,49 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace ArmGuiClient.Utils
{
internal class Logger
{
public static readonly Brush InfoBrush = Brushes.LightGreen;
public static readonly Brush WarnBrush = Brushes.Yellow;
public static readonly Brush ErrorBrush = Brushes.Red;
private static RichTextBox _outputRTB;
private static FlowDocument _flowDoc;
public static void Init(RichTextBox textBox)
{
_outputRTB = textBox;
_flowDoc = _outputRTB.Document;
}
public static void WriteLn(string content, Brush background)
{
Application.Current.Dispatcher.Invoke(() =>
{
var para = new Paragraph(new Run(content));
para.Background = background;
_outputRTB.Document.Blocks.Add(para);
_outputRTB.ScrollToEnd();
});
}
public static void InfoLn(string format, params object[] args)
{
WriteLn(string.Format(format, args), InfoBrush);
}
public static void WarnLn(string format, params object[] args)
{
WriteLn(string.Format(format, args), WarnBrush);
}
public static void ErrorLn(string format, params object[] args)
{
WriteLn(string.Format(format, args), ErrorBrush);
}
}
}

129
ArmClient.Gui/config.json Normal file
Просмотреть файл

@ -0,0 +1,129 @@
{
"TargetEnvironment": "Prod",
"ApiVersions": [ "2014-04-01", "2014-11-01" ],
"Verbose": "true",
"Editor": "%windir%\\system32\\notepad.exe",
"AutoPromptEditor": "true",
"DefaultValues": {
"resourceGroup": "tryag",
siteName: "trysite001"
},
"Actioins": [
{
"httpMethod": "GET",
"name": "List Resource Groups",
"template": "/subscriptions/{subscription}/resourceGroups?api-version={apiVersion}"
},
{
"httpMethod": "GET",
"name": "Get Resource Group",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
}
]
},
{
"httpMethod": "PUT",
"name": "Create Resource Group",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
}
],
"payload": {
"location": "{location}"
}
},
{
"httpMethod": "DELETE",
"name": "Delete Resource Group",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
}
]
},
{
"httpMethod": "GET",
"name": "List Websites",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
}
]
},
{
"httpMethod": "GET",
"name": "Get Website",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{siteName}?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
},
{
"name": "Website Name",
"placeHolder": "siteName",
"required": "true"
}
]
},
{
"httpMethod": "GET",
"name": "List Site Extensions",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{siteName}/siteextensions?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
},
{
"name": "Website Name",
"placeHolder": "siteName",
"required": "true"
}
]
},
{
"httpMethod": "PUT",
"name": "Install Site Extensions",
"template": "/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{siteName}/siteextensions/{extensionName}?api-version={apiVersion}",
"params": [
{
"name": "Resource Group Name",
"placeHolder": "resourceGroup",
"required": "true"
},
{
"name": "Website Name",
"placeHolder": "siteName",
"required": "true"
},
{
"name": "Extension Name",
"placeHolder": "extensionName",
"required": "true"
}
],
"payload": {
"properties": {
}
}
}
]
}

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.2" targetFramework="net451" />
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.14.201151115" targetFramework="net451" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net451" />
</packages>