public release 1.0.0.17

This commit is contained in:
STAM 2024-09-05 13:33:51 +03:00
parent d2f0294465
commit 8ebaca9759
No known key found for this signature in database
GPG Key ID: 711526C6938897F1
2328 changed files with 433464 additions and 0 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src-temp
COPY ["./src/epicmorg.jira.issue.web.reporter", "jwr/"]
RUN dotnet restore "jwr/epicmorg.jira.issue.web.reporter.csproj"
COPY . .
WORKDIR "/src-temp/jwr"
RUN cat ./epicmorg.jira.issue.web.reporter.csproj && \
dotnet build "./epicmorg.jira.issue.web.reporter.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "./epicmorg.jira.issue.web.reporter.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "epicmorg.jira.issue.web.reporter.dll"]
EXPOSE 5000
EXPOSE 80

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
all: jwr
jwr:
docker build --compress -t epicmorg/jira-issue-web-reporter .
docker push epicmorg/jira-issue-web-reporter

27
docker-compose.yml Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29509.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "epicmorg.jira.issue.web.reporter", "epicmorg.jira.issue.web.reporter\epicmorg.jira.issue.web.reporter.csproj", "{7C460FFD-D311-47F3-90CC-C42996BBB2DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7C460FFD-D311-47F3-90CC-C42996BBB2DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C460FFD-D311-47F3-90CC-C42996BBB2DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C460FFD-D311-47F3-90CC-C42996BBB2DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C460FFD-D311-47F3-90CC-C42996BBB2DE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {181F5AB3-A6E9-4ABC-AFDB-7E060CEDC759}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,147 @@
namespace epicmorg.jira.issue.web.reporter.Controllers
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Captcha;
using epicmorg.jira.issue.web.reporter.Models.Check.Request;
using epicmorg.jira.issue.web.reporter.Models.Configuration;
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
using epicmorg.jira.issue.web.reporter.Models.Create.ViewModels;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public class HomeController : Controller
{
private readonly ILogger<HomeController> logger;
private readonly UiConfig uiOptions;
private readonly ICaptchaValidator captchaValidator;
private readonly IJiraService jiraService;
private readonly JiraConfig jiraConfig;
public HomeController(
ILogger<HomeController> logger,
IOptions<UiConfig> uiOptions,
IOptions<JiraConfig> jiraConfig,
ICaptchaValidator captchaValidator,
IJiraService jiraService)
{
this.logger = logger;
this.uiOptions = uiOptions.Value;
this.jiraConfig = jiraConfig.Value;
this.captchaValidator = captchaValidator;
this.jiraService = jiraService;
}
public async Task<IActionResult> Index() => this.View();
public IActionResult Privacy() => this.View();
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() => this.View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? this.HttpContext.TraceIdentifier });
[HttpGet]
public async Task<IActionResult> Create(CancellationToken cancellationToken) => this.View(await this.BuildCreateModel(cancellationToken).ConfigureAwait(false));
[HttpGet]
[Route("Check/{id?}")]
public async Task<IActionResult> Check(string id = null) => this.View(new CheckRequest { Issue = id});
[HttpPost]
[ValidateAntiForgeryToken]
[Route("Check")]
public async Task<IActionResult> Check(CheckRequest model, CancellationToken cancellationToken)
{
var valid = await this.IsModelAndCaptchaValid(model, cancellationToken).ConfigureAwait(false);
if (!valid)
{
return this.View(model);
}
var result = await this.jiraService.GetIssueStatus(model.Issue, cancellationToken).ConfigureAwait(false);
return this.View("CheckResult", result);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateWebRequest model, CancellationToken cancellationToken)
{
var valid = await this.IsModelAndCaptchaValid(model, cancellationToken).ConfigureAwait(false);
if (!valid)
{
var viewModel = await this.BuildCreateModel(cancellationToken, new CreateViewModel(model)).ConfigureAwait(false);
return this.View(nameof(this.Create), viewModel);
}
var request = await this.BuildJiraRequest(model).ConfigureAwait(false);
var result = await this.jiraService.CreateIssue(request, cancellationToken).ConfigureAwait(false);
return this.View("Created", result);
}
private async Task<bool> IsModelAndCaptchaValid(ICaptchaRequest model, CancellationToken cancellationToken)
{
var valid = this.ModelState.IsValid;
if (valid)
{
if (!await this.captchaValidator.Validate(model.Captcha, cancellationToken).ConfigureAwait(false))
{
this.logger.LogInformation($"User has failed captcha");
this.ModelState.AddModelError(null, "You failed the CAPTCHA");
valid = false;
}
}
return valid;
}
private async Task<CreateViewModel> BuildCreateModel(CancellationToken cancellationToken, CreateViewModel model = null)
{
model ??= new CreateViewModel();
model.IssueTypes = new SelectList(
items: this.jiraConfig.AllowedIssueTypes,
dataValueField: null,
dataTextField: null,
selectedValue: null);
model.Projects = new SelectList(
items: await this.jiraService.GetProjects(cancellationToken).ConfigureAwait(false),
dataValueField: nameof(ProjectDto.Key),
dataTextField: nameof(ProjectDto.Name),
selectedValue: null);
return model;
}
private async Task<CreateJiraRequest> BuildJiraRequest(CreateWebRequest model)
{
var request = new CreateJiraRequest(model);
var lst = new List<JiraFile>();
foreach (var file in model.Files ?? Enumerable.Empty<IFormFile>())
{
using var stream = file.OpenReadStream();
using var ms = new MemoryStream();
await stream.CopyToAsync(ms).ConfigureAwait(false);
lst.Add(new JiraFile
{
Name = file.FileName,
Content = ms.ToArray(),
});
}
request.Files = lst;
return request;
}
}
}

View File

@ -0,0 +1,12 @@
namespace epicmorg.jira.issue.web.reporter.Models.Captcha
{
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
public interface ICaptchaRequest
{
[Required]
[BindProperty(Name = "g-recaptcha-response")]
public string Captcha { get; set; }
}
}

View File

@ -0,0 +1,16 @@
namespace epicmorg.jira.issue.web.reporter.Models.Check.Request
{
using System.ComponentModel.DataAnnotations;
using epicmorg.jira.issue.web.reporter.Models.Captcha;
using Microsoft.AspNetCore.Mvc;
public class CheckRequest : ICaptchaRequest
{
[Required]
public string Issue { get; set; }
[Required]
[BindProperty(Name = "g-recaptcha-response")]
public string Captcha { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace epicmorg.jira.issue.web.reporter.Models.Check.Response
{
public class CheckResponse
{
public string Issue { get; set; }
public bool Success { get; set; }
public string Resolution { get; set; }
public string Status { get; set; }
public string Message { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace epicmorg.jira.issue.web.reporter.Models.Configuration
{
public class CaptchaConfig
{
public string Key { get; set; }
public string Secret { get; set; }
}
}

View File

@ -0,0 +1,23 @@
namespace epicmorg.jira.issue.web.reporter.Models.Configuration
{
using System.ComponentModel.DataAnnotations;
public class JiraConfig
{
[Url]
public string Domain { get; set; }
[Required]
public string Login { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string[] AllowedProjects { get; set; }
[Required]
public string[] AllowedIssueTypes { get; set; }
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace epicmorg.jira.issue.web.reporter.Models.Configuration
{
using System.ComponentModel.DataAnnotations;
public class UiConfig
{
[Url]
public string LogoUrl { get; set; }
[Required]
public string HeaderText { get; set; }
[Required]
public string DescriptionText { get; set; }
[Required]
public string LicensedTo { get; set; }
[Required]
public string Theme { get; set; }
public string GetAssemblyVersion() { return GetType().Assembly.GetName().Version.ToString(); }
public string GetAssemblyProduct {
get {
var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);
return attributes.Length == 0 ? "" : ((AssemblyProductAttribute)attributes[0]).Product.ToString();
}
}
}
}

View File

@ -0,0 +1,9 @@
namespace epicmorg.jira.issue.web.reporter.Models
{
public class JiraFile
{
public string Name { get; set; }
public byte[] Content { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace epicmorg.jira.issue.web.reporter.Models
{
public class ProjectDto
{
public string Name { get; set; }
public string Key { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace epicmorg.jira.issue.web.reporter.Models.Create.Request
{
using System.Collections.Generic;
public class CreateJiraRequest : CreateRequestBase
{
public CreateJiraRequest() { }
public CreateJiraRequest(CreateRequestBase source)
: base(source) { }
public IEnumerable<JiraFile> Files { get; set; }
}
}

View File

@ -0,0 +1,34 @@
namespace epicmorg.jira.issue.web.reporter.Models.Create.Request
{
using System.ComponentModel.DataAnnotations;
public class CreateRequestBase
{
public CreateRequestBase() { }
public CreateRequestBase(CreateRequestBase source)
{
this.Description = source.Description;
this.Email = source.Email;
this.IssueType = source.IssueType;
this.Name = source.Name;
this.Project = source.Project;
}
[Required]
public string Project { get; set; }
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string IssueType { get; set; }
[Required]
public string Description { get; set; }
}
}

View File

@ -0,0 +1,26 @@
namespace epicmorg.jira.issue.web.reporter.Models.Create.Request
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using epicmorg.jira.issue.web.reporter.Models.Captcha;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
public class CreateWebRequest : CreateRequestBase, ICaptchaRequest
{
public CreateWebRequest() { }
public CreateWebRequest(CreateWebRequest source)
: base(source)
{
this.Files = source.Files;
this.Captcha = source.Captcha;
}
[Required]
[BindProperty(Name = "g-recaptcha-response")]
public string Captcha { get; set; }
public IEnumerable<IFormFile> Files { get; set; }
}
}

View File

@ -0,0 +1,11 @@
namespace epicmorg.jira.issue.web.reporter.Models.Create.Response
{
public class CreateResponse
{
public bool Success { get; set; }
public string IssueKey { get; set; }
public string Message { get; set; }
}
}

View File

@ -0,0 +1,17 @@
namespace epicmorg.jira.issue.web.reporter.Models.Create.ViewModels
{
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
using Microsoft.AspNetCore.Mvc.Rendering;
public class CreateViewModel : CreateWebRequest
{
public CreateViewModel() { }
public CreateViewModel(CreateWebRequest source)
: base(source) { }
public SelectList Projects { get; set; }
public SelectList IssueTypes { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace epicmorg.jira.issue.web.reporter.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(this.RequestId);
}
}

View File

@ -0,0 +1,14 @@
namespace epicmorg.jira.issue.web.reporter
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
public static class Program
{
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}
}

View File

@ -0,0 +1,28 @@
namespace epicmorg.jira.issue.web.reporter.Services.DI
{
using Atlassian.Jira;
using epicmorg.jira.issue.web.reporter.Models.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
public static class AtlassianExtensions
{
public static IServiceCollection AddJira(this IServiceCollection services)
{
services.AddScoped<Jira>(context =>
{
var jiraOptions = context.GetService<IOptions<JiraConfig>>().Value;
var client = Jira.CreateRestClient(
jiraOptions.Domain,
jiraOptions.Login,
jiraOptions.Password,
new JiraRestClientSettings
{
Cache = new Atlassian.Jira.JiraCache(),
});
return client;
});
return services;
}
}
}

View File

@ -0,0 +1,66 @@
namespace epicmorg.jira.issue.web.reporter.Services.Impl
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models.Configuration;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
public class GoogleRecaptchaValidator : ICaptchaValidator
{
private const string ApiRoot = "https://www.google.com/recaptcha/api/siteverify";
private readonly HttpClient httpClient;
private readonly string secret;
private readonly ILogger<GoogleRecaptchaValidator> logger;
public GoogleRecaptchaValidator(
HttpClient httpClient,
IOptions<CaptchaConfig> captchaOptions,
ILogger<GoogleRecaptchaValidator> logger)
{
this.httpClient = httpClient;
this.secret = captchaOptions.Value.Secret;
this.logger = logger;
}
public async Task<bool> Validate(string response, CancellationToken cancellationToken)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("secret", this.secret),
new KeyValuePair<string, string>("response", response),
});
try
{
var res = await this.httpClient.PostAsync(ApiRoot, content, cancellationToken).ConfigureAwait(false);
if (res.StatusCode != HttpStatusCode.OK)
{
return false;
}
string jsonResult = await res.Content.ReadAsStringAsync().ConfigureAwait(false);
dynamic jsonData = JObject.Parse(jsonResult);
var success = jsonData.success == "true";
if (!success)
{
this.logger.LogInformation("Failed to pass captcha");
}
return success;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed validate captcha for respose {response}", response);
throw;
}
}
}
}

View File

@ -0,0 +1,28 @@
namespace epicmorg.jira.issue.web.reporter.Services.Impl
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.Extensions.Caching.Distributed;
public class JiraCache : JsonCache, IJiraCache
{
private const string projectsKey = "JiraProjectsKey";
public JiraCache(IDistributedCache cache)
: base(cache) { }
public Task<IReadOnlyList<ProjectDto>> GetProjects() => this.GetCachedValue<IReadOnlyList<ProjectDto>>(projectsKey);
public Task SetProjects(IReadOnlyList<ProjectDto> value) => this.SetCachedValue(projectsKey, value, TimeSpan.FromMinutes(1));
public Task<CheckResponse> GetIssueStatus(string issue) => this.GetCachedValue<CheckResponse>(this.GetIssueKey(issue));
public Task SetIssue(string issue, CheckResponse value) => this.SetCachedValue(this.GetIssueKey(issue), value, TimeSpan.FromMinutes(1));
private string GetIssueKey(string issue) => $"JiraIssue_{issue}";
}
}

View File

@ -0,0 +1,82 @@
namespace epicmorg.jira.issue.web.reporter.Services.Impl
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Atlassian.Jira;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.Extensions.Logging;
public class JiraClient : IJiraClient
{
private readonly ILogger<JiraClient> logger;
private readonly Jira jira;
public JiraClient(
ILogger<JiraClient> logger,
Jira jira)
{
this.logger = logger;
this.jira = jira;
}
public async Task<CheckResponse> GetIssueStatus(string issueId, CancellationToken cancellationToken)
{
var issue = await this.jira.Issues.GetIssueAsync(issueId, cancellationToken).ConfigureAwait(false);
var result = new CheckResponse
{
Resolution = issue.Resolution?.Name,
Status = issue.Status.Name,
Success = true,
};
return result;
}
public async Task<string> CreateIssue(CreateJiraRequest request, CancellationToken cancellationToken)
{
try
{
var issue = this.jira.CreateIssue(request.Project);
issue.Summary = $"[Bot] {request.Description[0..(Math.Min(50, request.Description.Length) - 1)]}...";
issue.Description = request.Description;
issue.Type = request.IssueType;
issue["Reporter name"] = request.Name;
issue["Reporter email"] = request.Email;
var result = await issue.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
if (request.Files?.Any() ?? false)
{
await issue.AddAttachmentAsync(
request.Files.Select(file => new UploadAttachmentInfo(file.Name, file.Content)).ToArray(),
cancellationToken)
.ConfigureAwait(false);
}
this.logger.LogInformation("Created issue {issue} for request {request}", issue, request);
return result.Key.Value;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to create issue for requst {request}", request);
throw;
}
}
public async Task<IReadOnlyList<ProjectDto>> GetProjects(CancellationToken cancellationToken)
{
var projects = await this.jira.Projects.GetProjectsAsync(cancellationToken).ConfigureAwait(false);
var result = projects.Select(a => new ProjectDto
{
Key = a.Key,
Name = a.Name,
}).ToArray();
return result;
}
}
}

View File

@ -0,0 +1,139 @@
namespace epicmorg.jira.issue.web.reporter.Services.Impl
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
using epicmorg.jira.issue.web.reporter.Models.Configuration;
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
using epicmorg.jira.issue.web.reporter.Models.Create.Response;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public class JiraService : IJiraService
{
private readonly HashSet<string> allowedProjects;
private readonly IJiraClient client;
private readonly ILogger<JiraService> logger;
private readonly IJiraCache cache;
public JiraService(
ILogger<JiraService> logger,
IJiraClient client,
IOptions<JiraConfig> options,
IJiraCache cache)
{
this.allowedProjects = new HashSet<string>(options.Value.AllowedProjects, StringComparer.OrdinalIgnoreCase);
this.client = client;
this.logger = logger;
this.cache = cache;
}
public async Task<CreateResponse> CreateIssue(CreateJiraRequest request, CancellationToken cancellationToken)
{
if (!this.IsAllowedProject(request.Project))
{
this.logger.LogWarning("Tried to access disabled project {project} when creating issue {issue}", request.Project, request);
return new CreateResponse
{
IssueKey = null,
Message = "Invalid project",
Success = false,
};
}
try
{
var result = await this.client.CreateIssue(request, cancellationToken).ConfigureAwait(false);
return new CreateResponse
{
Message = "Issue created!",
IssueKey = result,
Success = true,
};
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to create jira issue: {request}", request);
return new CreateResponse
{
IssueKey = null,
Message = "Failed to create an issue",
Success = false,
};
}
}
public async Task<IReadOnlyList<ProjectDto>> GetProjects(CancellationToken cancellationToken)
{
try
{
var cached = await this.cache.GetProjects().ConfigureAwait(false);
if (cached != null)
{
return cached;
}
var result = await this.client.GetProjects(cancellationToken).ConfigureAwait(false);
result = result.Where(a => this.IsAllowedProject(a.Key)).ToList();
await this.cache.SetProjects(result).ConfigureAwait(false);
return result;
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to get projects for the desk form");
throw;
}
}
public async Task<CheckResponse> GetIssueStatus(string issue, CancellationToken cancellationToken)
{
var project = issue.Split('-').First();
if (!this.IsAllowedProject(project))
{
this.logger.LogWarning("Tried to access disabled project {project} for issue {issue}", project, issue);
return new CheckResponse
{
Resolution = "No such issue",
Status = "Invalid project",
Success = false,
Issue = issue,
};
}
CheckResponse result;
try
{
var cached = await this.cache.GetIssueStatus(issue).ConfigureAwait(false);
var status = await this.client.GetIssueStatus(issue, cancellationToken).ConfigureAwait(false);
result = new CheckResponse
{
Resolution = status.Resolution,
Status = status.Status,
Success = true,
};
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to check status for issue {issue}", issue);
result = new CheckResponse
{
Resolution = null,
Status = null,
Success = false,
};
}
await this.cache.SetIssue(issue, result).ConfigureAwait(false);
return result;
}
private bool IsAllowedProject(string key) => this.allowedProjects.Contains(key);
}
}

View File

@ -0,0 +1,37 @@
namespace epicmorg.jira.issue.web.reporter.Services.Impl
{
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
public abstract class JsonCache
{
private readonly IDistributedCache cache;
protected JsonCache(IDistributedCache cache)
{
this.cache = cache;
}
protected async Task<T> GetCachedValue<T>(string key)
{
var cache = await this.cache.GetStringAsync(key).ConfigureAwait(false);
if (cache == null)
{
return default;
}
return JsonSerializer.Deserialize<T>(cache);
}
protected Task SetCachedValue<T>(string key, T value, TimeSpan lifetime) =>
this.cache.SetStringAsync(
key,
JsonSerializer.Serialize<T>(value),
new DistributedCacheEntryOptions
{
SlidingExpiration = lifetime,
});
}
}

View File

@ -0,0 +1,10 @@
namespace epicmorg.jira.issue.web.reporter.Services.Interfaces
{
using System.Threading;
using System.Threading.Tasks;
public interface ICaptchaValidator
{
Task<bool> Validate(string response, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,18 @@
namespace epicmorg.jira.issue.web.reporter.Services.Interfaces
{
using System.Collections.Generic;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
public interface IJiraCache
{
Task<IReadOnlyList<ProjectDto>> GetProjects();
Task SetProjects(IReadOnlyList<ProjectDto> value);
Task<CheckResponse> GetIssueStatus(string issue);
Task SetIssue(string issue, CheckResponse value);
}
}

View File

@ -0,0 +1,18 @@
namespace epicmorg.jira.issue.web.reporter.Services.Interfaces
{
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
public interface IJiraClient
{
Task<CheckResponse> GetIssueStatus(string issueId, CancellationToken cancellationToken);
Task<string> CreateIssue(CreateJiraRequest request, CancellationToken cancellationToken);
Task<IReadOnlyList<ProjectDto>> GetProjects(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,19 @@
namespace epicmorg.jira.issue.web.reporter.Services.Interfaces
{
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using epicmorg.jira.issue.web.reporter.Models;
using epicmorg.jira.issue.web.reporter.Models.Check.Response;
using epicmorg.jira.issue.web.reporter.Models.Create.Request;
using epicmorg.jira.issue.web.reporter.Models.Create.Response;
public interface IJiraService
{
Task<CheckResponse> GetIssueStatus(string issue, CancellationToken cancellationToken);
Task<IReadOnlyList<ProjectDto>> GetProjects(CancellationToken cancellationToken);
Task<CreateResponse> CreateIssue(CreateJiraRequest request, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,86 @@
namespace epicmorg.jira.issue.web.reporter
{
using epicmorg.jira.issue.web.reporter.Models.Configuration;
using epicmorg.jira.issue.web.reporter.Services.DI;
using epicmorg.jira.issue.web.reporter.Services.Impl;
using epicmorg.jira.issue.web.reporter.Services.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Startup
{
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
.AddControllersWithViews();
services
.AddMvc()
.AddMvcLocalization()
.AddDataAnnotationsLocalization();
services
.AddOptions()
.Configure<JiraConfig>(this.Configuration.GetSection("Jira"))
.Configure<UiConfig>(this.Configuration.GetSection("UI"))
.Configure<CaptchaConfig>(this.Configuration.GetSection("Captcha"));
services
.AddAntiforgery(o =>
{
o.SuppressXFrameOptionsHeader = true;
o.Cookie.SameSite = SameSiteMode.None;
})
.AddHttpClient()
.AddHttpClient<ICaptchaValidator, GoogleRecaptchaValidator>();
services
.AddJira()
//.AddScoped<IDistributedCache, MemoryDistributedCache>()
.AddScoped<IJiraCache, Services.Impl.JiraCache>()
.AddScoped<IJiraClient, JiraClient>()
.AddScoped<IJiraService, JiraService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

View File

@ -0,0 +1,30 @@
@inject IOptions<CaptchaConfig> captchaConfig
@model CheckRequest
<form asp-action="Check" id="form" asp-all-route-data="new Dictionary<string, string>()">
<div class="mb-3">
<div class="form-group">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Issue"><i class="fas fa-keyboard"></i> <span data-localize="label-enter-issue-key">Enter issue key</span>:</label>
<input type="text" asp-for="Issue" class="form-control" placeholder="ABC-123" value="" required>
<span asp-validation-for="Issue" class="text-danger"></span>
</div>
</div>
<div class="mb-3">
<div class="form-group">
<div class="g-recaptcha" data-sitekey="@captchaConfig.Value.Key" data-callback="recaptchaCallback"></div>
</div>
<span asp-validation-for="Captcha" class="text-danger"></span>
</div>
<div class="form-group">
<button class="btn btn-primary btn-lg btn-block" type="submit"><span data-localize="label-check">Check</span> <i class="fas fa-hand-pointer"></i></button>
</div>
</form>
@section Head {
<script src='https://www.google.com/recaptcha/api.js'></script>
}
@section Scripts{
@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@ -0,0 +1,47 @@
@model CheckResponse
<div class="mb-3">
<div class="form-group">
<label for="disabled-ui-text"><i class="fas fa-keyboard"></i> <span data-localize="label-issue-key">Issue key</span>:</label>
</div>
</div>
@if (!Model.Success) {
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong><i class="fas fa-exclamation-triangle"></i> <span data-localize="message-error">Error!</span></strong> <span data-localize="message-something-wrong-2">Something went wrong. Failed to create request.</span> @Model.Message
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
} else {
<div class="mb-3">
<div class="card">
<h5 class="card-header"><input asp-for="Issue" id="disabled-ui-text" class="form-control" readonly></h5>
<div class="card-body">
@if (Model.Status != null) {
<p class="card-text"><span data-localize="message-status">Status</span>: <span class="badge badge-info">@Model.Status</span></p>
}
@if (Model.Resolution != null) {
<p class="card-text"><span data-localize="message-resolution">Resolution</span>: <span class="text-muted">@Model.Resolution</span></p>
}
</div>
</div>
</div>
}
<div class="row">
<div class="col-sm-6 padding-fix">
<a class="btn btn-primary btn-lg btn-block" asp-action="Create">
<i class="fas fa-bug"></i> <span data-localize="label-submit-another">Submit Another Issue</span>
</a>
</div>
<div class="col-sm-6 padding-fix">
<a class="btn btn-primary btn-lg btn-block" asp-action="Check">
<i class="fas fa-search"></i> <span data-localize="label-check-another">Check Another Report</span>
</a>
</div>
</div>

View File

@ -0,0 +1,98 @@
@inject IOptions<CaptchaConfig> captchaConfig
@inject IOptions<UiConfig> uiConfig
@model CreateViewModel
<div class="row">
<div class="col-md-12 order-md-1">
<form asp-action="Create" enctype="multipart/form-data" id="form">
<div asp-validation-summary="ModelOnly" class="alert alert-danger alert-dismissible fade show" role="alert">
<strong><i class="fas fa-exclamation-triangle"></i> <span data-localize="message-error">Error!</span></strong> <span data-localize="message-something-wrong">Something went wrong.</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<div class="form-group">
<label asp-for="Name" class="control-label"><i class="fas fa-user"></i> <span data-localize="label-fn">First name</span>:</label>
<input asp-for="Name" type="text" class="form-control" id="firstName" placeholder="" value="" required>
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="form-group">
<label asp-for="Email" class="control-label"><i class="far fa-envelope"></i> Email:</label>
<input asp-for="Email" type="email" class="form-control" id="email" placeholder="you@example.com">
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
@if (Model.IssueTypes?.Count() > 1)
{
<div class="col-md-6 mb-3">
<div class="form-group">
<label asp-for="IssueType" class="control-label"><i class="fas fa-list"></i> <span data-localize="label-rep-type">Report type</span>:</label>
<select asp-for="IssueType" asp-items="@Model.IssueTypes" class="form-control custom-select d-block w-100" required>
<option disabled selected data-localize="label-select">--- SELECT ---</option>
</select>
<span asp-validation-for="IssueType" class="text-danger"></span>
</div>
</div>
}
else
{
<input type="hidden" asp-for="IssueType" value="@Model.IssueTypes.FirstOrDefault()?.Value" />
}
@if (Model.Projects?.Count() > 1)
{
<div class="col-md-6 mb-3">
<div class="form-group">
<label asp-for="Project" class="control-label"><i class="fas fa-list"></i> <span data-localize="label-project">Select project</span>:</label>
<select class="custom-select d-block w-100 form-control" asp-for="Project" asp-items="@Model.Projects" required>
<option disabled selected data-localize="label-select">--- SELECT ---</option>
</select>
<span asp-validation-for="Project" class="text-danger"></span>
</div>
</div>
}
else
{
<input type="hidden" asp-for="Project" value="@Model.Projects.FirstOrDefault()?.Value" />
}
</div>
<div class="mb-3">
<div class="form-group">
<label asp-for="Description" class="control-label"><i class="fas fa-keyboard"></i> <span data-localize="label-description">Your description</span>:</label>
<textarea asp-for="Description" class="form-control" rows="3"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<div class="form-group">
<label asp-for="Files" class="control-label"><i class="fas fa-file-upload"></i> <span data-localize="label-attach-file">Attach file</span>:</label>
<input asp-for="Files" typeclass="form-control" multiple type="file" class="form-control-file">
<span asp-validation-for="Files" class="text-danger"></span>
</div>
</div>
<div class="form-group col-md-6 mb-3">
<div class="g-recaptcha" data-sitekey="@captchaConfig.Value.Key"></div>
<span asp-validation-for="Captcha" class="text-danger"></span>
</div>
</div>
<button class="btn btn-primary btn-lg btn-block" type="submit"><span data-localize="label-submit">Submit report</span> <i class="fas fa-paper-plane"></i></button>
</form>
</div>
</div>
@section Head {
<script src='https://www.google.com/recaptcha/api.js'></script>
}
@section Scripts{
@{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@ -0,0 +1,39 @@
@model CreateResponse
@if (Model.Success)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<strong>
<i class="fas fa-hands-helping"></i> <span data-localize="message-awesome">Awesome!</span>
</strong>
<span data-localize="message-reported-ok">You successfully send your report. Your issue-key is</span>:
<strong>
<a asp-action="Check" asp-route-id="@Model.IssueKey">@Model.IssueKey</a>
</strong>.
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
}
else
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong><i class="fas fa-exclamation-triangle"></i> <span data-localize="message-error">Error!</span></strong> <span data-localize="message-something-wrong">Something went wrong.</span> @Model.Message
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
}
<div class="row">
<div class="col-sm-6 padding-fix">
<a class="btn btn-primary btn-lg btn-block" asp-action="Create">
<i class="fas fa-bug"></i> <span data-localize="label-submit-another">Submit Another Issue</span>
</a>
</div>
<div class="col-sm-6 padding-fix">
<a class="btn btn-primary btn-lg btn-block" asp-action="Check">
<i class="fas fa-search"></i> <span data-localize="label-check-another">Check Another Report</span>
</a>
</div>
</div>

View File

@ -0,0 +1,22 @@
@inject IOptions<UiConfig> uiConfig
<div class="row">
<div class="col-sm-6 padding-fix">
<div class="card border-secondary ">
<div class="card-body">
<a class="btn btn-link btn-lg btn-block text-muted" asp-action="Create">
<i class="fas fa-bug"></i> <span data-localize="btn-submit-new-issue">Submit New Issue</span>
</a>
</div>
</div>
</div>
<div class="col-sm-6 padding-fix">
<div class="card border-secondary ">
<div class="card-body">
<a class="btn btn-link btn-lg btn-block text-muted" asp-action="Check">
<i class="fas fa-search"></i> <span data-localize="btn-check-report">Check Report</span>
</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,2 @@
<h3>3rd party licenses:</h3>
<div>Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>

View File

@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@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>

View File

@ -0,0 +1,90 @@
@using System.Reflection
@inject IOptions<UiConfig> uiConfig
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>@uiConfig.Value.HeaderText - @uiConfig.Value.GetAssemblyProduct</title>
<link href="~/lib/fontawesome/5.11.2-web/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="~/lib/bootstrap/themes/@uiConfig.Value.Theme/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
@RenderSection("Head", required: false)
</head>
<body>
<div class="container">
<main role="main" class="pb-3">
<div class="py-3 text-center">
<a asp-action="Index">
<img class="d-block mx-auto mb-4" src="@uiConfig.Value.LogoUrl" alt="@uiConfig.Value.HeaderText - @uiConfig.Value.DescriptionText" title="@uiConfig.Value.HeaderText - @uiConfig.Value.DescriptionText" width="72" height="72">
</a>
<h2>@uiConfig.Value.HeaderText</h2>
<p class="lead">@uiConfig.Value.DescriptionText</p>
</div>
@RenderBody()
</main>
</div>
<footer class="footer text-muted text-center">
<div class="container">
<div class="row">
<div class="col-sm">
<p class="mb-1">
<i class="far fa-copyright"></i> 2009-@DateTime.Now.Year.ToString() EpicMorg
</p>
</div>
<div class="col-sm">
<p class="mb-1">
<i class="fas fa-cubes"></i> @uiConfig.Value.GetAssemblyProduct: <a class="text-secondary" href="https://github.com/EpicMorg/jira-issue-web-reporter/releases" target="_blank">@uiConfig.Value.GetAssemblyVersion()</a>
</p>
</div>
<div class="col-sm">
<p class="mb-1">
<i class="fas fa-key"></i> <span data-localize="label-licensed-to">Liceensed to</span>: @uiConfig.Value.LicensedTo
</p>
</div>
</div>
</div>
<ul class="list-inline">
<li class="list-inline-item">
<a asp-action="Create">
<i class="fas fa-bug"></i> <span data-localize="btn-submit-new-issue">Submit New Issue</span>
</a>
</li>
<li class="list-inline-item">
<a asp-action="Check">
<i class="fas fa-search"></i> <span data-localize="btn-check-report">Check Report</span>
</a>
</li>
<li class="list-inline-item">
<div class="dropdown dropup">
<a class="dropdown-toggle" href="#" role="button" id="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-language"></i> <span data-localize="lang-language">Language</span>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="#" id="lang_en_btn">
<img height="16" src="~/lib/flags/png/260-united-kingdom.png" alt="" class="mr-2">
<span data-localize="lang-english">English</span>
</a>
<a class="dropdown-item" href="#" id="lang_ru_btn">
<img height="16" src="~/lib/flags/png/248-russia.png" alt="" class="mr-2">
<span data-localize="lang-rusian">Russian</span>
</a>
</div>
</div>
</li>
</ul>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-localize/jquery.localize.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,18 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<script>
window.onload = function () {
jQuery.validator.addMethod('checkCaptcha', function (value, element) {
//https://stackoverflow.com/a/34746663
return grecaptcha.getResponse() !== '';
}, 'Invalid captcha');
jQuery.validator.addClassRules({
'g-recaptcha-response': {
checkCaptcha: true
}
});
$('#form').validate();
};
</script>

View File

@ -0,0 +1,10 @@
@using epicmorg.jira.issue.web.reporter
@using epicmorg.jira.issue.web.reporter.Models
@using epicmorg.jira.issue.web.reporter.Models.Create.ViewModels
@using epicmorg.jira.issue.web.reporter.Models.Check.Request
@using epicmorg.jira.issue.web.reporter.Models.Create.Response
@using epicmorg.jira.issue.web.reporter.Models.Check.Response
@using Microsoft.Extensions.Options
@using epicmorg.jira.issue.web.reporter.Models.Configuration
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UserSecretsId>988e0611-42e5-4c51-9300-0b75e780c73a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<PackageId>epicmorg.jira.issue.web.reporter</PackageId>
<Version>1.0.0.17</Version>
<Authors>epicm.org</Authors>
<Product>Jira issue web reporter</Product>
<Copyright>EpicM.org</Copyright>
<Description>Web-based simple issue reporter via official rest-api.</Description>
<AssemblyName>epicmorg.jira.issue.web.reporter</AssemblyName>
<RootNamespace>epicmorg.jira.issue.web.reporter</RootNamespace>
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
<PackageProjectUrl>https://ww.epicm.org/</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0" />
<PackageReference Include="Automapper" Version="9.0.0" />
<PackageReference Include="Atlassian.SDK" Version="11.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.9.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,84 @@

a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
/* line-height: 60px; Vertically center the text there */
}
#g-recaptcha-response {
display: block !important;
position: absolute;
margin: -78px 0 0 0 !important;
width: 302px !important;
height: 76px !important;
z-index: -999999;
opacity: 0;
}
.padding-fix {
padding-bottom: 15px;
}
a, .btn-link {
outline: none !important;
outline-style: none !important;
text-decoration: none !important;
}
.container {
max-width: 960px;
}
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
.lh-condensed {
line-height: 1.25;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -0,0 +1,53 @@
$(document).ready(function () {
console.log("crap.js v1.1 inintialized");
var userLang = navigator.language || navigator.userLanguage;
if (localStorage.getItem("selectedLang") === null) {
console.log('selectedLang is empty');
localStorage.setItem('selectedLang', userLang);
console.log(`selectedLang now is ${localStorage.selectedLang}`);
}
$.setRussianLang = function () {
$("#lang_en_btn").removeClass("active");
$("#lang_ru_btn").addClass("active");
$("[data-localize]").localize("site", { language: "ru", pathPrefix: "/lib/jquery-localize/lang/" });
};
$.setEnglishLang = function () {
$("#lang_ru_btn").removeClass("active");
$("#lang_en_btn").addClass("active");
$("[data-localize]").localize("site", { language: "en", pathPrefix: "/lib/jquery-localize/lang/" });
};
switch (localStorage.selectedLang) {
case "ru-RU":
$.setRussianLang();
break;
// case "en-US":
// $.setEnglishLang();
// break;
default:
$.setEnglishLang();
}
$("#lang_ru_btn").click(function (e) {
console.log("lang_ru_btn");
userLang = "ru-RU";
localStorage.setItem('selectedLang', userLang);
console.log(`selectedLang is ${localStorage.selectedLang}`);
$.setRussianLang();
});
$("#lang_en_btn").click(function (e) {
console.log("lang_en_btn");
userLang = "en-US";
localStorage.setItem('selectedLang', userLang);
console.log(`selectedLang is ${localStorage.selectedLang}`);
$.setEnglishLang();
});
});

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,103 @@
// Cerulean 4.3.1
// Bootswatch
@mixin btn-shadow($color){
@include gradient-y-three-colors(lighten($color, 8%), $color, 60%, darken($color, 4%));
}
$text-shadow: 0 1px 0 rgba(0, 0, 0, 0.05) !default;
// Navbar ======================================================================
.bg-primary {
@include btn-shadow($primary);
}
.bg-dark {
@include btn-shadow($blue);
}
.bg-light {
@include gradient-y-three-colors(lighten($gray-200, 8%), $gray-200, 60%, darken($gray-200, 2%));
}
.navbar-brand,
.nav-link {
text-shadow: $text-shadow
}
// Buttons =====================================================================
.btn {
text-shadow: $text-shadow
}
.btn-primary {
@include btn-shadow($primary);
}
.btn-secondary {
@include btn-shadow($secondary);
color: $gray-700;
}
.btn-success {
@include btn-shadow($success);
}
.btn-info {
@include btn-shadow($info);
}
.btn-warning {
@include btn-shadow($warning);
}
.btn-danger {
@include btn-shadow($danger);
}
.btn-light {
@include btn-shadow($light);
}
.btn-dark {
@include btn-shadow($dark);
}
// Typography ==================================================================
.text-secondary {
color: $gray-500 !important;
}
.bg-primary,
.bg-success,
.bg-info,
.bg-warning,
.bg-danger,
.bg-dark {
h1, h2, h3, h4, h5, h6 {
color: $white;
}
}
// Tables ======================================================================
// Forms =======================================================================
// Navs ========================================================================
.dropdown-menu {
.dropdown-header {
color: $gray-600;
}
}
// Indicators ==================================================================
// Progress bars ===============================================================
// Containers ==================================================================

View File

@ -0,0 +1,57 @@
// Cerulean 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #868e96 !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #033C73 !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #C71C22 !default;
$orange: #fd7e14 !default;
$yellow: #DD5600 !default;
$green: #73A839 !default;
$teal: #20c997 !default;
$cyan: #2FA4E7 !default;
$primary: $cyan !default;
$secondary: $gray-200 !default;
$success: $green !default;
$info: $blue !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-800 !default;
// Body
$body-color: $gray-700 !default;
// Fonts
$headings-color: $cyan !default;
// Dropdowns
$dropdown-link-color: $body-color !default;
$dropdown-link-hover-color: $white !default;
$dropdown-link-hover-bg: $primary !default;
// Navbar
$navbar-dark-color: rgba($white,.8) !default;
$navbar-dark-hover-color: $white !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
// Cosmo 4.3.1
// Bootswatch
// Variables ===================================================================
$web-font-path: "https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700" !default;
@import url($web-font-path);
// Navbar ======================================================================
// Buttons =====================================================================
// Typography ==================================================================
body {
-webkit-font-smoothing: antialiased;
}
// Tables ======================================================================
// Forms =======================================================================
// Navs ========================================================================
// Indicators ==================================================================
// Progress bars ===============================================================
.progress {
@include box-shadow(none);
.progress-bar {
font-size: 8px;
line-height: 8px;
}
}
// Containers ==================================================================

View File

@ -0,0 +1,68 @@
// Cosmo 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #868e96 !default;
$gray-700: #495057 !default;
$gray-800: #373a3c !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #2780E3 !default;
$indigo: #6610f2 !default;
$purple: #613d7c !default;
$pink: #e83e8c !default;
$red: #FF0039 !default;
$orange: #f0ad4e !default;
$yellow: #FF7518 !default;
$green: #3FB618 !default;
$teal: #20c997 !default;
$cyan: #9954BB !default;
$primary: $blue !default;
$secondary: $gray-800 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-800 !default;
// Options
$enable-rounded: false !default;
// Body
$body-color: $gray-800 !default;
// Fonts
$font-family-sans-serif: "Segoe UI", "Source Sans Pro", Calibri, Candara, Arial, sans-serif !default;
$font-size-base: 0.9375rem !default;
$headings-font-weight: 300 !default;
// Navbar
$navbar-dark-hover-color: rgba($white,1) !default;
$navbar-light-hover-color: rgba($black,.9) !default;
// Alerts
$alert-border-width: 0 !default;
// Progress bars
$progress-height: 0.5rem !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,286 @@
// Cyborg 4.3.1
// Bootswatch
// Variables ===================================================================
$web-font-path: "https://fonts.googleapis.com/css?family=Roboto:400,700" !default;
@import url($web-font-path);
// Navbar ======================================================================
.navbar {
&.bg-primary {
border: 1px solid $gray-700;
}
&.bg-dark {
background-color: $body-bg !important;
border: 1px solid $gray-700;
}
&.bg-light {
background-color: $gray-500 !important;
}
&.fixed-top {
border-width: 0 0 1px 0;
}
&.fixed-bottom {
border-width: 1px 0 0 0;
}
}
// Buttons =====================================================================
.btn {
@each $color, $value in $theme-colors {
&-#{$color} {
@if $enable-gradients {
background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x;
} @else {
background-color: $value;
}
}
}
}
// Typography ==================================================================
// Tables ======================================================================
table {
color: #fff;
}
.table {
&-primary {
&, > th, > td {
background-color: $primary;
}
}
&-secondary {
&, > th, > td {
background-color: $secondary;
}
}
&-light {
&, > th, > td {
background-color: $light;
}
}
&-dark {
&, > th, > td {
background-color: $dark;
}
}
&-success {
&, > th, > td {
background-color: $success;
}
}
&-info {
&, > th, > td {
background-color: $info;
}
}
&-danger {
&, > th, > td {
background-color: $danger;
}
}
&-warning {
&, > th, > td {
background-color: $warning;
}
}
&-active {
&, > th, > td {
background-color: $table-active-bg;
}
}
&-hover {
.table-primary:hover {
&, > th, > td {
background-color: darken($primary, 5%);
}
}
.table-secondary:hover {
&, > th, > td {
background-color: darken($secondary, 5%);
}
}
.table-light:hover {
&, > th, > td {
background-color: darken($light, 5%);
}
}
.table-dark:hover {
&, > th, > td {
background-color: darken($dark, 5%);
}
}
.table-success:hover {
&, > th, > td {
background-color: darken($success, 5%);
}
}
.table-info:hover {
&, > th, > td {
background-color: darken($info, 5%);
}
}
.table-danger:hover {
&, > th, > td {
background-color: darken($danger, 5%);
}
}
.table-warning:hover {
&, > th, > td {
background-color: darken($warning, 5%);
}
}
.table-active:hover {
&, > th, > td {
background-color: $table-active-bg;
}
}
}
}
// Forms =======================================================================
legend {
color: #fff;
}
// Navs ========================================================================
.nav-tabs,
.nav-pills {
.nav-link {
color: #fff;
&:hover {
background-color: $gray-700;
}
&.disabled,
&.disabled:hover {
background-color: transparent;
color: $nav-link-disabled-color;
}
&.active {
background-color: $primary;
}
}
}
.breadcrumb {
a {
color: #fff;
}
}
.pagination {
a:hover {
text-decoration: none;
}
}
// Indicators ==================================================================
.alert {
border: none;
color: $white;
a,
.alert-link {
color: #fff;
text-decoration: underline;
}
@each $color, $value in $theme-colors {
&-#{$color} {
@if $enable-gradients {
background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x;
} @else {
background-color: $value;
}
}
}
}
.badge {
&-warning {
color: $white;
}
}
.close {
opacity: 0.6;
&:hover {
opacity: 1;
}
}
// Progress bars ===============================================================
// Containers ==================================================================
.list-group-item {
&:hover {
background-color: $gray-700;
color: #fff;
}
&-action {
color: $gray-500;
.list-group-item-heading {
color: $gray-500;
}
}
&:hover .list-group-item-heading {
color: #fff;
}
}
.card,
.list-group-item {
h1, h2, h3, h4, h5, h6 {
color: inherit;
}
}
.popover {
&-title {
border-bottom: none;
}
}

View File

@ -0,0 +1,173 @@
// Cyborg 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ADAFAE !default;
$gray-500: #888 !default;
$gray-600: #555 !default;
$gray-700: #282828 !default;
$gray-800: #222 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #2A9FD6 !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #CC0000 !default;
$orange: #fd7e14 !default;
$yellow: #FF8800 !default;
$green: #77B300 !default;
$teal: #20c997 !default;
$cyan: #9933CC !default;
$primary: $blue !default;
$secondary: $gray-600 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-800 !default;
$dark: $gray-400 !default;
$yiq-contrasted-threshold: 175 !default;
// Body
$body-bg: #060606 !default;
$body-color: $gray-400 !default;
// Fonts
$font-family-sans-serif: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
$font-size-base: .875rem !default;
$h1-font-size: 4rem !default;
$h2-font-size: 3rem !default;
$h3-font-size: 2.5rem !default;
$h4-font-size: 2rem !default;
$h5-font-size: 1.5rem !default;
$headings-color: $white !default;
// Tables
$table-color: $white !default;
$table-accent-bg: rgba($white,.05) !default;
$table-hover-bg: rgba($white,.075) !default;
$table-border-color: $gray-700 !default;
$table-dark-bg: $gray-500 !default;
$table-dark-border-color: darken($gray-500, 7.5%) !default;
// Buttons
$input-btn-padding-x: 1rem !default;
// Forms
$input-disabled-bg: $gray-400 !default;
$input-border-color: transparent !default;
$input-group-addon-color: $white !default;
$input-group-addon-bg: $gray-700 !default;
$custom-file-color: $white !default;
$custom-file-border-color: $gray-700 !default;
// Dropdowns
$dropdown-bg: $gray-700 !default;
$dropdown-divider-bg: $gray-800 !default;
$dropdown-link-color: $white !default;
$dropdown-link-hover-color: $white !default;
$dropdown-link-hover-bg: $primary !default;
// Navs
$nav-tabs-border-color: $table-border-color !default;
$nav-tabs-link-hover-border-color: $nav-tabs-border-color !default;
$nav-tabs-link-active-color: $white !default;
$nav-tabs-link-active-bg: $nav-tabs-border-color !default;
$nav-tabs-link-active-border-color: $nav-tabs-border-color !default;
// Navbar
$navbar-dark-hover-color: $white !default;
// Pagination
$pagination-color: $white !default;
$pagination-bg: $gray-700 !default;
$pagination-border-color: transparent !default;
$pagination-hover-color: $white !default;
$pagination-hover-bg: $primary !default;
$pagination-hover-border-color: $pagination-border-color !default;
$pagination-disabled-bg: $pagination-bg !default;
$pagination-disabled-border-color: $pagination-border-color !default;
// Jumbotron
$jumbotron-bg: $gray-700 !default;
// Cards
$card-bg: $gray-700 !default;
// Tooltips
$tooltip-bg: $gray-700 !default;
$tooltip-opacity: 1 !default;
// Popovers
$popover-bg: $gray-700 !default;
// Modals
$modal-content-bg: $gray-800 !default;
$modal-header-border-color: $gray-700 !default;
// Progress bars
$progress-bg: $gray-700 !default;
// List group
$list-group-bg: $gray-800 !default;
$list-group-border-color: $gray-700 !default;
$list-group-hover-bg: $primary !default;
$list-group-disabled-bg: $gray-700 !default;
$list-group-action-active-bg: $primary !default;
// Breadcrumbs
$breadcrumb-bg: $gray-700 !default;
// Close
$close-color: $white !default;
$close-text-shadow: none !default;
// Code
$pre-color: inherit !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,240 @@
// Darkly 4.3.1
// Bootswatch
// Variables ===================================================================
$web-font-path: "https://fonts.googleapis.com/css?family=Lato:400,700,400italic" !default;
@import url($web-font-path);
// Navbar ======================================================================
.bg-primary {
.navbar-nav .active > .nav-link {
color: $success !important;
}
}
.bg-light {
&.navbar {
background-color: $success !important;
}
&.navbar-light .navbar-nav {
.nav-link:focus,
.nav-link:hover,
.active > .nav-link {
color: $primary !important;
}
}
}
// Buttons =====================================================================
// Typography ==================================================================
.blockquote {
&-footer {
color: $gray-600;
}
}
// Tables ======================================================================
.table {
&-primary {
&, > th, > td {
background-color: $primary;
}
}
&-secondary {
&, > th, > td {
background-color: $secondary;
}
}
&-light {
&, > th, > td {
background-color: $light;
}
}
&-dark {
&, > th, > td {
background-color: $dark;
}
}
&-success {
&, > th, > td {
background-color: $success;
}
}
&-info {
&, > th, > td {
background-color: $info;
}
}
&-danger {
&, > th, > td {
background-color: $danger;
}
}
&-warning {
&, > th, > td {
background-color: $warning;
}
}
&-active {
&, > th, > td {
background-color: $table-active-bg;
}
}
&-hover {
.table-primary:hover {
&, > th, > td {
background-color: darken($primary, 5%);
}
}
.table-secondary:hover {
&, > th, > td {
background-color: darken($secondary, 5%);
}
}
.table-light:hover {
&, > th, > td {
background-color: darken($light, 5%);
}
}
.table-dark:hover {
&, > th, > td {
background-color: darken($dark, 5%);
}
}
.table-success:hover {
&, > th, > td {
background-color: darken($success, 5%);
}
}
.table-info:hover {
&, > th, > td {
background-color: darken($info, 5%);
}
}
.table-danger:hover {
&, > th, > td {
background-color: darken($danger, 5%);
}
}
.table-warning:hover {
&, > th, > td {
background-color: darken($warning, 5%);
}
}
.table-active:hover {
&, > th, > td {
background-color: $table-active-bg;
}
}
}
}
// Forms =======================================================================
.input-group-addon {
color: #fff;
}
// Navs ========================================================================
.nav-tabs,
.nav-pills {
.nav-link,
.nav-link.active,
.nav-link.active:focus,
.nav-link.active:hover,
.nav-item.open .nav-link,
.nav-item.open .nav-link:focus,
.nav-item.open .nav-link:hover {
color: #fff;
}
}
.breadcrumb a {
color: #fff;
}
.pagination {
a:hover {
text-decoration: none;
}
}
// Indicators ==================================================================
.close {
opacity: 0.4;
&:hover,
&:focus {
opacity: 1;
}
}
.alert {
border: none;
color: $white;
a,
.alert-link {
color: #fff;
text-decoration: underline;
}
@each $color, $value in $theme-colors {
&-#{$color} {
@if $enable-gradients {
background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x;
} @else {
background-color: $value;
}
}
}
}
// Progress bars ===============================================================
// Containers ==================================================================
.list-group-item-action {
color: #fff;
&:hover,
&:focus {
background-color: $gray-700;
color: #fff;
}
.list-group-item-heading {
color: #fff;
}
}

View File

@ -0,0 +1,174 @@
// Darkly 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #ebebeb !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #999 !default;
$gray-700: #444 !default;
$gray-800: #303030 !default;
$gray-900: #222 !default;
$black: #000 !default;
$blue: #375a7f !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #E74C3C !default;
$orange: #fd7e14 !default;
$yellow: #F39C12 !default;
$green: #00bc8c !default;
$teal: #20c997 !default;
$cyan: #3498DB !default;
$primary: $blue !default;
$secondary: $gray-700 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-600 !default;
$dark: $gray-800 !default;
$yiq-contrasted-threshold: 175 !default;
// Body
$body-bg: $gray-900 !default;
$body-color: $white !default;
// Links
$link-color: $success !default;
// Fonts
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
$font-size-base: 0.9375rem !default;
$h1-font-size: 3rem !default;
$h2-font-size: 2.5rem !default;
$h3-font-size: 2rem !default;
// Tables
$table-accent-bg: $gray-800 !default;
$table-border-color: $gray-700 !default;
// Forms
$input-border-color: $body-bg !default;
$input-group-addon-color: $gray-500 !default;
$input-group-addon-bg: $gray-700 !default;
$custom-file-color: $gray-500 !default;
$custom-file-border-color: $body-bg !default;
// Dropdowns
$dropdown-bg: $gray-900 !default;
$dropdown-border-color: $gray-700 !default;
$dropdown-divider-bg: $gray-700 !default;
$dropdown-link-color: $white !default;
$dropdown-link-hover-color: $white !default;
$dropdown-link-hover-bg: $primary !default;
// Navs
$nav-link-padding-x: 2rem !default;
$nav-link-disabled-color: $gray-500 !default;
$nav-tabs-border-color: $gray-700 !default;
$nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent !default;
$nav-tabs-link-active-color: $white !default;
$nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent !default;
// Navbar
$navbar-padding-y: 1rem !default;
$navbar-dark-color: rgba($white,.6) !default;
$navbar-dark-hover-color: $white !default;
$navbar-light-color: $white !default;
$navbar-light-hover-color: $success !default;
$navbar-light-active-color: $white !default;
$navbar-light-disabled-color: rgba($white,.3) !default;
$navbar-light-toggler-border-color: rgba($white,.1) !default;
// Pagination
$pagination-color: $white !default;
$pagination-bg: $success !default;
$pagination-border-width: 0 !default;
$pagination-border-color: transparent !default;
$pagination-hover-color: $white !default;
$pagination-hover-bg: lighten($success, 10%) !default;
$pagination-hover-border-color: transparent !default;
$pagination-active-bg: $pagination-hover-bg !default;
$pagination-active-border-color: transparent !default;
$pagination-disabled-color: $white !default;
$pagination-disabled-bg: darken($success, 15%) !default;
$pagination-disabled-border-color: transparent !default;
// Jumbotron
$jumbotron-bg: $gray-800 !default;
// Cards
$card-cap-bg: $gray-700 !default;
$card-bg: $gray-800 !default;
// Popovers
$popover-bg: $gray-800 !default;
$popover-header-bg: $gray-700 !default;
// Modals
$modal-content-bg: $gray-800 !default;
$modal-content-border-color: $gray-700 !default;
$modal-header-border-color: $gray-700 !default;
// Progress bars
$progress-height: 0.625rem !default;
$progress-font-size: 0.625rem !default;
$progress-bg: $gray-700 !default;
// List group
$list-group-bg: $gray-800 !default;
$list-group-border-color: $gray-700 !default;
$list-group-hover-bg: $gray-700 !default;
// Breadcrumbs
$breadcrumb-bg: $gray-700 !default;
// Close
$close-color: $white !default;
$close-text-shadow: none !default;
// Code
$pre-color: inherit !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,247 @@
// Flatly 4.3.1
// Bootswatch
// Variables ===================================================================
$web-font-path: "https://fonts.googleapis.com/css?family=Lato:400,700,400italic" !default;
@import url($web-font-path);
// Navbar =======================================================================
.bg-primary {
.navbar-nav .active > .nav-link {
color: $success !important;
}
}
.bg-dark {
background-color: $success !important;
&.navbar-dark .navbar-nav {
.nav-link:focus,
.nav-link:hover,
.active > .nav-link {
color: $primary !important;
}
}
}
// Buttons =====================================================================
.btn {
&-secondary,
&-secondary:hover,
&-warning,
&-warning:hover {
color: #fff;
}
}
// Typography ==================================================================
// Tables ======================================================================
.table {
&-primary,
&-secondary,
&-success,
&-info,
&-warning,
&-danger {
color: #fff;
}
&-primary {
&, > th, > td {
background-color: $primary;
}
}
&-secondary {
&, > th, > td {
background-color: $secondary;
}
}
&-light {
&, > th, > td {
background-color: $light;
}
}
&-dark {
&, > th, > td {
background-color: $dark;
}
}
&-success {
&, > th, > td {
background-color: $success;
}
}
&-info {
&, > th, > td {
background-color: $info;
}
}
&-danger {
&, > th, > td {
background-color: $danger;
}
}
&-warning {
&, > th, > td {
background-color: $warning;
}
}
&-active {
&, > th, > td {
background-color: $table-active-bg;
}
}
&-hover {
.table-primary:hover {
&, > th, > td {
background-color: darken($primary, 5%);
}
}
.table-secondary:hover {
&, > th, > td {
background-color: darken($secondary, 5%);
}
}
.table-light:hover {
&, > th, > td {
background-color: darken($light, 5%);
}
}
.table-dark:hover {
&, > th, > td {
background-color: darken($dark, 5%);
}
}
.table-success:hover {
&, > th, > td {
background-color: darken($success, 5%);
}
}
.table-info:hover {
&, > th, > td {
background-color: darken($info, 5%);
}
}
.table-danger:hover {
&, > th, > td {
background-color: darken($danger, 5%);
}
}
.table-warning:hover {
&, > th, > td {
background-color: darken($warning, 5%);
}
}
.table-active:hover {
&, > th, > td {
background-color: $table-active-bg;
}
}
}
}
// Forms =======================================================================
// Navs ========================================================================
.nav-tabs {
.nav-link.active,
.nav-link.active:focus,
.nav-link.active:hover,
.nav-item.open .nav-link,
.nav-item.open .nav-link:focus,
.nav-item.open .nav-link:hover {
color: $primary;
}
}
.pagination {
a:hover {
text-decoration: none;
}
}
// Indicators ==================================================================
.close {
text-decoration: none;
opacity: 0.4;
&:hover,
&:focus {
opacity: 1;
}
}
.badge {
&-secondary,
&-warning {
color: #fff;
}
}
.alert {
border: none;
color: $white;
a,
.alert-link {
color: #fff;
text-decoration: underline;
}
@each $color, $value in $theme-colors {
&-#{$color} {
@if $enable-gradients {
background: $value linear-gradient(180deg, mix($body-bg, $value, 15%), $value) repeat-x;
} @else {
background-color: $value;
}
}
}
&-light {
&,
& a,
& .alert-link {
color: $body-color;
}
}
}
// Progress bars ===============================================================
// Containers ==================================================================
.modal .close{
color: $black;
&:not(:disabled):not(.disabled):hover,
&:not(:disabled):not(.disabled):focus {
color: $black;
}
}

View File

@ -0,0 +1,113 @@
// Flatly 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #ecf0f1 !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #b4bcc2 !default;
$gray-600: #95a5a6 !default;
$gray-700: #7b8a8b !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #2C3E50 !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #E74C3C !default;
$orange: #fd7e14 !default;
$yellow: #F39C12 !default;
$green: #18BC9C !default;
$teal: #20c997 !default;
$cyan: #3498DB !default;
$primary: $blue !default;
$secondary: $gray-600 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-200 !default;
$dark: $gray-700 !default;
$yiq-contrasted-threshold: 175 !default;
// Links
$link-color: $success !default;
// Fonts
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
$font-size-base: 0.9375rem !default;
$h1-font-size: 3rem !default;
$h2-font-size: 2.5rem !default;
$h3-font-size: 2rem !default;
// Tables
$table-accent-bg: $gray-200 !default;
// Dropdowns
$dropdown-link-color: $gray-700 !default;
$dropdown-link-hover-color: $white !default;
$dropdown-link-hover-bg: $primary !default;
// Navs
$nav-link-padding-y: .5rem !default !default;
$nav-link-padding-x: 2rem !default;
$nav-link-disabled-color: $gray-600 !default !default;
$nav-tabs-border-color: $gray-200 !default;
// Navbar
$navbar-padding-y: 1rem !default;
$navbar-dark-color: $white !default;
$navbar-dark-hover-color: $success !default;
// Pagination
$pagination-color: $white !default;
$pagination-bg: $success !default;
$pagination-border-width: 0 !default;
$pagination-border-color: transparent !default;
$pagination-hover-color: $white !default;
$pagination-hover-bg: darken($success, 15%) !default;
$pagination-hover-border-color: transparent !default;
$pagination-active-bg: $pagination-hover-bg !default;
$pagination-active-border-color: transparent !default;
$pagination-disabled-color: $gray-200 !default;
$pagination-disabled-bg: lighten($success, 15%) !default;
$pagination-disabled-border-color: transparent !default;
// Progress bars
$progress-height: 0.625rem !default;
$progress-font-size: 0.625rem !default;
// List group
$list-group-hover-bg: $gray-200 !default;
$list-group-disabled-bg: $gray-200 !default;
// Close
$close-color: $white !default;
$close-text-shadow: none !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
// Journal 4.3.1
// Bootswatch
// Variables ===================================================================
$web-font-path: "https://fonts.googleapis.com/css?family=News+Cycle:400,700" !default;
@import url($web-font-path);
// Navbar ======================================================================
.bg-dark {
background-color: #000 !important;
}
.bg-light {
background-color: $white !important;
color: $black;
border: 1px solid $gray-200;
&.navbar-fixed-top {
border-width: 0 0 1px 0;
}
&.navbar-fixed-bottom {
border-width: 1px 0 0 0;
}
}
.navbar {
font-size: 18px;
font-family: $headings-font-family;
font-weight: $headings-font-weight;
}
.navbar-brand {
padding-top: 0.5rem;
font-size: inherit;
font-weight: $headings-font-weight;
text-transform: uppercase;
}
// Buttons =====================================================================
.btn {
font-family: $headings-font-family;
font-weight: $headings-font-weight;
&-secondary,
&-warning {
color: $white;
}
}
// Typography ==================================================================
// Tables ======================================================================
// Forms =======================================================================
// Navs ========================================================================
.pagination {
a:hover {
text-decoration: none;
}
}
// Indicators ==================================================================
// Progress bars ===============================================================
// Containers ==================================================================

View File

@ -0,0 +1,60 @@
// Journal 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #eee !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #aaa !default;
$gray-600: #777 !default;
$gray-700: #495057 !default;
$gray-800: #333 !default;
$gray-900: #222 !default;
$black: #000 !default;
$blue: #EB6864 !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #F57A00 !default;
$orange: #fd7e14 !default;
$yellow: #F5E625 !default;
$green: #22B24C !default;
$teal: #20c997 !default;
$cyan: #369 !default;
$primary: $blue !default;
$secondary: $gray-500 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-900 !default;
// Fonts
$headings-font-family: "News Cycle", "Arial Narrow Bold", sans-serif !default;
$headings-font-weight: 700 !default;
$headings-line-height: 1.1 !default;
// Buttons
$input-btn-padding-x: 1rem !default;
// Navbar
$navbar-light-color: rgba($black,.7) !default;
$navbar-light-hover-color: $black !default;
$navbar-light-active-color: $black !default;
// Pagination
$pagination-hover-color: $white !default;
$pagination-hover-bg: $primary !default;
$pagination-hover-border-color: $primary !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,255 @@
// Litera 4.3.1
// Bootswatch
// Variables ===================================================================
$font-family-serif: Georgia, Cambria, "Times New Roman", Times, serif !default;
// Navbar ======================================================================
.navbar {
font-size: $font-size-sm;
&.bg-dark {
background-color: $success !important;
}
&.bg-light {
background-color: #fff !important;
border: 1px solid rgba(0, 0, 0, 0.1);
&.navbar-fixed-top {
border-width: 0 0 1px 0;
}
&.navbar-fixed-bottom {
border-width: 1px 0 0 0;
}
}
}
// Buttons =====================================================================
.btn {
border-radius: 1.078em;
&-lg {
border-radius: 2.688em;
}
&-sm {
border-radius: 0.844em;
}
}
// Typography ==================================================================
p {
font-family: $font-family-serif;
}
blockquote {
font-style: italic;
}
footer {
font-size: $font-size-sm;
}
.lead {
color: $gray-600;
font-family: $font-family-sans-serif;
}
// Tables ======================================================================
table,
.table {
font-size: $font-size-sm;
&-primary,
&-secondary,
&-success,
&-info,
&-warning,
&-danger {
color: #fff;
}
}
.table {
&-primary {
&, > th, > td {
background-color: $primary;
}
}
&-secondary {
&, > th, > td {
background-color: $secondary;
}
}
&-light {
&, > th, > td {
background-color: $light;
}
}
&-dark {
&, > th, > td {
background-color: $dark;
}
}
&-success {
&, > th, > td {
background-color: $success;
}
}
&-info {
&, > th, > td {
background-color: $info;
}
}
&-danger {
&, > th, > td {
background-color: $danger;
}
}
&-warning {
&, > th, > td {
background-color: $warning;
}
}
&-active {
&, > th, > td {
background-color: $table-active-bg;
}
}
&-hover {
.table-primary:hover {
&, > th, > td {
background-color: darken($primary, 5%);
}
}
.table-secondary:hover {
&, > th, > td {
background-color: darken($secondary, 5%);
}
}
.table-light:hover {
&, > th, > td {
background-color: darken($light, 5%);
}
}
.table-dark:hover {
&, > th, > td {
background-color: darken($dark, 5%);
}
}
.table-success:hover {
&, > th, > td {
background-color: darken($success, 5%);
}
}
.table-info:hover {
&, > th, > td {
background-color: darken($info, 5%);
}
}
.table-danger:hover {
&, > th, > td {
background-color: darken($danger, 5%);
}
}
.table-warning:hover {
&, > th, > td {
background-color: darken($warning, 5%);
}
}
.table-active:hover {
&, > th, > td {
background-color: $table-active-bg;
}
}
}
}
// Forms =======================================================================
// Navs ========================================================================
.nav,
.breadcrumb,
.pagination {
font-size: $font-size-sm;
}
.dropdown-menu {
font-size: $font-size-sm;
}
// Indicators ==================================================================
.alert {
color: $white;
font-size: $font-size-sm;
&, p {
font-family: $font-family-sans-serif;
}
a, .alert-link {
color: #fff;
font-weight: normal;
text-decoration: underline;
}
@each $color, $value in $theme-colors {
&-#{$color} {
@if $enable-gradients {
background: $value linear-gradient(0deg, mix($body-bg, $value, 15%), $value) repeat-x;
} @else {
background-color: $value;
}
}
}
&-light {
&,
& a,
& .alert-link {
color: $body-color;
}
}
}
.badge {
vertical-align: bottom;
}
// Progress bars ===============================================================
// Containers ==================================================================
.list-group {
font-size: $font-size-sm;
}

View File

@ -0,0 +1,94 @@
// Litera 4.3.1
// Bootswatch
//
// Color system
//
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #ddd !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #868e96 !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #4582EC !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #e83e8c !default;
$red: #d9534f !default;
$orange: #fd7e14 !default;
$yellow: #f0ad4e !default;
$green: #02B875 !default;
$teal: #20c997 !default;
$cyan: #17a2b8 !default;
$primary: $blue !default;
$secondary: $gray-500 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-800 !default;
$yiq-contrasted-threshold: 190 !default;
// Body
$body-color: $gray-800 !default;
// Fonts
$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;
$font-size-base: 1.063rem !default;
$headings-font-weight: 700 !default;
// Tables
$table-border-color: rgba(0,0,0,0.1) !default;
// Buttons
$input-btn-padding-y: 0.5rem !default;
$input-btn-padding-x: 1.1rem !default;
$btn-font-family: $font-family-sans-serif !default;
$btn-font-size: 0.875rem !default;
$btn-font-size-sm: 0.688rem !default;
// Forms
$input-border-color: rgba(0,0,0,.1) !default;
$input-group-addon-bg: $gray-200 !default !default;
// Navbar
$navbar-dark-color: rgba($white,.6) !default;
$navbar-dark-hover-color: $white !default;
$navbar-light-hover-color: $body-color !default;
$navbar-light-active-color: $body-color !default;
// Tooltips
$tooltip-font-size: 11px !default;
// Badges
$badge-font-weight: normal !default;
$badge-padding-y: 0.6em !default;
$badge-padding-x: 1.2em !default;
// Alerts
$alert-border-width: 0 !default;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More