mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-25 14:06:41 +01:00
Adds the github login prompt
This commit is contained in:
@@ -44,6 +44,12 @@
|
||||
<setting name="RemoteEntropy" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="GitHubAPIKey" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
<setting name="GitHubAPIKeyEntropy" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
</TGControlPanel.Properties.Settings>
|
||||
<TGControlPanel.Properties.Settings1>
|
||||
<setting name="LastPageIndex" serializeAs="String">
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
namespace TGControlPanel
|
||||
{
|
||||
partial class GitHubLoginPrompt
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.PasswordLabel = new System.Windows.Forms.Label();
|
||||
this.UsernameLabel = new System.Windows.Forms.Label();
|
||||
this.PasswordTextBox = new System.Windows.Forms.TextBox();
|
||||
this.UsernameTextBox = new System.Windows.Forms.TextBox();
|
||||
this.DividerLabel = new System.Windows.Forms.Label();
|
||||
this.APIKeyLabel = new System.Windows.Forms.Label();
|
||||
this.APIKeyTextBox = new System.Windows.Forms.TextBox();
|
||||
this.OrLabel = new System.Windows.Forms.Label();
|
||||
this.LoginButton = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// PasswordLabel
|
||||
//
|
||||
this.PasswordLabel.AutoSize = true;
|
||||
this.PasswordLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.PasswordLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242)))));
|
||||
this.PasswordLabel.Location = new System.Drawing.Point(12, 41);
|
||||
this.PasswordLabel.Name = "PasswordLabel";
|
||||
this.PasswordLabel.Size = new System.Drawing.Size(92, 18);
|
||||
this.PasswordLabel.TabIndex = 25;
|
||||
this.PasswordLabel.Text = "Password:";
|
||||
this.PasswordLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// UsernameLabel
|
||||
//
|
||||
this.UsernameLabel.AutoSize = true;
|
||||
this.UsernameLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.UsernameLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242)))));
|
||||
this.UsernameLabel.Location = new System.Drawing.Point(12, 9);
|
||||
this.UsernameLabel.Name = "UsernameLabel";
|
||||
this.UsernameLabel.Size = new System.Drawing.Size(97, 18);
|
||||
this.UsernameLabel.TabIndex = 24;
|
||||
this.UsernameLabel.Text = "Username:";
|
||||
this.UsernameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// PasswordTextBox
|
||||
//
|
||||
this.PasswordTextBox.Location = new System.Drawing.Point(132, 41);
|
||||
this.PasswordTextBox.Name = "PasswordTextBox";
|
||||
this.PasswordTextBox.Size = new System.Drawing.Size(233, 20);
|
||||
this.PasswordTextBox.TabIndex = 23;
|
||||
this.PasswordTextBox.UseSystemPasswordChar = true;
|
||||
//
|
||||
// UsernameTextBox
|
||||
//
|
||||
this.UsernameTextBox.Location = new System.Drawing.Point(132, 9);
|
||||
this.UsernameTextBox.Name = "UsernameTextBox";
|
||||
this.UsernameTextBox.Size = new System.Drawing.Size(233, 20);
|
||||
this.UsernameTextBox.TabIndex = 22;
|
||||
//
|
||||
// DividerLabel
|
||||
//
|
||||
this.DividerLabel.AutoSize = true;
|
||||
this.DividerLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.DividerLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242)))));
|
||||
this.DividerLabel.Location = new System.Drawing.Point(-24, 59);
|
||||
this.DividerLabel.Name = "DividerLabel";
|
||||
this.DividerLabel.Size = new System.Drawing.Size(468, 18);
|
||||
this.DividerLabel.TabIndex = 26;
|
||||
this.DividerLabel.Text = "______________________________________________";
|
||||
this.DividerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// APIKeyLabel
|
||||
//
|
||||
this.APIKeyLabel.AutoSize = true;
|
||||
this.APIKeyLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.APIKeyLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242)))));
|
||||
this.APIKeyLabel.Location = new System.Drawing.Point(12, 95);
|
||||
this.APIKeyLabel.Name = "APIKeyLabel";
|
||||
this.APIKeyLabel.Size = new System.Drawing.Size(78, 18);
|
||||
this.APIKeyLabel.TabIndex = 28;
|
||||
this.APIKeyLabel.Text = "API Key:";
|
||||
this.APIKeyLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// APIKeyTextBox
|
||||
//
|
||||
this.APIKeyTextBox.Location = new System.Drawing.Point(132, 95);
|
||||
this.APIKeyTextBox.Name = "APIKeyTextBox";
|
||||
this.APIKeyTextBox.Size = new System.Drawing.Size(233, 20);
|
||||
this.APIKeyTextBox.TabIndex = 27;
|
||||
this.APIKeyTextBox.UseSystemPasswordChar = true;
|
||||
//
|
||||
// OrLabel
|
||||
//
|
||||
this.OrLabel.AutoSize = true;
|
||||
this.OrLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.OrLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242)))));
|
||||
this.OrLabel.Location = new System.Drawing.Point(157, 64);
|
||||
this.OrLabel.Name = "OrLabel";
|
||||
this.OrLabel.Size = new System.Drawing.Size(32, 18);
|
||||
this.OrLabel.TabIndex = 29;
|
||||
this.OrLabel.Text = "OR";
|
||||
this.OrLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// LoginButton
|
||||
//
|
||||
this.LoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.LoginButton.Location = new System.Drawing.Point(145, 133);
|
||||
this.LoginButton.Name = "LoginButton";
|
||||
this.LoginButton.Size = new System.Drawing.Size(88, 25);
|
||||
this.LoginButton.TabIndex = 30;
|
||||
this.LoginButton.Text = InitalLoginButtonText;
|
||||
this.LoginButton.UseVisualStyleBackColor = true;
|
||||
this.LoginButton.Click += new System.EventHandler(this.LoginButton_Click);
|
||||
//
|
||||
// GitHubLoginPrompt
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34)))));
|
||||
this.ClientSize = new System.Drawing.Size(376, 170);
|
||||
this.Controls.Add(this.LoginButton);
|
||||
this.Controls.Add(this.OrLabel);
|
||||
this.Controls.Add(this.APIKeyLabel);
|
||||
this.Controls.Add(this.APIKeyTextBox);
|
||||
this.Controls.Add(this.DividerLabel);
|
||||
this.Controls.Add(this.PasswordLabel);
|
||||
this.Controls.Add(this.UsernameLabel);
|
||||
this.Controls.Add(this.PasswordTextBox);
|
||||
this.Controls.Add(this.UsernameTextBox);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "GitHubLoginPrompt";
|
||||
this.ShowIcon = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Login To GitHub";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label PasswordLabel;
|
||||
private System.Windows.Forms.Label UsernameLabel;
|
||||
private System.Windows.Forms.TextBox PasswordTextBox;
|
||||
private System.Windows.Forms.TextBox UsernameTextBox;
|
||||
private System.Windows.Forms.Label DividerLabel;
|
||||
private System.Windows.Forms.Label APIKeyLabel;
|
||||
private System.Windows.Forms.TextBox APIKeyTextBox;
|
||||
private System.Windows.Forms.Label OrLabel;
|
||||
private System.Windows.Forms.Button LoginButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Octokit;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using TGServiceInterface;
|
||||
|
||||
namespace TGControlPanel
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for recieving a GitHub API key for use in <see cref="Credentials"/>
|
||||
/// </summary>
|
||||
sealed partial class GitHubLoginPrompt : Form
|
||||
{
|
||||
/// <summary>
|
||||
/// The text to appear on <see cref="LoginButton"/>
|
||||
/// </summary>
|
||||
const string InitalLoginButtonText = "Login";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GitHubClient"/> to use for OAuth requests
|
||||
/// </summary>
|
||||
readonly GitHubClient client;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="GitHubLoginPrompt"/>
|
||||
/// </summary>
|
||||
/// <param name="c">The <see cref="GitHubClient"/> to use for requests</param>
|
||||
public GitHubLoginPrompt(GitHubClient c)
|
||||
{
|
||||
InitializeComponent();
|
||||
AcceptButton = LoginButton;
|
||||
DialogResult = DialogResult.Cancel;
|
||||
client = c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="GetAPIKey"/>. On success, encrypts it's return value, saves it in <see cref="Properties.Settings.GitHubAPIKey"/> and <see cref="Properties.Settings.GitHubAPIKeyEntropy"/>, and closes the <see cref="GitHubLoginPrompt"/>. On failure, shows a <see cref="MessageBox"/> and exits
|
||||
/// </summary>
|
||||
/// <param name="sender">The sender of the event</param>
|
||||
/// <param name="e">The <see cref="EventArgs"/></param>
|
||||
async void LoginButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
LoginButton.Text = "Logging in...";
|
||||
Enabled = false;
|
||||
var APIKey = await GetAPIKey();
|
||||
if(APIKey == null)
|
||||
{
|
||||
MessageBox.Show("Authentication failure!");
|
||||
Enabled = true;
|
||||
LoginButton.Text = "Login";
|
||||
return;
|
||||
}
|
||||
|
||||
//Encrypt it and let's be on our way
|
||||
var Config = Properties.Settings.Default;
|
||||
Config.GitHubAPIKey = Helpers.EncryptData(APIKey, out string entropy);
|
||||
Config.GitHubAPIKeyEntropy = entropy;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an API key from GitHub based on login information or from the <see cref="APIKeyTextBox"/> and verifies it
|
||||
/// </summary>
|
||||
/// <returns>The GitHub API key on success, <see langword="null"/> on failure</returns>
|
||||
async Task<string> GetAPIKey()
|
||||
{
|
||||
string APIKey;
|
||||
if (String.IsNullOrWhiteSpace(APIKeyTextBox.Text))
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Credentials = new Credentials(UsernameTextBox.Text, PasswordTextBox.Text);
|
||||
var token = await client.Authorization.Create(new NewAuthorization { Note = "TGControlPanel token to bypass rate limiting" });
|
||||
APIKey = token.Token;
|
||||
}
|
||||
catch (AuthorizationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
APIKey = APIKeyTextBox.Text;
|
||||
|
||||
//validate it by pinging a random repository
|
||||
client.Credentials = new Credentials(APIKey);
|
||||
try
|
||||
{
|
||||
await client.Repository.Get("Dextraspace", "Test");
|
||||
}
|
||||
catch (AuthorizationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return APIKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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.Runtime.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:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<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" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</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" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
+25
-1
@@ -12,7 +12,7 @@ namespace TGControlPanel.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
@@ -142,5 +142,29 @@ namespace TGControlPanel.Properties {
|
||||
this["RemoteEntropy"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string GitHubAPIKey {
|
||||
get {
|
||||
return ((string)(this["GitHubAPIKey"]));
|
||||
}
|
||||
set {
|
||||
this["GitHubAPIKey"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string GitHubAPIKeyEntropy {
|
||||
get {
|
||||
return ((string)(this["GitHubAPIKeyEntropy"]));
|
||||
}
|
||||
set {
|
||||
this["GitHubAPIKeyEntropy"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,5 +32,11 @@
|
||||
<Setting Name="RemoteEntropy" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="GitHubAPIKey" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="GitHubAPIKeyEntropy" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -64,6 +64,12 @@
|
||||
<Compile Include="CountedForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="GitHubLoginPrompt.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="GitHubLoginPrompt.Designer.cs">
|
||||
<DependentUpon>GitHubLoginPrompt.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="InstanceSelector.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -108,6 +114,9 @@
|
||||
<Compile Include="ServerOpForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="GithubLoginPrompt.resx">
|
||||
<DependentUpon>GitHubLoginPrompt.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="InstanceSelector.resx">
|
||||
<DependentUpon>InstanceSelector.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
|
||||
@@ -30,78 +30,96 @@ namespace TGControlPanel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate <see cref="PullRequestListBox"/> with <see cref="PullRequestInfo"/> from github and check off ones that are currently merged
|
||||
/// Populate <see cref="PullRequestListBox"/> with <see cref="PullRequestInfo"/> from github and check off ones that are currently merged. Prompts the user to login to GitHub if they hit the rate limit
|
||||
/// </summary>
|
||||
async void LoadPullRequests()
|
||||
/// <param name="client">The <see cref="GitHubClient"/> to use. If <see langword="null"/>, one will be created</param>
|
||||
async void LoadPullRequests(GitHubClient client)
|
||||
{
|
||||
Enabled = false;
|
||||
UseWaitCursor = true;
|
||||
if(client == null)
|
||||
client = new GitHubClient(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name));
|
||||
var config = Properties.Settings.Default;
|
||||
if (!String.IsNullOrWhiteSpace(config.GitHubAPIKey))
|
||||
client.Credentials = new Credentials(Helpers.DecryptData(config.GitHubAPIKey, config.GitHubAPIKeyEntropy));
|
||||
try
|
||||
{
|
||||
PullRequestListBox.Items.Clear();
|
||||
|
||||
var repo = currentInterface.GetComponent<ITGRepository>();
|
||||
string remote = null, error = null, error2 = null;
|
||||
IList<PullRequestInfo> pulls = null;
|
||||
|
||||
var remoteRequest = WrapServerOp(() => remote = repo.GetRemote(out error));
|
||||
|
||||
//get started on this while we're processing here
|
||||
var pullsRequest = Task.Factory.StartNew(() => pulls = repo.MergedPullRequests(out error2));
|
||||
|
||||
await remoteRequest;
|
||||
|
||||
if (remote == null)
|
||||
{
|
||||
MessageBox.Show(String.Format("Error retrieving remote repository: {0}", error));
|
||||
return;
|
||||
}
|
||||
if (!remote.Contains("github.com"))
|
||||
{
|
||||
MessageBox.Show("Pull request support is only available for github based repositories!", "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
|
||||
var splits = remote.Split('/');
|
||||
var repoName = splits[splits.Length - 1];
|
||||
var repoOwner = splits[splits.Length - 2];
|
||||
|
||||
//Search for open PRs
|
||||
var client = new GitHubClient(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name));
|
||||
Enabled = false;
|
||||
UseWaitCursor = true;
|
||||
SearchIssuesResult result;
|
||||
try
|
||||
{
|
||||
result = await client.Search.SearchIssues(new SearchIssuesRequest
|
||||
PullRequestListBox.Items.Clear();
|
||||
|
||||
var repo = currentInterface.GetComponent<ITGRepository>();
|
||||
string remote = null, error = null, error2 = null;
|
||||
IList<PullRequestInfo> pulls = null;
|
||||
|
||||
var remoteRequest = WrapServerOp(() => remote = repo.GetRemote(out error));
|
||||
|
||||
//get started on this while we're processing here
|
||||
var pullsRequest = Task.Factory.StartNew(() => pulls = repo.MergedPullRequests(out error2));
|
||||
|
||||
await remoteRequest;
|
||||
|
||||
if (remote == null)
|
||||
{
|
||||
Repos = new RepositoryCollection
|
||||
{
|
||||
{ repoOwner, repoName }
|
||||
},
|
||||
State = ItemState.Open,
|
||||
Type = IssueTypeQualifier.PullRequest
|
||||
});
|
||||
MessageBox.Show(String.Format("Error retrieving remote repository: {0}", error));
|
||||
return;
|
||||
}
|
||||
if (!remote.Contains("github.com"))
|
||||
{
|
||||
MessageBox.Show("Pull request support is only available for github based repositories!", "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
|
||||
var splits = remote.Split('/');
|
||||
var repoName = splits[splits.Length - 1];
|
||||
var repoOwner = splits[splits.Length - 2];
|
||||
|
||||
//Search for open PRs
|
||||
Enabled = false;
|
||||
UseWaitCursor = true;
|
||||
SearchIssuesResult result;
|
||||
try
|
||||
{
|
||||
result = await client.Search.SearchIssues(new SearchIssuesRequest
|
||||
{
|
||||
Repos = new RepositoryCollection { { repoOwner, repoName } },
|
||||
State = ItemState.Open,
|
||||
Type = IssueTypeQualifier.PullRequest
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
Enabled = true;
|
||||
UseWaitCursor = false;
|
||||
}
|
||||
|
||||
//now we need to know what's merged
|
||||
await WrapServerOp(() => pullsRequest.Wait());
|
||||
if (pulls == null)
|
||||
MessageBox.Show(String.Format("Error retrieving currently merged pull requests: {0}", error2));
|
||||
|
||||
foreach (var I in result.Items)
|
||||
PullRequestListBox.Items.Add(String.Format("#{0} - {1}", I.Number, I.Title), pulls.Any(x => x.Number == I.Number));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Enabled = true;
|
||||
UseWaitCursor = false;
|
||||
}
|
||||
|
||||
//now we need to know what's merged
|
||||
await WrapServerOp(() => pullsRequest.Wait());
|
||||
if (pulls == null)
|
||||
MessageBox.Show(String.Format("Error retrieving currently merged pull requests: {0}", error2));
|
||||
|
||||
foreach (var I in result.Items)
|
||||
PullRequestListBox.Items.Add(String.Format("#{0} - {1}", I.Number, I.Title), pulls.Any(x => x.Number == I.Number));
|
||||
}
|
||||
finally
|
||||
catch (ForbiddenException)
|
||||
{
|
||||
Enabled = true;
|
||||
UseWaitCursor = false;
|
||||
if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) //assume request limit hit
|
||||
{
|
||||
if (MessageBox.Show("You seem to have hit the rate limit of 60 requests per hour of the GitHub API for anonymous requests. Would you like to enter credentials to bypass this?", "Rate limited", MessageBoxButtons.YesNo) != DialogResult.Yes)
|
||||
return;
|
||||
using (var D = new GitHubLoginPrompt(client))
|
||||
if (D.ShowDialog() == DialogResult.OK)
|
||||
LoadPullRequests(client);
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +130,7 @@ namespace TGControlPanel
|
||||
/// <param name="e">The <see cref="EventArgs"/></param>
|
||||
void PullRequestManager_Load(object sender, EventArgs e)
|
||||
{
|
||||
LoadPullRequests();
|
||||
LoadPullRequests(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +220,7 @@ namespace TGControlPanel
|
||||
/// <param name="e">The <see cref="EventArgs"/></param>
|
||||
void RefreshButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
LoadPullRequests();
|
||||
LoadPullRequests(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user