Redone contacts, gallery, projects and resume pages. Added some admin panel pages
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Models;
|
||||
using MyWebsite.Areas.API.Models;
|
||||
|
||||
namespace MyWebsite.Areas.API
|
||||
{
|
||||
@@ -9,6 +11,10 @@ namespace MyWebsite.Areas.API
|
||||
[ApiController]
|
||||
public class FoxTubeController : ControllerBase
|
||||
{
|
||||
FoxTubeDatabaseContext db;
|
||||
public FoxTubeController(FoxTubeDatabaseContext context) =>
|
||||
db = context;
|
||||
|
||||
[HttpPost("AddMetrics")]
|
||||
public string AddMetrics()
|
||||
{
|
||||
@@ -16,34 +22,88 @@ namespace MyWebsite.Areas.API
|
||||
{
|
||||
Title = Request.Form["Title"],
|
||||
Content = Request.Form["Content"],
|
||||
Version = Request.Form["Version"]
|
||||
Version = Request.Form["Version"],
|
||||
TimeStamp = DateTime.Now
|
||||
};
|
||||
|
||||
// TODO: Insert package to database
|
||||
db.Metrics.Add(metrics);
|
||||
db.SaveChanges();
|
||||
|
||||
return metrics.Id.ToString();
|
||||
return db.Metrics.FirstOrDefault(i => i.TimeStamp == metrics.TimeStamp)?.Id.ToString();
|
||||
}
|
||||
|
||||
[HttpGet("Messages")]
|
||||
public IActionResult Messages(bool toast = false, DateTime? publishedAfter = null, string lang = "en-US")
|
||||
public object Messages(bool toast = false, DateTime? publishedAfter = null, string lang = "en-US")
|
||||
{
|
||||
if(toast)
|
||||
{
|
||||
Message message = publishedAfter.HasValue ?
|
||||
db.Messages.FirstOrDefault(i => i.PublishedAt >= publishedAfter && i.PublishedAt <= DateTime.Now && i.Language == lang) :
|
||||
db.Messages.OrderByDescending(i => i.PublishedAt).FirstOrDefault();
|
||||
|
||||
if (message == null)
|
||||
return NoContent();
|
||||
|
||||
return $@"<toast activationType='foreground' launch='inbox|{message.Id}'>
|
||||
<visual>
|
||||
<binding template='ToastGeneric'>
|
||||
<image placement='hero' src='{message.HeroImage}'/>
|
||||
<image placement='appLogoOverride' hint-crop='circle' src='{message.Icon}'/>
|
||||
<text>{message.Title}</text>
|
||||
<text hint-maxLines='5'>{message.Description}</text>
|
||||
</binding>
|
||||
</visual>
|
||||
</toast>";
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Message> messages = db.Messages.Where(i => i.PublishedAt <= DateTime.Now).ToList();
|
||||
|
||||
return NoContent();
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Changelogs")]
|
||||
public IActionResult Changelogs(bool toast = false, string version = null, string lang = "en-US")
|
||||
public object Changelogs(bool toast = false, string version = null, string lang = "en-US")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
throw new ArgumentNullException("You must specify required version number");
|
||||
|
||||
if(toast)
|
||||
{
|
||||
Changelog changelog = db.Changelogs.FirstOrDefault(i => i.Version == version && i.Language == lang);
|
||||
if (changelog == null)
|
||||
return NoContent();
|
||||
|
||||
return $@"<toast activationType='foreground' launch='changelog|{changelog.Id}'>
|
||||
<visual>
|
||||
<binding template='ToastGeneric'>
|
||||
<image placement='hero' src='{changelog.HeroImage}'/>
|
||||
<image placement='appLogoOverride' hint-crop='circle' src='{changelog.Icon}'/>
|
||||
<text>{changelog.Description}</text>
|
||||
<text>{changelog.Title}</text>
|
||||
</binding>
|
||||
</visual>
|
||||
</toast>";
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Changelog> changelogs = db.Changelogs.Where(i => !IsVersionGreater(i.Version, version)).ToList();
|
||||
|
||||
return NoContent();
|
||||
return changelogs;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsVersionGreater(string versionOne, string versionTwo)
|
||||
{
|
||||
string[] v1 = versionOne.Split('.');
|
||||
string[] v2 = versionTwo.Split('.');
|
||||
|
||||
for (int i = 0; i < v1.Length && i < v2.Length; i++)
|
||||
if (byte.Parse(v1[i]) > byte.Parse(v2[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MyWebsite.Areas.API.Models
|
||||
{
|
||||
public class Changelog : Message
|
||||
{
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(5)")]
|
||||
public string Version { get; set; }
|
||||
|
||||
public Changelog()
|
||||
{
|
||||
Icon = "https://xfox111.net/projects-assets/FoxTube/ChangelogIcon.png";
|
||||
HeroImage = "https://xfox111.net/projects-assets/FoxTube/ChangelogHero.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MyWebsite.Areas.API.Models
|
||||
{
|
||||
public class FoxTubeDatabaseContext : DbContext
|
||||
{
|
||||
public DbSet<MetricsPackage> Metrics { get; set; }
|
||||
public DbSet<Message> Messages { get; set; }
|
||||
public DbSet<Changelog> Changelogs { get; set; }
|
||||
|
||||
public FoxTubeDatabaseContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Areas.API.Models
|
||||
{
|
||||
public class InboxItem
|
||||
{
|
||||
// Metadata
|
||||
[Key]
|
||||
[Required]
|
||||
[Column(TypeName = "varchae(20)")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime PublishedAt { get; set; } = DateTime.Now;
|
||||
|
||||
|
||||
// Content
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "text")]
|
||||
public string Content { get; set; }
|
||||
|
||||
|
||||
// Media
|
||||
[Column(TypeName = "varchar(255")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(255")]
|
||||
public string HeroImage { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string HtmlEmbed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Areas.API.Models
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
// Metadata
|
||||
[Key]
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime PublishedAt { get; set; } = DateTime.Now;
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(10)")]
|
||||
public string Language { get; set; } = "en-US";
|
||||
|
||||
|
||||
// Content
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "text")]
|
||||
public string Content { get; set; }
|
||||
|
||||
|
||||
// Media
|
||||
[Column(TypeName = "varchar(255")]
|
||||
public string Icon { get; set; } = "https://xfox111.net/projects-assets/FoxTube/DefaultIcon.png";
|
||||
|
||||
[Column(TypeName = "varchar(255")]
|
||||
public string HeroImage { get; set; } = "https://xfox111.net/projects-assets/FoxTube/DefaultHero.png";
|
||||
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string HtmlEmbed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Areas.API.Models
|
||||
{
|
||||
public class MetricsPackage
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "mediumtext")]
|
||||
public string Content { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(5)")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime TimeStamp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Models;
|
||||
|
||||
namespace MyWebsite.Areas.Admin.Controllers
|
||||
{
|
||||
[Area("Admin")]
|
||||
public class ContactsController : Controller
|
||||
{
|
||||
public ContactsController(DatabaseContext context) =>
|
||||
Startup.Database = context;
|
||||
|
||||
public IActionResult Index() =>
|
||||
View(Startup.Database.Links);
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Edit(string id) =>
|
||||
View(Startup.Database.Links.Find(id));
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult Edit(Link model)
|
||||
{
|
||||
Startup.Database.Links.Update(model);
|
||||
Startup.Database.SaveChanges();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@using MyWebsite.Models;
|
||||
@model Link;
|
||||
@{
|
||||
ViewData["Title"] = "Edit link";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>Edit contact link</h1>
|
||||
<p> <a asp-action="Index">Back to the list</a></p>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<form asp-action="Edit">
|
||||
<div asp-validation-summary="ModelOnly" class="validation-summary-errors"></div>
|
||||
<div>
|
||||
<label asp-for="Name"></label>
|
||||
<input asp-for="Name" />
|
||||
<span asp-validation-for="Name"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Title"></label>
|
||||
<input asp-for="Title" />
|
||||
<span asp-validation-for="Title"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Username"></label>
|
||||
<input asp-for="Username" />
|
||||
<span asp-validation-for="Username"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Url"></label>
|
||||
<input asp-for="Url" />
|
||||
<span asp-validation-for="Url"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="CanContactMe">@Html.DisplayNameFor(model => model.CanContactMe)</label>
|
||||
<input style="width:auto" type="checkbox" asp-for="CanContactMe" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Save"/>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="~/css/Admin.css" />
|
||||
@@ -0,0 +1,55 @@
|
||||
@model IEnumerable<MyWebsite.Models.Link>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Contacts - Admin panel";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>Contact links list</h1>
|
||||
<p> <a asp-action="Index" asp-controller="Admin" asp-area="">Back to admin panel</a></p>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<p>
|
||||
<a class="comment" asp-action="Create">// + Create New</a>
|
||||
</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Title</th>
|
||||
<th>Username</th>
|
||||
<th>URL</th>
|
||||
<th>Contact link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.OrderByDescending(i => i.CanContactMe))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Name)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Title)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Username)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.Url)
|
||||
</td>
|
||||
<td>
|
||||
@Html.DisplayFor(modelItem => item.CanContactMe)
|
||||
</td>
|
||||
<td>
|
||||
@Html.ActionLink("Edit", "Edit", new { id = item.Name }) |
|
||||
@Html.ActionLink("Delete", "Delete", new { id = item.Name })
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="~/css/Admin.css" />
|
||||
@@ -0,0 +1,3 @@
|
||||
@using MyWebsite
|
||||
@using MyWebsite.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Helpers;
|
||||
using MyWebsite.Models;
|
||||
|
||||
namespace MyWebsite.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class AdminController : Controller
|
||||
{
|
||||
public AdminController(DatabaseContext context) =>
|
||||
Startup.Database = context;
|
||||
|
||||
public IActionResult Index() =>
|
||||
View();
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
public IActionResult Login() =>
|
||||
View();
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Login(LoginModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
ModelState.AddModelError("Authorization error", "Invalid data");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
LoginModel user = Startup.Database.Users.FirstOrDefault(i => i.Email == model.Email);
|
||||
if (user == null || !Cryptography.VerifyHash(model.Password, "SHA512", user.Password))
|
||||
{
|
||||
ModelState.AddModelError("Authorization error", "Invaild e-mail or password");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
Claim claim = new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email);
|
||||
|
||||
ClaimsIdentity id = new ClaimsIdentity(new Claim[] { claim }, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id));
|
||||
|
||||
return RedirectToAction("Index", "Admin");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return RedirectToAction("Login", "Admin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyWebsite.Controllers
|
||||
@@ -13,7 +12,7 @@ namespace MyWebsite.Controllers
|
||||
public IActionResult Index() =>
|
||||
View();
|
||||
|
||||
public async Task<IActionResult> Details(string id) =>
|
||||
public IActionResult Details(string id) =>
|
||||
View(Startup.Database.Gallery.FirstOrDefault(i => i.FileName == id));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Models;
|
||||
using System.Linq;
|
||||
|
||||
namespace MyWebsite.Controllers
|
||||
{
|
||||
@@ -18,9 +16,13 @@ namespace MyWebsite.Controllers
|
||||
public IActionResult Contacts() =>
|
||||
View();
|
||||
|
||||
[Route("Projects")]
|
||||
public IActionResult Projects() =>
|
||||
View();
|
||||
|
||||
[Route("Error")]
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error() =>
|
||||
View("Views/Shared/Error.cshtml", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MyWebsite.Models;
|
||||
using SelectPdf;
|
||||
|
||||
namespace MyWebsite.Controllers
|
||||
{
|
||||
public class ResumeController : Controller
|
||||
{
|
||||
public ResumeController(DatabaseContext context) =>
|
||||
Startup.Database = context;
|
||||
|
||||
public IActionResult Index() =>
|
||||
View(Startup.Database.Resume.Find("en-US"));
|
||||
|
||||
public IActionResult Print() =>
|
||||
View(Startup.Database.Resume.Find("en-US"));
|
||||
|
||||
public IActionResult Download()
|
||||
{
|
||||
HtmlToPdf converter = new HtmlToPdf();
|
||||
converter.Options.MarginTop = 25;
|
||||
converter.Options.MarginBottom = 25;
|
||||
converter.Options.MarginLeft = 25;
|
||||
converter.Options.MarginRight = 25;
|
||||
PdfDocument doc = converter.ConvertHtmlString(
|
||||
$@"<html style=""margin-top: -50px"">
|
||||
{Startup.Database.Resume.Find("en-US").Content}
|
||||
|
||||
<link rel=""stylesheet"" type=""text/css"" href=""{Request.Scheme}://{Request.Host}/css/Style.css"" />
|
||||
</html>");
|
||||
byte[] data = doc.Save();
|
||||
doc.Close();
|
||||
return File(data, "application/pdf", "[Michael Gordeev] CV.pdf");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MyWebsite.Helpers
|
||||
{
|
||||
public class Cryptography
|
||||
{
|
||||
public static string ComputeHash(string plainText, string hashAlgorithm, byte[] saltBytes)
|
||||
{
|
||||
// If salt is not specified, generate it.
|
||||
if (saltBytes == null)
|
||||
{
|
||||
// Define min and max salt sizes.
|
||||
int minSaltSize = 4;
|
||||
int maxSaltSize = 8;
|
||||
|
||||
// Generate a random number for the size of the salt.
|
||||
Random random = new Random();
|
||||
int saltSize = random.Next(minSaltSize, maxSaltSize);
|
||||
|
||||
// Allocate a byte array, which will hold the salt.
|
||||
saltBytes = new byte[saltSize];
|
||||
|
||||
// Initialize a random number generator.
|
||||
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||
|
||||
// Fill the salt with cryptographically strong byte values.
|
||||
rng.GetNonZeroBytes(saltBytes);
|
||||
}
|
||||
|
||||
// Convert plain text into a byte array.
|
||||
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
|
||||
// Allocate array, which will hold plain text and salt.
|
||||
byte[] plainTextWithSaltBytes =
|
||||
new byte[plainTextBytes.Length + saltBytes.Length];
|
||||
|
||||
// Copy plain text bytes into resulting array.
|
||||
for (int i = 0; i < plainTextBytes.Length; i++)
|
||||
plainTextWithSaltBytes[i] = plainTextBytes[i];
|
||||
|
||||
// Append salt bytes to the resulting array.
|
||||
for (int i = 0; i < saltBytes.Length; i++)
|
||||
plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
|
||||
|
||||
HashAlgorithm hash;
|
||||
|
||||
// Make sure hashing algorithm name is specified.
|
||||
if (hashAlgorithm == null)
|
||||
hashAlgorithm = "";
|
||||
|
||||
// Initialize appropriate hashing algorithm class.
|
||||
switch (hashAlgorithm.ToUpper())
|
||||
{
|
||||
|
||||
case "SHA384":
|
||||
hash = new SHA384Managed();
|
||||
break;
|
||||
|
||||
case "SHA512":
|
||||
hash = new SHA512Managed();
|
||||
break;
|
||||
|
||||
default:
|
||||
hash = new MD5CryptoServiceProvider();
|
||||
break;
|
||||
}
|
||||
|
||||
// Compute hash value of our plain text with appended salt.
|
||||
byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
|
||||
|
||||
// Create array which will hold hash and original salt bytes.
|
||||
byte[] hashWithSaltBytes = new byte[hashBytes.Length +
|
||||
saltBytes.Length];
|
||||
|
||||
// Copy hash bytes into resulting array.
|
||||
for (int i = 0; i < hashBytes.Length; i++)
|
||||
hashWithSaltBytes[i] = hashBytes[i];
|
||||
|
||||
// Append salt bytes to the result.
|
||||
for (int i = 0; i < saltBytes.Length; i++)
|
||||
hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];
|
||||
|
||||
// Convert result into a base64-encoded string.
|
||||
string hashValue = Convert.ToBase64String(hashWithSaltBytes);
|
||||
|
||||
// Return the result.
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
public static bool VerifyHash(string plainText, string hashAlgorithm, string hashValue)
|
||||
{
|
||||
|
||||
// Convert base64-encoded hash value into a byte array.
|
||||
byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);
|
||||
|
||||
// We must know size of hash (without salt).
|
||||
int hashSizeInBits, hashSizeInBytes;
|
||||
|
||||
// Make sure that hashing algorithm name is specified.
|
||||
if (hashAlgorithm == null)
|
||||
hashAlgorithm = "";
|
||||
|
||||
// Size of hash is based on the specified algorithm.
|
||||
switch (hashAlgorithm.ToUpper())
|
||||
{
|
||||
|
||||
case "SHA384":
|
||||
hashSizeInBits = 384;
|
||||
break;
|
||||
|
||||
case "SHA512":
|
||||
hashSizeInBits = 512;
|
||||
break;
|
||||
|
||||
default: // Must be MD5
|
||||
hashSizeInBits = 128;
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert size of hash from bits to bytes.
|
||||
hashSizeInBytes = hashSizeInBits / 8;
|
||||
|
||||
// Make sure that the specified hash value is long enough.
|
||||
if (hashWithSaltBytes.Length < hashSizeInBytes)
|
||||
return false;
|
||||
|
||||
// Allocate array to hold original salt bytes retrieved from hash.
|
||||
byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes];
|
||||
|
||||
// Copy salt from the end of the hash to the new array.
|
||||
for (int i = 0; i < saltBytes.Length; i++)
|
||||
saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];
|
||||
|
||||
// Compute a new hash string.
|
||||
string expectedHashString = ComputeHash(plainText, hashAlgorithm, saltBytes);
|
||||
|
||||
// If the computed hash matches the specified hash,
|
||||
// the plain text value must be correct.
|
||||
return (hashValue == expectedHashString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class Badge
|
||||
{
|
||||
[Key]
|
||||
[Column(TypeName = "varchar(10)")]
|
||||
public string Name { get; set; }
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
[Column(TypeName = "varchar(20)")]
|
||||
[Required]
|
||||
public string Image { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ namespace MyWebsite.Models
|
||||
public DbSet<Link> Links { get; set; }
|
||||
public DbSet<Image> Gallery { get; set; }
|
||||
public DbSet<Project> Projects { get; set; }
|
||||
public DbSet<LoginModel> Users { get; set; }
|
||||
public DbSet<Badge> Badges { get; set; }
|
||||
public DbSet<Resume> Resume { get; set; }
|
||||
|
||||
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
|
||||
{
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class Image
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
public string Title { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string Description { get; set; }
|
||||
[Required]
|
||||
public DateTime CreationDate { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(20)")]
|
||||
public string FileName { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(10)")]
|
||||
public string Language { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class Link
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(20)")]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(20)")]
|
||||
public string Title { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string Username { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(255)")]
|
||||
public string Url { get; set; }
|
||||
public bool CanContactMe { get; set; }
|
||||
[Required]
|
||||
public bool CanContactMe { get; set; } = false;
|
||||
[Required]
|
||||
public bool DisplayInFooter { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class LoginModel
|
||||
{
|
||||
[Key]
|
||||
[Required(ErrorMessage = "This field is required")]
|
||||
[DataType(DataType.EmailAddress)]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "This field is required")]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class MetricsPackage
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Content { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,29 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class Project
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Column(TypeName = "text")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string ImageName { get; set; }
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string Link { get; set; }
|
||||
[Column(TypeName = "varchar(50)")]
|
||||
public string LinkCaption { get; set; }
|
||||
[Column(TypeName = "varchar(100)")]
|
||||
public string Badges { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MyWebsite.Models
|
||||
{
|
||||
public class Resume
|
||||
{
|
||||
[Key]
|
||||
[Column(TypeName = "varchar(10)")]
|
||||
public string Language { get; set; }
|
||||
[Required]
|
||||
[Column(TypeName = "text")]
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,21 @@
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Areas\Admin\NewFile.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
|
||||
<PackageReference Include="Select.HtmlToPdf.NetCore" Version="19.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Areas\Admin\Views\Shared\" />
|
||||
<Folder Include="wwwroot\projects-assets\FoxTube\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<WebStackScaffolding_IsLayoutPageSelected>True</WebStackScaffolding_IsLayoutPageSelected>
|
||||
<WebStackScaffolding_IsPartialViewSelected>False</WebStackScaffolding_IsPartialViewSelected>
|
||||
<WebStackScaffolding_IsReferencingScriptLibrariesSelected>False</WebStackScaffolding_IsReferencingScriptLibrariesSelected>
|
||||
<WebStackScaffolding_LayoutPageFile />
|
||||
<WebStackScaffolding_LayoutPageFile>
|
||||
</WebStackScaffolding_LayoutPageFile>
|
||||
<WebStackScaffolding_IsAsyncSelected>False</WebStackScaffolding_IsAsyncSelected>
|
||||
<WebStackScaffolding_ViewDialogWidth>600</WebStackScaffolding_ViewDialogWidth>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -5,6 +5,9 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MyWebsite.Models;
|
||||
using MyWebsite.Areas.API.Models;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace MyWebsite
|
||||
{
|
||||
@@ -22,9 +25,14 @@ namespace MyWebsite
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
string connection = Configuration.GetConnectionString("DefaultConnection");
|
||||
services.AddDbContext<DatabaseContext>(options =>
|
||||
options.UseSqlServer(connection));
|
||||
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
services.AddDbContext<FoxTubeDatabaseContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("FoxTubeAPI")));
|
||||
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
|
||||
options.LoginPath = new PathString("/Admin/Login"));
|
||||
|
||||
services.AddControllersWithViews();
|
||||
}
|
||||
@@ -45,6 +53,7 @@ namespace MyWebsite
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
@@ -53,6 +62,10 @@ namespace MyWebsite
|
||||
name: "projects",
|
||||
areaName: "Projects",
|
||||
pattern: "Projects/{controller=Projects}/{action=Index}/{id?}");
|
||||
endpoints.MapAreaControllerRoute(
|
||||
name: "admin",
|
||||
areaName: "Admin",
|
||||
pattern: "Admin/{controller}/{action=Index}/{id?}");
|
||||
endpoints.MapAreaControllerRoute(
|
||||
name: "api",
|
||||
areaName: "API",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Admin panel";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>Administration</h1>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<p class="admin-menu">
|
||||
<a asp-action="Artworks" class="comment">// Artworks</a><br />
|
||||
<a asp-action="Projects" class="comment">// Projects</a><br />
|
||||
<a asp-action="Badges" class="comment">// Badges</a><br />
|
||||
<a asp-action="Resume" class="comment">// Resume</a><br />
|
||||
<a asp-action="Contacts" class="comment">// Contact links</a><br />
|
||||
</p>
|
||||
<p>
|
||||
<a asp-action="Logout" class="logout"> Logout</a>
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="~/css/Admin.css" />
|
||||
@@ -0,0 +1,27 @@
|
||||
@model LoginModel;
|
||||
@{
|
||||
ViewData["Title"] = "Login";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>Administration panel - Login</h1>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
<form asp-action="Login" asp-antiforgery="true">
|
||||
<div asp-validation-summary="All" class="validation-error"></div>
|
||||
<div>
|
||||
<label asp-for="Email">E-mail</label>
|
||||
<input type="email" asp-for="Email" />
|
||||
<span asp-validation-for="Email"></span>
|
||||
</div>
|
||||
<div>
|
||||
<label asp-for="Password">Password</label>
|
||||
<input type="password" asp-for="Password" />
|
||||
<span asp-validation-for="Password"></span>
|
||||
</div>
|
||||
<input style="margin-top:10px" type="submit" value="Login" />
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="~/css/Admin.css" />
|
||||
@@ -1,4 +1,4 @@
|
||||
@model Image;
|
||||
@model Image
|
||||
@{
|
||||
ViewData["Title"] = Model.Title;
|
||||
}
|
||||
@@ -13,7 +13,9 @@
|
||||
<div>
|
||||
<h1>@Model.Title</h1>
|
||||
<span>Creation date: @Model.CreationDate.ToShortDateString()</span>
|
||||
<p>@Model.Description</p>
|
||||
<p>
|
||||
@Html.Raw(Model.Description)
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<h1>My arts</h1>
|
||||
</header>
|
||||
|
||||
<article class="info-block gallery">
|
||||
<article class="gallery">
|
||||
@if (Startup.Database.Gallery.Count() > 0)
|
||||
foreach (Image image in Startup.Database.Gallery)
|
||||
foreach (Image image in Startup.Database.Gallery.OrderByDescending(i => i.CreationDate))
|
||||
{
|
||||
<a asp-action="Details" asp-route-id="@image.FileName"><img title="@image.Title" src="~/images/Gallery/@image.FileName" /></a>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@model Resume
|
||||
@{
|
||||
ViewData["Title"] = "My resume";
|
||||
}
|
||||
|
||||
<header>
|
||||
<h1>My resume</h1>
|
||||
|
||||
<a class="comment" asp-action="Download">// Download CV (.pdf) </a><br />
|
||||
<a class="comment" asp-action="Print">// Print CV </a>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
@Html.Raw(Model.Content)
|
||||
</article>
|
||||
|
||||
<partial name="~/Views/Shared/ContactsBlock.cshtml" />
|
||||
@@ -0,0 +1,23 @@
|
||||
@model Resume
|
||||
@{
|
||||
Layout = null;
|
||||
ViewData["Title"] = "Print";
|
||||
}
|
||||
|
||||
@Html.Raw(Model.Content)
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="~/css/Style.css" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.print();
|
||||
window.onfocus = function ()
|
||||
{
|
||||
window.location = "/Resume";
|
||||
}
|
||||
</script>
|
||||
+2
-2
@@ -8,13 +8,13 @@
|
||||
|
||||
<article>
|
||||
<p>
|
||||
@foreach (Link link in Startup.Database.Links.Where(i => i.CanContactMe))
|
||||
@foreach (Link link in Startup.Database.Links.Where(i => i.CanContactMe).OrderBy(i => i.Id))
|
||||
{
|
||||
<a class="socicon-@(link.Name)"></a> @(link.Title) <a href="@(link.Url)" target="_blank">@(link.Username)</a><br />
|
||||
}
|
||||
</p>
|
||||
<p>
|
||||
@foreach (Link link in Startup.Database.Links.Where(i => !i.CanContactMe))
|
||||
@foreach (Link link in Startup.Database.Links.Where(i => !i.CanContactMe).OrderBy(i => i.Id))
|
||||
{
|
||||
<a class="socicon-@(link.Name)"></a> @(link.Title) <a href="@(link.Url)" target="_blank">@(link.Username)</a><br />
|
||||
}
|
||||
@@ -3,23 +3,16 @@
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
<header>
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
</header>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
<article>
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
</article>
|
||||
@@ -0,0 +1,35 @@
|
||||
@{
|
||||
ViewData["Title"] = "My projects";
|
||||
List<Badge> badges = Startup.Database.Badges.ToList();
|
||||
}
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<h1>My projects</h1>
|
||||
<h3>Here is presented the most of projects I worked on</h3>
|
||||
</div>
|
||||
<iframe src="//githubbadge.appspot.com/xfox111" class="github-stats" frameborder="0"></iframe>
|
||||
</header>
|
||||
|
||||
<article>
|
||||
@foreach (Project project in Startup.Database.Projects)
|
||||
{
|
||||
<div class="project-item">
|
||||
<div>
|
||||
<h1>@project.Title</h1>
|
||||
<p class="description">
|
||||
@Html.Raw(project.Description)
|
||||
</p>
|
||||
<a href="@(project.Link)" target="_blank">@project.LinkCaption</a>
|
||||
</div>
|
||||
<div>
|
||||
@foreach (string badge in project.Badges.Split(','))
|
||||
{
|
||||
<div class="badge" style="background-image: url(../images/Badges/@(badges.FirstOrDefault(i => i.Name == badge).Image).png)" title="@(badges.FirstOrDefault(i => i.Name == badge).Description)"></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="~/css/Projects.css" />
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<menu type="toolbar" class="main-menu" style="display:none">
|
||||
<li><a asp-controller="About" asp-action="Index">AboutMe();</a></li>
|
||||
<li><a asp-controller="CV" asp-action="Index">CV();</a></li>
|
||||
<li><a asp-controller="Resume" asp-action="Index">CV();</a></li>
|
||||
<li><a asp-controller="Projects" asp-action="Index">Projects();</a></li>
|
||||
<li><a asp-controller="Gallery" asp-action="Index">Arts();</a></li>
|
||||
<li><a asp-controller="Contacts" asp-action="Index">Contacts();</a></li>
|
||||
@@ -44,9 +44,9 @@
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<span class="comment">// Copyright ©@(DateTime.Today.Year) <b>Michael "XFox" Gordeev</b></span>
|
||||
<span class="comment">// Copyright ©@(DateTime.Today.Year) Michael "XFox" Gordeev</span>
|
||||
|
||||
@foreach (Link link in from i in Startup.Database.Links where new string[] { "outlook", "linkedin", "github" }.Contains(i.Name) orderby i.Name descending select i)
|
||||
@foreach (Link link in Startup.Database.Links.Where(i => i.DisplayInFooter).OrderBy(i => i.Id))
|
||||
{
|
||||
<a class="socicon-@(link.Name)" href="@(link.Url)" target="_blank" title="@(link.Title)"></a>
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
@@ -8,6 +8,7 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=xfox111;Trusted_Connection=True;"
|
||||
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=xfox111;Trusted_Connection=True;",
|
||||
"FoxTubeAPI": "Server=(localdb)\\mssqllocaldb;Database=foxtube;Trusted_Connection=True;"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
border: 1px solid black;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -55,6 +56,26 @@ form {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.admin-menu a {
|
||||
font-size: 16pt;
|
||||
}
|
||||
|
||||
.logout, .logout:link, .logout:link:visited {
|
||||
font-size: 16pt;
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.validation-summary-errors {
|
||||
color: red;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
form {
|
||||
max-width: initial;
|
||||
|
||||
@@ -34,16 +34,6 @@ article {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.csharp { background-image: url(../images/Badges/csharp.png); }
|
||||
.dotnet { background-image: url("../images/Badges/dotnet.png"); }
|
||||
.xamarin { background-image: url("../images/Badges/xamarin.png"); }
|
||||
.unity { background-image: url("../images/Badges/unity.png"); }
|
||||
.android { background-image: url("../images/Badges/android.png"); }
|
||||
.uwp { background-image: url("../images/Badges/windows.png"); }
|
||||
.win32 { background-image: url("../images/Badges/windows.png"); }
|
||||
.windows { background-image: url("../images/Badges/windows.png"); }
|
||||
.ios { background-image: url("../images/Badges/ios.png"); }
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.github-stats {
|
||||
grid-row: 2;
|
||||
|
||||
@@ -72,10 +72,15 @@ footer a:hover {
|
||||
}
|
||||
|
||||
/*Body styles*/
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
margin: 0px;
|
||||
margin-top: 53px;
|
||||
font-family: Consolas, 'Segoe MDL2 Assets';
|
||||
font-family: 'Consolas', 'Segoe MDL2 Assets';
|
||||
|
||||
/*This stuff is necessary for sticky footer*/
|
||||
display: grid;
|
||||
@@ -100,7 +105,7 @@ article a:visited, article a:link {
|
||||
}
|
||||
|
||||
.comment, .comment:visited {
|
||||
color: #57a64a;
|
||||
color: #57a64a !important;
|
||||
}
|
||||
|
||||
/*Adaptive code*/
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Reference in New Issue
Block a user