2023-06-23 - BryanSoltis - Intial code migration

This commit is contained in:
Bryan Soltis
2023-06-23 12:34:12 -05:00
parent 8c322f0b17
commit 71ab5816ed
256 changed files with 87892 additions and 1 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

367
.gitignore vendored Normal file
View File

@@ -0,0 +1,367 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# Settings files
settings/*
!settings/blank.txt

23
App.razor Normal file
View File

@@ -0,0 +1,23 @@
@using AzureNamingTool.Models;
<CascadingValue Value="identityProviderDetails">
<CascadingBlazoredModal>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<img src="/images/runnoft.png" />
<p role="alert">Whoa, it looks like that page went and r-u-n-n-o-f-t! Try again!</p>
</LayoutView>
</NotFound>
</Router>
</CascadingBlazoredModal>
</CascadingValue>
@code {
[Parameter]
public IdentityProviderDetails? identityProviderDetails { get; set; }
}

View File

@@ -0,0 +1,42 @@
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace AzureNamingTool.Attributes
{
[AttributeUsage(validOn: AttributeTargets.Class)]
public class ApiKeyAttribute : Attribute, IAsyncActionFilter
{
private const string APIKEYNAME = "APIKey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key was not provided"
};
return;
}
var config = ConfigurationHelper.GetConfigurationData();
//var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
//var apiKey = appSettings.GetValue<string>(APIKEYNAME);
if (!GeneralHelper.DecryptString(config.APIKey, config.SALTKey).Equals(extractedApiKey))
{
context.Result = new ContentResult()
{
StatusCode = 401,
Content = "Api Key is not valid"
};
return;
}
await next();
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace AzureNamingTool.Attributes
{
public class CustomHeaderSwaggerAttribute : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "APIKey",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema
{
Type = "string"
}
});
}
}
}

40
Attributes/RateFilter.cs Normal file
View File

@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using AzureNamingTool.Models;
using AzureNamingTool.Services;
namespace AzureNamingTool.Attributes
{
public class RateFilter : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
try
{
var minRequestRateFeature = context.HttpContext.Features.Get<IHttpMinRequestBodyDataRateFeature>();
var minResponseRateFeature = context.HttpContext.Features.Get<IHttpMinResponseDataRateFeature>();
//Default Bytes/s = 240, Default TimeOut = 5s
if (minRequestRateFeature != null)
{
minRequestRateFeature.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
if (minResponseRateFeature != null)
{
minResponseRateFeature.MinDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}

84
AzureNamingTool.csproj Normal file
View File

@@ -0,0 +1,84 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Version>2.8.0</Version>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>8d243b16-78f2-4542-8f36-682411560b1a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1591;1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1591;1701;1702</NoWarn>
</PropertyGroup>
<ItemGroup>
<Content Remove="appsettings.Development.json" />
<Content Remove="appsettings.json" />
<Content Remove="settings\appsettings.Development.json" />
<Content Remove="settings\appsettings.json" />
<Content Remove="settings\namePolicyDefinition.json" />
<Content Remove="settings\resourcecomponents.json" />
<Content Remove="settings\resourcedelimiters.json" />
<Content Remove="settings\resourceenvironments.json" />
<Content Remove="settings\resourcefunctions.json" />
<Content Remove="settings\resourcelocations.json" />
<Content Remove="settings\generatednames.json" />
<Content Remove="settings\adminlog.json" />
<Content Remove="settings\resourceorgs.json" />
<Content Remove="settings\resourceprojappsvcs.json" />
<Content Remove="settings\resourcetypes.json" />
<Content Remove="settings\resourceunitdepts.json" />
<Content Remove="settings\resourcevmroles.json" />
<Content Remove="settings\resoureceunitdepts.json" />
<Content Remove="Shared\Admin.razor" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\Screenshots\AdminPasswordPrompt.png" />
<None Include="wwwroot\Screenshots\Containers1.png" />
<None Include="wwwroot\Screenshots\ConfigurationPageAdmin.png" />
<None Include="wwwroot\Screenshots\Containers2.png" />
<None Include="wwwroot\Screenshots\FileShare.png" />
<None Include="wwwroot\Screenshots\HomePage.png" />
<None Include="wwwroot\Screenshots\MountStorage.png" />
<None Include="wwwroot\Screenshots\ReferencePage.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
<PackageReference Include="Blazored.Modal" Version="7.1.0" />
<PackageReference Include="Blazored.Toast" Version="4.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
<PackageReference Include="System.Runtime.Caching" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\images\runnoft.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\js\jquery-3.7.0.min.js">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="settings\blank.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="VersionNotes\" />
</ItemGroup>
</Project>

25
AzureNamingTool.sln Normal file
View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureNamingTool", "AzureNamingTool.csproj", "{611DC036-F19A-4198-A63E-9163B545F59A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{611DC036-F19A-4198-A63E-9163B545F59A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{611DC036-F19A-4198-A63E-9163B545F59A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{611DC036-F19A-4198-A63E-9163B545F59A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{611DC036-F19A-4198-A63E-9163B545F59A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8C98E701-BDC5-4F6F-9F85-7BEC01C00E51}
EndGlobalSection
EndGlobal

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
aznamingtool@outlook.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

14
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,14 @@
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
and actually do, grant us the rights to use your contribution. For details, visit
https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -0,0 +1,275 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Mvc.ModelBinding;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class AdminController : ControllerBase
{
private ServiceResponse serviceResponse = new();
private SiteConfiguration config = ConfigurationHelper.GetConfigurationData();
// POST api/<AdminController>
/// <summary>
/// This function will update the Admin Password.
/// </summary>
/// <param name="password">string - New Admin Password</param>
/// <param name="adminpassword">string - Current Admin Password</param>
/// <returns>string - Successful update</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> UpdatePassword([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword, [FromBody] string password)
{
try
{
if (!String.IsNullOrEmpty(adminpassword))
{
if (adminpassword == GeneralHelper.DecryptString(config.AdminPassword, config.SALTKey))
{
serviceResponse = await AdminService.UpdatePassword(password);
return (serviceResponse.Success ? Ok("SUCCESS"): Ok("FAILURE - There was a problem updating the password."));
}
else
{
return Ok("FAILURE - Incorrect Admin Password.");
}
}
else
{
return Ok("FAILURE - You must provide teh Admin Password.");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<AdminController>
/// <summary>
/// This function will update the API Key.
/// </summary>
/// <param name="apikey">string - New API Key</param>
/// <param name="adminpassword">string - Current Admin Password</param>
/// <returns>dttring - Successful update</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> UpdateAPIKey([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword, [FromBody] string apikey)
{
try
{
if (!String.IsNullOrEmpty(adminpassword))
{
if (adminpassword == GeneralHelper.DecryptString(config.AdminPassword, config.SALTKey))
{
serviceResponse = await AdminService.UpdateAPIKey(apikey);
return (serviceResponse.Success ? Ok("SUCCESS") : Ok("FAILURE - There was a problem updating the API Key."));
}
else
{
return Ok("FAILURE - Incorrect Admin Password.");
}
}
else
{
return Ok("FAILURE - You must provide teh Admin Password.");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<AdminController>
/// <summary>
/// This function will generate a new API Key.
/// </summary>
/// <param name="adminpassword">string - Current Admin Password</param>
/// <returns>string - Successful update / Generated API Key</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> GenerateAPIKey([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword)
{
try
{
if (!String.IsNullOrEmpty(adminpassword))
{
if (adminpassword == GeneralHelper.DecryptString(config.AdminPassword, config.SALTKey))
{
serviceResponse = await AdminService.GenerateAPIKey();
return (serviceResponse.Success ? Ok("SUCCESS") : Ok("FAILURE - There was a problem generating the API Key."));
}
else
{
return Ok("FAILURE - Incorrect Admin Password.");
}
}
else
{
return Ok("FAILURE - You must provide teh Admin Password.");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
/// <summary>
/// This function will return the admin log data.
/// </summary>
/// <returns>json - Current admin log data</returns>
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> GetAdminLog([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword)
{
try
{
serviceResponse = await AdminLogService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
/// <summary>
/// This function will purge the admin log data.
/// </summary>
/// <returns>dttring - Successful operation</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PurgeAdminLog([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword)
{
try
{
serviceResponse = await AdminLogService.DeleteAllItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
/// <summary>
/// This function will return the generated names data.
/// </summary>
/// <returns>json - Current generated names data</returns>
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> GetGeneratedNamesLog()
{
try
{
serviceResponse = await GeneratedNamesService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
/// <summary>
/// This function will purge the generated names data.
/// </summary>
/// <returns>dttring - Successful operation</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PurgeGeneratedNamesLog([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword)
{
try
{
serviceResponse = await GeneratedNamesService.DeleteAllItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
/// <summary>
/// This function will reset the site configuration. THIS CANNOT BE UNDONE!
/// </summary>
/// <returns>dttring - Successful operation</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> ResetSiteConfiguration([BindRequired][FromHeader(Name = "AdminPassword")] string adminpassword)
{
try
{
if (ConfigurationHelper.ResetSiteConfiguration())
{
return Ok("Site configuration reset suceeded!");
}
else
{
return BadRequest("Site configuration reset failed!");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,275 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
using Microsoft.Extensions.Options;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class CustomComponentsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<CustomComponentsController>
/// <summary>
/// This function will return the custom components data.
/// </summary>
/// <returns>json - Current custom components data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
// Get list of items
serviceResponse = await CustomComponentService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<CustomComponentsController>/sample
/// <summary>
/// This function will return the custom components data for the specifc parent component type.
/// </summary>
/// <param name="parenttype">string - Parent Component Type Name</param>
/// <returns>json - Current custom components data</returns>
[Route("[action]/{parenttype}")]
[HttpGet]
public async Task<IActionResult> GetByParentType(string parenttype)
{
try
{
// Get list of items
serviceResponse = await CustomComponentService.GetItemsByParentType(GeneralHelper.NormalizeName(parenttype, true));
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<CustomComponentsController>/5
/// <summary>
/// This function will return the specifed custom component data.
/// </summary>
/// <param name="id">int - Custom Component id</param>
/// <returns>json - Custom component data</returns>
[HttpGet("{id:int}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await CustomComponentService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<CustomComponentsController>
/// <summary>
/// This function will create/update the specified custom component data.
/// </summary>
/// <param name="item">CustomComponent (json) - Custom component data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] CustomComponent item)
{
try
{
serviceResponse = await CustomComponentService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<CustomComponentsController>
/// <summary>
/// This function will update all custom components data.
/// </summary>
/// <param name="items">List-CustomComponent (json) - All custom components data. (Legacy functionality).</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<CustomComponent> items)
{
try
{
serviceResponse = await CustomComponentService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<CustomComponentsController>
/// <summary>
/// This function will update all custom components data.
/// </summary>
/// <param name="config">CustomComponmentConfig (json) - Full custom components data with parent component data.</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfigWithParentData([FromBody] CustomComponmentConfig config)
{
try
{
List<ResourceComponent> currentresourcecomponents = new();
List<CustomComponent> newcustomcomponents = new();
// Get the current resource components
serviceResponse = await ResourceComponentService.GetItems(true);
if (serviceResponse.Success)
{
currentresourcecomponents = serviceResponse.ResponseObject;
// Loop through the posted components
foreach (ResourceComponent thisparentcomponent in config.ParentComponents)
{
// Check if the posted component exists in the current components
if (!currentresourcecomponents.Exists(x => x.Name == thisparentcomponent.Name))
{
// Add the custom component
ResourceComponent newcustomcomponent = new()
{
Name = thisparentcomponent.Name,
DisplayName = thisparentcomponent.Name,
IsCustom = true
};
serviceResponse = await ResourceComponentService.PostItem(newcustomcomponent);
if (serviceResponse.Success)
{
// Add the new custom component to the list
currentresourcecomponents.Add(newcustomcomponent);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
}
if (config.CustomComponents.Count > 0)
{
// Loop through custom components to make sure the parent exists
foreach (CustomComponent thiscustomcomponent in config.CustomComponents)
{
if (currentresourcecomponents.Where(x => GeneralHelper.NormalizeName(x.Name, true) == thiscustomcomponent.ParentComponent).Any())
{
newcustomcomponents.Add(thiscustomcomponent);
}
}
// Update the custom component options
serviceResponse = await CustomComponentService.PostConfig(newcustomcomponents);
if (!serviceResponse.Success)
{
return BadRequest(serviceResponse.ResponseObject);
}
}
return Ok("Custom Component configuration updated!");
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<CustomComponentsController>/5
/// <summary>
/// This function will delete the specifed custom component data.
/// </summary>
/// <param name="id">int - Custom component id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await CustomComponentService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,79 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ImportExportController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ImportExportController>
/// <summary>
/// This function will export the current configuration data (all components) as a single JSON file.
/// </summary>
/// <returns>json - JSON configuration file</returns>
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> ExportConfiguration(bool includeAdmin = false)
{
try
{
serviceResponse = await ImportExportService.ExportConfig(includeAdmin);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ImportExportController>
/// <summary>
/// This function will import the provided configuration data (all components). This will overwrite the existing configuration data.
/// </summary>
/// <param name="configdata">ConfigurationData (json) - Tool configuration File</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> ImportConfiguration([FromBody] ConfigurationData configdata)
{
try
{
serviceResponse = await ImportExportService.PostConfig(configdata);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,52 @@
using AzureNamingTool.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using System.Net;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class PolicyController : ControllerBase
{
//private ServiceResponse serviceResponse = new();
// GET: api/<PolicyController>
//[HttpGet]
//[Route("[action]")]
//public async Task<IActionResult> GetPolicyDefinition()
//{
// try
// {
// serviceResponse = await PolicyService.GetPolicy();
// //MemoryStream stream = serviceResponse.ResponseObject;
// if (serviceResponse.Success)
// {
// var stream = new MemoryStream();
// var writer = new StreamWriter(stream);
// writer.Write(serviceResponse.ResponseObject);
// writer.Flush();
// stream.Position = 0;
// return File(stream,"application/json" , "namePolicyDefinition.json");
// }
// else
// {
// return BadRequest(serviceResponse.ResponseObject);
// }
// }
// catch (Exception ex)
// {
// AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
// return BadRequest(ex);
// }
//}
}
}

View File

@@ -0,0 +1,135 @@
using AzureNamingTool.Attributes;
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceComponentsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<resourcecomponentsController>
/// <summary>
/// This function will return the components data.
/// </summary>
/// <param name="admin">bool - All/Only-enabled components flag</param>
/// <returns>json - Current components data</returns>
[HttpGet]
public async Task<IActionResult> Get(bool admin = false)
{
try
{
serviceResponse = await ResourceComponentService.GetItems(admin);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<resourcecomponentsController>/5
/// <summary>
/// This function will return the specifed resource component data.
/// </summary>
/// <param name="id">int - Resource Component id</param>
/// <returns>json - Resource component data</returns>
[HttpGet("{id:int}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceComponentService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceComponentsController>
/// <summary>
/// This function will create/update the specified component data.
/// </summary>
/// <param name="item">ResourceComponent (json) - Component data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceComponent item)
{
try
{
serviceResponse = await ResourceComponentService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceEnvironmentsController>
/// <summary>
/// This function will update all components data.
/// </summary>
/// <param name="items">List - ResourceComponent (json) - All components configuration data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceComponent> items)
{
try
{
serviceResponse = await ResourceComponentService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch(Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,140 @@
using AzureNamingTool.Attributes;
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceDelimitersController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET api/<ResourceDelimitersController>
/// <summary>
/// This function will return the delimiters data.
/// </summary>
/// <param name="admin">bool - All/Only-enabled delimiters flag</param>
/// <returns>json - Current delimiters data</returns>
[HttpGet]
public async Task<IActionResult> Get(bool admin = false)
{
try
{
serviceResponse = await ResourceDelimiterService.GetItems(admin);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceDelimitersController>/5
/// <summary>
/// This function will return the specifed resource delimiter data.
/// </summary>
/// <param name="id">int - Resource Delimiter id</param>
/// <returns>json - Resource delimiter data</returns>
[HttpGet("{id:int}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceDelimiterService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceDelimitersController>
/// <summary>
/// This function will create/update the specified delimiter data.
/// </summary>
/// <param name="item">ResourceDelimiter (json) - Delimiter data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceDelimiter item)
{
try
{
serviceResponse = await ResourceDelimiterService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<resourcedelimitersController>
/// <summary>
/// This function will update all delimiters data.
/// </summary>
/// <param name="items">List - ResourceDelimiter (json) - All delimiters data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceDelimiter> items)
{
try
{
serviceResponse = await ResourceDelimiterService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch(Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,162 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceEnvironmentsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceEnvironmentsController>
/// <summary>
/// This function will return the environments data.
/// </summary>
/// <returns>json - Current environments data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
serviceResponse = await ResourceEnvironmentService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceEnvironmentsController>/5
/// <summary>
/// This function will return the specifed environment data.
/// </summary>
/// <param name="id">int - Environment id</param>
/// <returns>json - Environment data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
serviceResponse = await ResourceEnvironmentService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceEnvironmentsController>
/// <summary>
/// This function will create/update the specified environment data.
/// </summary>
/// <param name="item">ResourceEnvironment (json) - Environment data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceEnvironment item)
{
try
{
serviceResponse = await ResourceEnvironmentService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceEnvironmentsController>
/// <summary>
/// This function will update all environments data.
/// </summary>
/// <param name="items">List - ResourceEnvironment (json) - All environments data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceEnvironment> items)
{
try
{
serviceResponse = await ResourceEnvironmentService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<ResourceEnvironmentsController>/5
/// <summary>
/// This function will delete the specifed environment data.
/// </summary>
/// <param name="id">int - Environment id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await ResourceEnvironmentService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,165 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceFunctionsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceFunctionsController>
/// <summary>
/// This function will return the functions data.
/// </summary>
/// <returns>json - Current functions data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
// Get list of items
serviceResponse = await ResourceFunctionService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceFunctionsController>/5
/// <summary>
/// This function will return the specifed function data.
/// </summary>
/// <param name="id">int - Function id</param>
/// <returns>json - Function data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
serviceResponse = await ResourceFunctionService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceFunctionsController>
/// <summary>
/// This function will create/update the specified function data.
/// </summary>
/// <param name="item">ResourceFunction (json) - Function data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceFunction item)
{
try
{
serviceResponse = await ResourceFunctionService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceFunctionsController>
/// <summary>
/// This function will update all functions data.
/// </summary>
/// <param name="items">List - ResourceFunction (json) - All functions data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceFunction> items)
{
try
{
serviceResponse = await ResourceFunctionService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<ResourceFunctionsController>/5
/// <summary>
/// This function will delete the specifed function data.
/// </summary>
/// <param name="id">int - Function id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await ResourceFunctionService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,150 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceLocationsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceLocationsController>
/// <summary>
/// This function will return the locations data.
/// </summary>
/// <returns>json - Current locations data</returns>
[HttpGet]
public async Task<IActionResult> Get(bool admin = false)
{
try
{
serviceResponse = await ResourceLocationService.GetItems(admin);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceLocationsController>/5
/// <summary>
/// This function will return the specifed location data.
/// </summary>
/// <param name="id">int - Location id</param>
/// <returns>json - Location data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
serviceResponse = await ResourceLocationService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
//// POST api/<ResourceLocationsController>
//[HttpPost]
//public async Task<IActionResult> Post([FromBody] ResourceLocation item)
//{
// try
// {
// serviceResponse = await ResourceLocationService.PostItem(item);
// if (serviceResponse.Success)
// {
// return Ok(serviceResponse.ResponseObject);
// }
// else
// {
// return BadRequest(serviceResponse.ResponseObject);
// }
// }
// catch (Exception ex)
// {
// return BadRequest(ex);
// }
//}
// POST api/<ResourceLocationsController>
/// <summary>
/// This function will update all locations data.
/// </summary>
/// <param name="items">List - ResourceLocation (json) - All locations data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceLocation> items)
{
try
{
serviceResponse = await ResourceLocationService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
//// DELETE api/<ResourceLocationsController>/5
//[HttpDelete("{id}")]
//public async Task<IActionResult> Delete(int id)
//{
// try
// {
// serviceResponse = await ResourceLocationService.DeleteItem(id);
// if (serviceResponse.Success)
// {
// return Ok(serviceResponse.ResponseObject);
// }
// else
// {
// return BadRequest(serviceResponse.ResponseObject);
// }
// }
// catch (Exception ex)
// {
// return BadRequest(ex);
// }
//}
}
}

View File

@@ -0,0 +1,86 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Text;
using System.Collections;
using System.Threading;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceNamingRequestsController : ControllerBase
{
// POST api/<ResourceNamingRequestsController>
/// <summary>
/// This function will generate a resoure type name for specifed component values. This function requires full definition for all components. It is recommended to use the RequestName API function for name generation.
/// </summary>
/// <param name="request">ResourceNameRequestWithComponents (json) - Resource Name Request data</param>
/// <returns>string - Name generation response</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> RequestNameWithComponents([FromBody] ResourceNameRequestWithComponents request)
{
try
{
ResourceNameResponse resourceNameRequestResponse = await ResourceNamingRequestService.RequestNameWithComponents(request);
if (resourceNameRequestResponse.Success)
{
return Ok(resourceNameRequestResponse);
}
else
{
return BadRequest(resourceNameRequestResponse);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex.Message);
}
}
// POST api/<ResourceNamingRequestsController>
/// <summary>
/// This function will generate a resoure type name for specifed component values, using a simple data format.
/// </summary>
/// <param name="request">ResourceNameRequest (json) - Resource Name Request data</param>
/// <returns>string - Name generation response</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> RequestName([FromBody] ResourceNameRequest request)
{
try
{
request.CreatedBy = "API";
ResourceNameResponse resourceNameRequestResponse = await ResourceNamingRequestService.RequestName(request);
if (resourceNameRequestResponse.Success)
{
return Ok(resourceNameRequestResponse);
}
else
{
return BadRequest(resourceNameRequestResponse);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex.Message);
}
}
}
}

View File

@@ -0,0 +1,165 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceOrgsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceOrgsController>
/// <summary>
/// This function will return the orgs data.
/// </summary>
/// <returns>json - Current orgs data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
// Get list of items
serviceResponse = await ResourceOrgService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceOrgsController>/5
/// <summary>
/// This function will return the specifed org data.
/// </summary>
/// <param name="id">int - Org id</param>
/// <returns>json - Org data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceOrgService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceOrgsController>
/// <summary>
/// This function will create/update the specified org data.
/// </summary>
/// <param name="item">ResourceOrg (json) - Org data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceOrg item)
{
try
{
serviceResponse = await ResourceOrgService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceOrgsController>
/// <summary>
/// This function will update all orgs data.
/// </summary>
/// <param name="items">List - ResourceOrg (json) - All orgs data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceOrg> items)
{
try
{
serviceResponse = await ResourceOrgService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<ResourceOrgsController>/5
/// <summary>
/// This function will delete the specifed org data.
/// </summary>
/// <param name="id">int - Org id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await ResourceOrgService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,164 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceProjAppSvcsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceProjAppSvcsController>
/// <summary>
/// This function will return the projects/apps/services data.
/// </summary>
/// <returns>json - Current projects/apps/servicse data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
serviceResponse = await ResourceProjAppSvcService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceProjAppSvcsController>/5
/// <summary>
/// This function will return the specifed project/app/service data.
/// </summary>
/// <param name="id">int - Project/App/Service id</param>
/// <returns>json - Project/App/Service data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceProjAppSvcService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceProjAppSvcsController>
/// <summary>
/// This function will create/update the specified project/app/service data.
/// </summary>
/// <param name="item">ResourceProjAppSvc (json) - Project/App/Service data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceProjAppSvc item)
{
try
{
serviceResponse = await ResourceProjAppSvcService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceProjAppSvcsController>
/// <summary>
/// This function will update all projects/apps/services data.
/// </summary>
/// <param name="items">List - ResourceProjAppSvc (json) - All projects/apps/services data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceProjAppSvc> items)
{
try
{
serviceResponse = await ResourceProjAppSvcService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<ResourceProjAppSvcsController>/5
/// <summary>
/// This function will delete the specifed project/app/service data.
/// </summary>
/// <param name="id">int - Project/App?service id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await ResourceProjAppSvcService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,183 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceTypesController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceTypesController>
/// <summary>
/// This function will return the resource types data.
/// </summary>
/// <returns>json - Current resource types data</returns>
[HttpGet]
public async Task<IActionResult> Get(bool admin = false)
{
try
{
// Get list of items
serviceResponse = await ResourceTypeService.GetItems(admin);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceTypesController>/5
/// <summary>
/// This function will return the specifed resource type data.
/// </summary>
/// <param name="id">int - Resource Type id</param>
/// <returns>json - Resource Type data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceTypeService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
//// POST api/<ResourceTypesController>
//[HttpPost]
//public async Task<IActionResult> Post([FromBody] ResourceType item)
//{
// try
// {
// serviceResponse = await ResourceTypeService.PostItem(item);
// if (serviceResponse.Success)
// {
// return Ok(serviceResponse.ResponseObject);
// }
// else
// {
// return BadRequest(serviceResponse.ResponseObject);
// }
// }
// catch (Exception ex)
// {
// return BadRequest(ex);
// }
//}
// POST api/<ResourceTypesController>
/// <summary>
/// This function will update all resource types data.
/// </summary>
/// <param name="items">List - ResourceType (json) - All resource types data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceType> items)
{
try
{
serviceResponse = await ResourceTypeService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceTypesController>
/// <summary>
/// This function will update all resource types for the specifed component
/// </summary>
/// <param name="operation">string - Operation type (optional-add, optional-remove, exlcude-add, exclude-remove)</param>
/// <param name="componentid">int - Component ID</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> UpdateTypeComponents(string operation, int componentid)
{
try
{
serviceResponse = await ResourceTypeService.UpdateTypeComponents(operation, componentid);
if (serviceResponse.Success)
{
return Ok("Resource Types updated!");
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
//// DELETE api/<ResourceTypesController>/5
//[HttpDelete("{id}")]
//public async Task<IActionResult> Delete(int id)
//{
// try
// {
// serviceResponse = await ResourceTypeService.DeleteItem(id);
// if (serviceResponse.Success)
// {
// return Ok(serviceResponse.ResponseObject);
// }
// else
// {
// return BadRequest(serviceResponse.ResponseObject);
// }
// }
// catch (Exception ex)
// {
// return BadRequest(ex);
// }
//}
}
}

View File

@@ -0,0 +1,164 @@
using AzureNamingTool.Models;
using AzureNamingTool.Helpers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AzureNamingTool.Services;
using AzureNamingTool.Attributes;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace AzureNamingTool.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKey]
public class ResourceUnitDeptsController : ControllerBase
{
private ServiceResponse serviceResponse = new();
// GET: api/<ResourceUnitDeptsController>
/// <summary>
/// This function will return the units/depts data.
/// </summary>
/// <returns>json - Current units/depts data</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
// Get list of items
serviceResponse = await ResourceUnitDeptService.GetItems();
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// GET api/<ResourceUnitDeptsController>/5
/// <summary>
/// This function will return the specifed unit/dept data.
/// </summary>
/// <param name="id">int - Unit/Dept id</param>
/// <returns>json - Unit/Dept data</returns>
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
try
{
// Get list of items
serviceResponse = await ResourceUnitDeptService.GetItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceUnitDeptsController>
/// <summary>
/// This function will create/update the specified unit/dept data.
/// </summary>
/// <param name="item">ResourceUnitDept (json) - Unit/Dept data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ResourceUnitDept item)
{
try
{
serviceResponse = await ResourceUnitDeptService.PostItem(item);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// POST api/<ResourceUnitDeptsController>
/// <summary>
/// This function will update all units/depts data.
/// </summary>
/// <param name="items">List - ResourceUnitDept (json) - All units/depts data</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpPost]
[Route("[action]")]
public async Task<IActionResult> PostConfig([FromBody] List<ResourceUnitDept> items)
{
try
{
serviceResponse = await ResourceUnitDeptService.PostConfig(items);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
// DELETE api/<ResourceUnitDeptsController>/5
/// <summary>
/// This function will delete the specifed unit/dept data.
/// </summary>
/// <param name="id">int - Unit/Dept id</param>
/// <returns>bool - PASS/FAIL</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
try
{
serviceResponse = await ResourceUnitDeptService.DeleteItem(id);
if (serviceResponse.Success)
{
return Ok(serviceResponse.ResponseObject);
}
else
{
return BadRequest(serviceResponse.ResponseObject);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return BadRequest(ex);
}
}
}
}

View File

@@ -0,0 +1,100 @@
targetScope = 'resourceGroup'
param location string
param currentTime string = utcNow()
param managedIdName string
param managedUserId string
param websiteName string
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2019-10-01-preview' = {
name: 'ds_createAppReg'
location: location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${resourceId(resourceGroup().name, 'Microsoft.ManagedIdentity/userAssignedIdentities', managedIdName)}': {}
}
}
properties: {
azPowerShellVersion: '7.0'
arguments: '-resourceName ${websiteName} -managedUserId ${managedUserId}'
scriptContent: '''
param([string] $resourceName, [string] $managedIdentityID)
connect-azaccount -Identity
$token = (Get-AzAccessToken -ResourceUrl https://graph.microsoft.com).Token
$headers = @{'Content-Type' = 'application/json'; 'Authorization' = 'Bearer ' + $token}
$bodyassign = @{
"@odata.type" = "#microsoft.graph.unifiedRoleAssignment"
roleDefinitionId = "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"
principalId = $managedUserId
directoryScopeId = "/"
}
Invoke-RestMethod -Method POST -Headers $headers -Uri "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments" -Body ($bodyassign | ConvertTo-Json)
start-sleep -s 10 # allow time for role assignment to propigate
$template = @{
displayName = $resourceName
requiredResourceAccess = @(
@{
resourceAppId = "00000003-0000-0000-c000-000000000000"
resourceAccess = @(
@{
id = "311a71cc-e848-46a1-bdf8-97ff7156d8e6"
type = "Scope"
}
)
}
)
signInAudience = "AzureADMyOrg"
}
# Upsert App registration
$app = (Invoke-RestMethod -Method Get -Headers $headers -Uri "https://graph.microsoft.com/beta/applications?filter=displayName eq '$($resourceName)'").value
$principal = @{}
if ($app) {
$ignore = Invoke-RestMethod -Method Patch -Headers $headers -Uri "https://graph.microsoft.com/beta/applications/$($app.id)" -Body ($template | ConvertTo-Json -Depth 10)
$principal = (Invoke-RestMethod -Method Get -Headers $headers -Uri "https://graph.microsoft.com/beta/servicePrincipals?filter=appId eq '$($app.appId)'").value
} else {
$app = (Invoke-RestMethod -Method Post -Headers $headers -Uri "https://graph.microsoft.com/beta/applications" -Body ($template | ConvertTo-Json -Depth 10))
$principal = Invoke-RestMethod -Method POST -Headers $headers -Uri "https://graph.microsoft.com/beta/servicePrincipals" -Body (@{ "appId" = $app.appId } | ConvertTo-Json)
}
# Creating client secret
$app = (Invoke-RestMethod -Method Get -Headers $headers -Uri "https://graph.microsoft.com/beta/applications/$($app.id)")
foreach ($password in $app.passwordCredentials) {
Write-Host "Deleting secret with id: $($password.keyId)"
$body = @{
"keyId" = $password.keyId
}
$ignore = Invoke-RestMethod -Method POST -Headers $headers -Uri "https://graph.microsoft.com/beta/applications/$($app.id)/removePassword" -Body ($body | ConvertTo-Json)
}
$body = @{
"passwordCredential" = @{
"displayName"= "Client Secret"
}
}
$secret = (Invoke-RestMethod -Method POST -Headers $headers -Uri "https://graph.microsoft.com/beta/applications/$($app.id)/addPassword" -Body ($body | ConvertTo-Json)).secretText
$DeploymentScriptOutputs = @{}
$DeploymentScriptOutputs['objectId'] = $app.id
$DeploymentScriptOutputs['clientId'] = $app.appId
$DeploymentScriptOutputs['clientSecret'] = $secret
$DeploymentScriptOutputs['principalId'] = $principal.id
'''
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
forceUpdateTag: currentTime // ensures script will run every time
}
}
output objectId string = deploymentScript.properties.outputs.objectId
output clientId string = deploymentScript.properties.outputs.clientId
output clientSecret string = deploymentScript.properties.outputs.clientSecret
output principalId string = deploymentScript.properties.outputs.principalId

View File

@@ -0,0 +1,46 @@
// Web App - define connection to user GitHub which will create action to update when the user's fork is updated
// Need to figure out how to get image in Linux vs Windows format - dependant on the docker.exe architecture/build
param location string = resourceGroup().location
param FileShareName string
param storageAccountName string
// Storage Account
resource sa 'Microsoft.Storage/storageAccounts@2021-06-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
minimumTlsVersion: 'TLS1_2'
}
}
resource fileShare 'Microsoft.Storage/storageAccounts/fileServices/shares@2021-04-01' = {
name: '${sa.name}/default/${FileShareName}'
}
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {
name: 'AzNamingTool'
location: location
}
resource roleAssignmentRG 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().name, managedIdentity.name)
properties: {
principalId: managedIdentity.properties.principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions','b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor over RG
principalType: 'ServicePrincipal'
}
}
output managedIdentityPrincipalId string = managedIdentity.properties.principalId
output managedIdentityName string = managedIdentity.name
output storageAccountResID string = sa.id
output storageAccountAPI string = sa.apiVersion
output fileshareinfo string = fileShare.name

View File

@@ -0,0 +1,71 @@
// Web App - define connection to user GitHub which will create action to update when the user's fork is updated
// Need to figure out how to get image in Linux vs Windows format - dependant on the docker.exe architecture/build
param location string = resourceGroup().location
param storageAccountName string
param storageAccountResId string
param storageAccountAPI string
param appServicePlanName string
// param appRegClientId string
param webSiteName string
// Create Azure Web App
resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
name: appServicePlanName
location: location
properties: {
reserved: true
}
sku: {
name: 'F1'
}
kind: 'linux'
}
resource appService 'Microsoft.Web/sites@2020-06-01' = {
name: webSiteName
location: location
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
netFrameworkVersion: 'v4.0'
linuxFxVersion: 'DOTNETCORE|6.0'
ftpsState: 'Disabled'
}
}
}
resource appServiceConfigStorage 'Microsoft.Web/sites/config@2021-03-01' = {
name: 'azurestorageaccounts'
parent: appService
properties: {
aznamingtooldata: {
type: 'AzureFiles'
accountName: storageAccountName
shareName: 'aznamingtooldata'
mountPath: '/app/settings'
accessKey: listKeys(storageAccountResId, storageAccountAPI).keys[0].value
}
}
}
/* REMOVED DUE TO Complexity with adding user to Azure AD role from deployment script
resource appServiceConfigAuth 'Microsoft.Web/sites/config@2021-03-01' = {
name: 'authsettingsV2'
parent: appService
properties: {
globalValidation: {
requireAuthentication: true
}
identityProviders: {
azureActiveDirectory: {
enabled: true
registration: {
clientId: appRegClientId
}
}
}
}
}
*/

View File

@@ -0,0 +1,74 @@
// Web App - define connection to user GitHub which will create action to update when the user's fork is updated
targetScope = 'subscription'
// Need to figure out how to get image in Linux vs Windows format - dependant on the docker.exe architecture/build
@minLength(3)
@maxLength(5)
@description('Company Name Identifier. (3-5 characters)')
param companyName string = 'fta'
@description('Location / Region for deployment.')
param location string = deployment().location
@description('File Share Folder Name.')
param FileShareName string = 'aznamingtooldata'
@description('Resource Group Name.')
param ResourceGroupName string
@description('The name of the Storage Account')
param storageAccountName string = 'stor${companyName}${uniqueString(ResourceGroupName)}'
var appServicePlanName = 'appsvcplan-aznamingtool'
var webSiteName = '${companyName}-aznamingtool'
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-01-01' = {
name: ResourceGroupName
location: location
}
module rgManageIdStor './modules/rgManageIdStor.bicep' = {
scope: resourceGroup
name: 'mod_${resourceGroup.name}_manageIdStor'
params: {
FileShareName: FileShareName
location: location
storageAccountName: storageAccountName
}
}
/* REMOVED DUE TO Complexities in assigning AD Roles via deployment script
module dsCreateAppReg './modules/ds_createAppReg.bicep' = {
scope: resourceGroup
name: 'mod_ds_CreateAppRegistration'
params: {
location: location
managedUserId: rgManageIdStor.outputs.managedIdentityPrincipalId
managedIdName: rgManageIdStor.outputs.managedIdentityName
websiteName: webSiteName
}
}
*/
module rgWebApp './modules/rgWebApp.bicep' = {
scope: resourceGroup
name: 'mod_${resourceGroup.name}_WebApp'
params: {
appServicePlanName: appServicePlanName
location: location
storageAccountName: storageAccountName
storageAccountResId: rgManageIdStor.outputs.storageAccountResID
storageAccountAPI: rgManageIdStor.outputs.storageAccountAPI
// appRegClientId: dsCreateAppReg.outputs.clientId
webSiteName: webSiteName
}
}

View File

@@ -0,0 +1,291 @@
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.10.61.36676",
"templateHash": "10370991779270488412"
}
},
"parameters": {
"companyName": {
"type": "string",
"metadata": {
"description": "Company Name Identifier. (3-5 characters)"
},
"maxLength": 5,
"minLength": 3
},
"location": {
"type": "string",
"defaultValue": "[deployment().location]",
"metadata": {
"description": "Location / Region for deployment."
}
},
"FileShareName": {
"type": "string",
"defaultValue": "aznamingtooldata",
"metadata": {
"description": "File Share Folder Name."
}
},
"ResourceGroupName": {
"type": "string",
"metadata": {
"description": "Resource Group Name."
}
},
"storageAccountName": {
"type": "string",
"defaultValue": "[format('stor{0}{1}', parameters('companyName'), uniqueString(parameters('ResourceGroupName')))]",
"metadata": {
"description": "The name of the Storage Account"
}
}
},
"variables": {
"appServicePlanName": "appsvcplan-aznamingtool",
"webSiteName": "[format('{0}-aznamingtool', parameters('companyName'))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2021-01-01",
"name": "[parameters('ResourceGroupName')]",
"location": "[parameters('location')]"
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-10-01",
"name": "[format('mod_{0}_manageIdStor', parameters('ResourceGroupName'))]",
"resourceGroup": "[parameters('ResourceGroupName')]",
"properties": {
"expressionEvaluationOptions": {
"scope": "inner"
},
"mode": "Incremental",
"parameters": {
"FileShareName": {
"value": "[parameters('FileShareName')]"
},
"location": {
"value": "[parameters('location')]"
},
"storageAccountName": {
"value": "[parameters('storageAccountName')]"
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.10.61.36676",
"templateHash": "11915341606082044459"
}
},
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"FileShareName": {
"type": "string"
},
"storageAccountName": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-06-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"minimumTlsVersion": "TLS1_2"
}
},
{
"type": "Microsoft.Storage/storageAccounts/fileServices/shares",
"apiVersion": "2021-04-01",
"name": "[format('{0}/default/{1}', parameters('storageAccountName'), parameters('FileShareName'))]",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
]
},
{
"type": "Microsoft.ManagedIdentity/userAssignedIdentities",
"apiVersion": "2022-01-31-preview",
"name": "AzNamingTool",
"location": "[parameters('location')]"
},
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2022-04-01",
"name": "[guid(resourceGroup().name, 'AzNamingTool')]",
"properties": {
"principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'AzNamingTool')).principalId]",
"roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
"principalType": "ServicePrincipal"
},
"dependsOn": [
"[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'AzNamingTool')]"
]
}
],
"outputs": {
"managedIdentityPrincipalId": {
"type": "string",
"value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', 'AzNamingTool')).principalId]"
},
"managedIdentityName": {
"type": "string",
"value": "AzNamingTool"
},
"storageAccountResID": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]"
},
"storageAccountAPI": {
"type": "string",
"value": "2021-06-01"
},
"fileshareinfo": {
"type": "string",
"value": "[format('{0}/default/{1}', parameters('storageAccountName'), parameters('FileShareName'))]"
}
}
}
},
"dependsOn": [
"[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('ResourceGroupName'))]"
]
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-10-01",
"name": "[format('mod_{0}_WebApp', parameters('ResourceGroupName'))]",
"resourceGroup": "[parameters('ResourceGroupName')]",
"properties": {
"expressionEvaluationOptions": {
"scope": "inner"
},
"mode": "Incremental",
"parameters": {
"appServicePlanName": {
"value": "[variables('appServicePlanName')]"
},
"location": {
"value": "[parameters('location')]"
},
"storageAccountName": {
"value": "[parameters('storageAccountName')]"
},
"storageAccountResId": {
"value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('ResourceGroupName')), 'Microsoft.Resources/deployments', format('mod_{0}_manageIdStor', parameters('ResourceGroupName')))).outputs.storageAccountResID.value]"
},
"storageAccountAPI": {
"value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('ResourceGroupName')), 'Microsoft.Resources/deployments', format('mod_{0}_manageIdStor', parameters('ResourceGroupName')))).outputs.storageAccountAPI.value]"
},
"webSiteName": {
"value": "[variables('webSiteName')]"
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.10.61.36676",
"templateHash": "17792755002068494409"
}
},
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
},
"storageAccountName": {
"type": "string"
},
"storageAccountResId": {
"type": "string"
},
"storageAccountAPI": {
"type": "string"
},
"appServicePlanName": {
"type": "string"
},
"webSiteName": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2020-06-01",
"name": "[parameters('appServicePlanName')]",
"location": "[parameters('location')]",
"properties": {
"reserved": true
},
"sku": {
"name": "F1"
},
"kind": "linux"
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2020-06-01",
"name": "[parameters('webSiteName')]",
"location": "[parameters('location')]",
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"siteConfig": {
"netFrameworkVersion": "v4.0",
"linuxFxVersion": "DOTNETCORE|6.0",
"ftpsState": "Disabled"
}
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]"
]
},
{
"type": "Microsoft.Web/sites/config",
"apiVersion": "2021-03-01",
"name": "[format('{0}/{1}', parameters('webSiteName'), 'azurestorageaccounts')]",
"properties": {
"aznamingtooldata": {
"type": "AzureFiles",
"accountName": "[parameters('storageAccountName')]",
"shareName": "aznamingtooldata",
"mountPath": "/app/settings",
"accessKey": "[listKeys(parameters('storageAccountResId'), parameters('storageAccountAPI')).keys[0].value]"
}
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('webSiteName'))]"
]
}
]
}
},
"dependsOn": [
"[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('ResourceGroupName'))]",
"[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('ResourceGroupName')), 'Microsoft.Resources/deployments', format('mod_{0}_manageIdStor', parameters('ResourceGroupName')))]"
]
}
]
}

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["AzureNamingTool.csproj", "."]
RUN dotnet restore "./AzureNamingTool.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "AzureNamingTool.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "AzureNamingTool.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AzureNamingTool.dll"]

84
Helpers/CacheHelper.cs Normal file
View File

@@ -0,0 +1,84 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using System.Runtime.Caching;
using System.Text;
namespace AzureNamingTool.Helpers
{
public class CacheHelper
{
public static object GetCacheObject(string cachekey)
{
try
{
ObjectCache memoryCache = MemoryCache.Default;
var encodedCache = memoryCache.Get(cachekey);
if (encodedCache == null)
{
return null;
}
else
{
return (object)encodedCache;
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return null;
}
}
public static void SetCacheObject(string cachekey, object cachedata)
{
try
{
ObjectCache memoryCache = MemoryCache.Default;
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(600.0),
};
memoryCache.Set(cachekey, cachedata, cacheItemPolicy);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static void InvalidateCacheObject(string cachekey)
{
try
{
ObjectCache memoryCache = MemoryCache.Default;
memoryCache.Remove(cachekey);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static string GetAllCacheData()
{
StringBuilder data = new();
try
{
ObjectCache memoryCache = MemoryCache.Default;
var cacheKeys = memoryCache.Select(kvp => kvp.Key).ToList();
foreach (var key in cacheKeys.OrderBy(x => x))
{
data.Append("<p><strong>" + key + "</strong></p><div class=\"alert alert-secondary\" style=\"word-wrap:break-word;\">" + MemoryCache.Default[key].ToString() + "</div>");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
data.Append("<p><strong>No data currently cached.</strong></p>");
}
return data.ToString();
}
}
}

View File

@@ -0,0 +1,832 @@
using AzureNamingTool.Models;
using System.Text.Json.Serialization;
using System.Text.Json;
using AzureNamingTool.Services;
using System.Xml.Linq;
using System.Net.NetworkInformation;
using System.Net;
using System.Runtime.Caching;
using System.Reflection;
using System.Net.Http;
using System.Text;
using System.Net.Http.Json;
using System.Net.Http.Headers;
using System;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Mvc;
namespace AzureNamingTool.Helpers
{
public class ConfigurationHelper
{
public static SiteConfiguration GetConfigurationData()
{
var config = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("settings/appsettings.json")
.Build()
.Get<SiteConfiguration>();
return config;
}
public static string GetAppSetting(string key, bool decrypt = false)
{
string value = null;
try
{
// Check if the data is cached
var data = CacheHelper.GetCacheObject(key);
if (data == null)
{
var config = GetConfigurationData();
// Check if the app setting is already set
if (config.GetType().GetProperty(key) != null)
{
value = config.GetType().GetProperty(key).GetValue(config, null).ToString();
// Verify the value is encrypted, and should be decrypted
if ((decrypt) && (value != "") && (GeneralHelper.IsBase64Encoded(value)))
{
value = GeneralHelper.DecryptString(value, config.SALTKey);
}
// Set the result to cache
CacheHelper.SetCacheObject(key, value);
}
else
{
// Create a new configuration object and get the default for the property
SiteConfiguration newconfig = new();
value = newconfig.GetType().GetProperty(key).GetValue(newconfig, null).ToString();
// Set the result to the app settings
SetAppSetting(key, value, decrypt);
// Set the result to cache
CacheHelper.SetCacheObject(key, value);
}
}
else
{
value = data.ToString();
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return value;
}
public static void SetAppSetting(string key, string value, bool encrypt = false)
{
try
{
var config = GetConfigurationData();
string valueoriginal = value;
if (encrypt)
{
value = GeneralHelper.EncryptString(value, config.SALTKey);
}
Type type = config.GetType();
System.Reflection.PropertyInfo propertyInfo = type.GetProperty(key);
propertyInfo.SetValue(config, value, null);
UpdateSettings(config);
// Save the original value to the cache
CacheHelper.SetCacheObject(key, valueoriginal);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static async void VerifyConfiguration(StateContainer state)
{
try
{
// Get all the files in teh repository folder
DirectoryInfo repositoryDir = new("repository");
foreach (FileInfo file in repositoryDir.GetFiles())
{
// Check if the file exists in the settings folder
if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/" + file.Name)))
{
// Copy the repository file to the settings folder
file.CopyTo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/" + file.Name));
}
}
// Migrate old data to new files, if needed
// Check if the admin log file exists in the settings folder and the adminmessages does not
if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/adminlog.json")) && !File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/adminlogmessages.json")))
{
// Migrate the data
FileSystemHelper.MigrateDataToFile("adminlog.json", "settings/", "adminlogmessages.json", "settings/", true);
}
// Sync cnfiguration data
if (!state.ConfigurationDataSynced)
{
await SyncConfigurationData("ResourceComponent");
state.SetConfigurationDataSynced(true);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static void VerifySecurity(StateContainer state)
{
try
{
var config = GetConfigurationData();
if (!state.Verified)
{
if (config.SALTKey == "")
{
// Create a new SALT key
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new();
var salt = new string(Enumerable.Repeat(chars, 16)
.Select(s => s[random.Next(s.Length)]).ToArray());
config.SALTKey = salt.ToString();
config.APIKey = GeneralHelper.EncryptString(config.APIKey, salt.ToString());
if (config.AdminPassword != "")
{
config.AdminPassword = GeneralHelper.EncryptString(config.AdminPassword, config.SALTKey.ToString());
state.Password = true;
}
else
{
state.Password = false;
}
}
if (config.AdminPassword != "")
{
state.Password = true;
}
else
{
state.Password = false;
}
UpdateSettings(config);
}
state.SetVerified(true);
// Set the site theme
state.SetAppTheme(config.AppTheme);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static bool VerifyConnectivity()
{
bool pingsuccessful = false;
bool result = false;
try
{
// Check if the data is cached
var data = CacheHelper.GetCacheObject("isconnected");
if (data == null)
{
// Check if the connectivity check is enabled
if (Convert.ToBoolean(ConfigurationHelper.GetAppSetting("ConnectivityCheckEnabled")))
{
// Atempt to ping a url first
Ping ping = new();
String host = "github.com";
byte[] buffer = new byte[32];
int timeout = 1000;
PingOptions pingOptions = new();
try
{
PingReply reply = ping.Send(host, timeout, buffer, pingOptions);
if (reply.Status == IPStatus.Success)
{
pingsuccessful = true;
result = true;
}
}
catch (Exception)
{
// Catch this exception but continue to try a web request instead
}
// If ping is not successful, attempt to download a file
if (!pingsuccessful)
{
// Atempt to download a file
var request = (HttpWebRequest)WebRequest.Create("https://github.com/mspnp/AzureNamingTool/blob/main/connectiontest.png");
request.KeepAlive = false;
request.Timeout = 1500;
using var response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
result = true;
}
else
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = "Connectivity Check Failed:" + response.StatusDescription });
}
}
}
else
{
result = true;
}
}
else
{
result = (bool)data;
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = "There was a problem verifying connectivty. Error: " + ex.Message });
}
// Set the result to cache
CacheHelper.SetCacheObject("isconnected", result);
return result;
}
public async static Task<List<T>> GetList<T>()
{
var items = new List<T>();
try
{
// Check if the data is cached
String data = (string)CacheHelper.GetCacheObject(typeof(T).Name);
// Load the data from the file system.
if ((data == null) || (data == ""))
{
data = typeof(T).Name switch
{
nameof(ResourceComponent) => await FileSystemHelper.ReadFile("resourcecomponents.json"),
nameof(ResourceEnvironment) => await FileSystemHelper.ReadFile("resourceenvironments.json"),
nameof(Models.ResourceLocation) => await FileSystemHelper.ReadFile("resourcelocations.json"),
nameof(ResourceOrg) => await FileSystemHelper.ReadFile("resourceorgs.json"),
nameof(ResourceProjAppSvc) => await FileSystemHelper.ReadFile("resourceprojappsvcs.json"),
nameof(Models.ResourceType) => await FileSystemHelper.ReadFile("resourcetypes.json"),
nameof(ResourceUnitDept) => await FileSystemHelper.ReadFile("resourceunitdepts.json"),
nameof(ResourceFunction) => await FileSystemHelper.ReadFile("resourcefunctions.json"),
nameof(ResourceDelimiter) => await FileSystemHelper.ReadFile("resourcedelimiters.json"),
nameof(CustomComponent) => await FileSystemHelper.ReadFile("customcomponents.json"),
nameof(AdminLogMessage) => await FileSystemHelper.ReadFile("adminlogmessages.json"),
nameof(GeneratedName) => await FileSystemHelper.ReadFile("generatednames.json"),
nameof(AdminUser) => await FileSystemHelper.ReadFile("adminusers.json"),
_ => "[]",
};
CacheHelper.SetCacheObject(typeof(T).Name, data);
}
if (data != "[]")
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
items = JsonSerializer.Deserialize<List<T>>(data, options);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return items;
}
public async static Task WriteList<T>(List<T> items)
{
try
{
switch (typeof(T).Name)
{
case nameof(ResourceComponent):
await FileSystemHelper.WriteConfiguation(items, "resourcecomponents.json");
break;
case nameof(ResourceEnvironment):
await FileSystemHelper.WriteConfiguation(items, "resourceenvironments.json");
break;
case nameof(Models.ResourceLocation):
await FileSystemHelper.WriteConfiguation(items, "resourcelocations.json");
break;
case nameof(ResourceOrg):
await FileSystemHelper.WriteConfiguation(items, "resourceorgs.json");
break;
case nameof(ResourceProjAppSvc):
await FileSystemHelper.WriteConfiguation(items, "resourceprojappsvcs.json");
break;
case nameof(Models.ResourceType):
await FileSystemHelper.WriteConfiguation(items, "resourcetypes.json");
break;
case nameof(ResourceUnitDept):
await FileSystemHelper.WriteConfiguation(items, "resourceunitdepts.json");
break;
case nameof(ResourceFunction):
await FileSystemHelper.WriteConfiguation(items, "resourcefunctions.json");
break;
case nameof(ResourceDelimiter):
await FileSystemHelper.WriteConfiguation(items, "resourcedelimiters.json");
break;
case nameof(CustomComponent):
await FileSystemHelper.WriteConfiguation(items, "customcomponents.json");
break;
case nameof(AdminLogMessage):
await FileSystemHelper.WriteConfiguation(items, "adminlogmessages.json");
break;
case nameof(GeneratedName):
await FileSystemHelper.WriteConfiguation(items, "generatednames.json");
break;
case nameof(AdminUser):
await FileSystemHelper.WriteConfiguation(items, "adminusers.json");
break;
default:
break;
}
String data = String.Empty;
data = typeof(T).Name switch
{
nameof(ResourceComponent) => await FileSystemHelper.ReadFile("resourcecomponents.json"),
nameof(ResourceEnvironment) => await FileSystemHelper.ReadFile("resourceenvironments.json"),
nameof(Models.ResourceLocation) => await FileSystemHelper.ReadFile("resourcelocations.json"),
nameof(ResourceOrg) => await FileSystemHelper.ReadFile("resourceorgs.json"),
nameof(ResourceProjAppSvc) => await FileSystemHelper.ReadFile("resourceprojappsvcs.json"),
nameof(Models.ResourceType) => await FileSystemHelper.ReadFile("resourcetypes.json"),
nameof(ResourceUnitDept) => await FileSystemHelper.ReadFile("resourceunitdepts.json"),
nameof(ResourceFunction) => await FileSystemHelper.ReadFile("resourcefunctions.json"),
nameof(ResourceDelimiter) => await FileSystemHelper.ReadFile("resourcedelimiters.json"),
nameof(CustomComponent) => await FileSystemHelper.ReadFile("customcomponents.json"),
nameof(AdminLogMessage) => await FileSystemHelper.ReadFile("adminlogmessages.json"),
nameof(GeneratedName) => await FileSystemHelper.ReadFile("generatednames.json"),
nameof(AdminUser) => await FileSystemHelper.ReadFile("adminusers.json"),
_ => "[]",
};
// Update the cache with the latest data
CacheHelper.SetCacheObject(typeof(T).Name, data);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static async void UpdateSettings(SiteConfiguration config)
{
// Clear the cache
ObjectCache memoryCache = MemoryCache.Default;
List<string> cacheKeys = memoryCache.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
memoryCache.Remove(cacheKey);
}
var jsonWriteOptions = new JsonSerializerOptions()
{
WriteIndented = true
};
jsonWriteOptions.Converters.Add(new JsonStringEnumConverter());
var newJson = JsonSerializer.Serialize(config, jsonWriteOptions);
var appSettingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/appsettings.json");
await FileSystemHelper.WriteFile("appsettings.json", newJson);
}
public static async Task<string> GetOfficalConfigurationFileVersionData()
{
string versiondata = null;
try
{
versiondata = await GeneralHelper.DownloadString("https://raw.githubusercontent.com/microsoft/CloudAdoptionFramework/master/ready/AzNamingTool/configurationfileversions.json");
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return versiondata;
}
public static async Task<string> GetCurrentConfigFileVersionData()
{
string versiondatajson = null;
try
{
versiondatajson = await FileSystemHelper.ReadFile("configurationfileversions.json");
// Check if the user has any version data. This value will be '[]' if not.
if (versiondatajson == "[]")
{
// Create new version data with default values in /settings file
ConfigurationFileVersionData? versiondata = new();
await FileSystemHelper.WriteFile("configurationfileversions.json", JsonSerializer.Serialize(versiondata), "settings/");
versiondatajson = JsonSerializer.Serialize(versiondata);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return versiondatajson;
}
public static async Task<List<string>> VerifyConfigurationFileVersionData()
{
List<string> versiondata = new();
try
{
// Get the official version from GitHub
ConfigurationFileVersionData? officialversiondata = new();
var officialdatajson = await GetOfficalConfigurationFileVersionData();
// Get the current version
ConfigurationFileVersionData? currentversiondata = new();
var currentdatajson = await GetCurrentConfigFileVersionData();
// Determine if the version data is different
if ((officialdatajson != null) && (currentdatajson != null))
{
officialversiondata = JsonSerializer.Deserialize<ConfigurationFileVersionData>(officialdatajson);
currentversiondata = JsonSerializer.Deserialize<ConfigurationFileVersionData>(currentdatajson);
// Compare the versions
// Resource Types
if (officialversiondata.resourcetypes != currentversiondata.resourcetypes)
{
versiondata.Add("<h5>Resource Types</h5><hr /><div>Your resource types configuration is out of date!<br /><br />It is recommended that you refresh your resource types to the latest configuration.<br /><br /><strong>To Refresh:</strong><ul><li>Expand the <strong>Types</strong> section</li><li>Expand the <strong>Configuration</strong> section</li><li>Select the <strong>Refresh</strong> option</li></ul></div><br />");
}
// Resource Locations
if (officialversiondata.resourcelocations != currentversiondata.resourcelocations)
{
versiondata.Add("<h5>Resource Locations</h5><hr /><div>Your resource locations configuration is out of date!<br /><br />It is recommended that you refresh your resource locations to the latest configuration.<br /><br /><strong>To Refresh:</strong><ul><li>Expand the <strong>Locations</strong> section</li><li>Expand the <strong>Configuration</strong> section</li><li>Select the <strong>Refresh</strong> option</li></ul></div><br />");
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return versiondata;
}
public static async Task UpdateConfigurationFileVersion(string fileName)
{
if (VerifyConnectivity())
{
try
{
// Get the official version from GitHub
ConfigurationFileVersionData? officialversiondata = new();
var officialdatajson = await GetOfficalConfigurationFileVersionData();
// Get the current version
ConfigurationFileVersionData? currentversiondata = new();
var currentdatajson = await GetCurrentConfigFileVersionData();
// Determine if the version data is different
if ((officialdatajson != null) && (currentdatajson != null))
{
officialversiondata = JsonSerializer.Deserialize<ConfigurationFileVersionData>(officialdatajson);
currentversiondata = JsonSerializer.Deserialize<ConfigurationFileVersionData>(currentdatajson);
switch (fileName)
{
case "resourcetypes":
currentversiondata.resourcetypes = officialversiondata.resourcetypes;
break;
case "resourcelocations":
currentversiondata.resourcelocations = officialversiondata.resourcelocations;
break;
}
// Update the current configuration file version data
await FileSystemHelper.WriteFile("configurationfileversions.json", JsonSerializer.Serialize(currentversiondata), "settings/");
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}
public static bool ResetSiteConfiguration()
{
bool result = false;
try
{
// Get all the files in teh repository folder
DirectoryInfo repositoryDir = new("repository");
// Filter out teh appsettings.json to retain admin credentials
foreach (FileInfo file in repositoryDir.GetFiles().Where(x => x.Name != "appsettings.json"))
{
// Copy the repository file to the settings folder
file.CopyTo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/" + file.Name), true);
}
// Clear the cache
ObjectCache memoryCache = MemoryCache.Default;
List<string> cacheKeys = memoryCache.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
memoryCache.Remove(cacheKey);
}
result = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return result;
}
public static void ResetState(StateContainer state)
{
state.SetVerified(false);
state.SetAdmin(false);
state.SetPassword(false);
state.SetAppTheme("bg-default text-dark");
}
public static async Task<string> GetToolVersion()
{
try
{
var response = await GeneralHelper.DownloadString("https://raw.githubusercontent.com/microsoft/CloudAdoptionFramework/master/ready/AzNamingTool/AzureNamingTool.csproj");
XDocument xdoc = XDocument.Parse(response);
string result = xdoc
.Descendants("PropertyGroup")
.Descendants("Version")
.First()
.Value;
return result;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return null;
}
}
public static async Task<string> GetVersionAlert(bool forceDisplay = false)
{
string alert = "";
try
{
VersionAlert versionalert = new();
bool dismissed = false;
string appversion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
// Check if version alert has been dismissed
var dismissedalerts = GetAppSetting("DismissedAlerts").Split(',');
if (dismissedalerts != null)
{
if (dismissedalerts.Contains(appversion))
{
dismissed = true;
}
}
if ((!dismissed) || (forceDisplay))
{
// Check if the data is cached
var cacheddata = CacheHelper.GetCacheObject("versionalert-" + appversion);
if (cacheddata == null)
{
// Get the alert
List<VersionAlert> versionAlerts = new();
string data = await FileSystemHelper.ReadFile("versionalerts.json", "");
if (data != null)
{
var items = new List<VersionAlert>();
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
items = JsonSerializer.Deserialize<List<VersionAlert>>(data, options).ToList();
versionalert = items.Where(x => x.Version == appversion).FirstOrDefault();
if (versionalert != null)
{
alert = versionalert.Alert;
// Set the result to cache
CacheHelper.SetCacheObject("versionalert-" + appversion, versionalert.Alert);
}
}
}
else
{
alert = (string)cacheddata;
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return alert;
}
public static void DismissVersionAlert()
{
try
{
string appversion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
List<string> dismissedalerts = new(GetAppSetting("DismissedAlerts").Split(','));
if (!dismissedalerts.Contains(appversion))
{
if (string.Join(",", dismissedalerts) == "")
{
dismissedalerts.Clear();
}
dismissedalerts.Add(appversion);
}
SetAppSetting("DismissedAlerts", string.Join(",", dismissedalerts));
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
public static bool VerifyDuplicateNamesAllowed()
{
bool result = false;
try
{
// Check if the data is cached
var cacheddata = CacheHelper.GetCacheObject("duplicatenamesallowed");
if (cacheddata == null)
{
// Check if version alert has been dismissed
var allowed = GetAppSetting("DuplicateNamesAllowed");
result = Convert.ToBoolean(allowed);
// Set the result to cache
CacheHelper.SetCacheObject("duplicatenamesallowed", result);
}
else
{
result = Convert.ToBoolean(cacheddata);
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return result;
}
public static async Task<bool> PostToGenerationWebhook(string URL, GeneratedName generatedName)
{
bool result = false;
try
{
HttpClient httpClient = new()
{
BaseAddress = new Uri(URL)
};
HttpResponseMessage response = await httpClient.PostAsJsonAsync("", generatedName);
if (response.IsSuccessStatusCode)
{
result = true;
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Generated Name (" + generatedName.ResourceName + ") successfully posted to webhook!" });
}
else
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Generated Name (" + generatedName.ResourceName + ") not successfully posted to webhook! " + response.ReasonPhrase });
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Generated Name (" + generatedName.ResourceName + ") not successfully posted to webhook! " + ex.Message });
}
return result;
}
public static async Task<string> GetProgramSetting(string programSetting)
{
string result = String.Empty;
try
{
string data = (string)CacheHelper.GetCacheObject(programSetting);
if (String.IsNullOrEmpty(data))
{
var response = await GeneralHelper.DownloadString("https://raw.githubusercontent.com/aznamingtool/AzureNamingTool/main/programsettings.json");
var setting = JsonDocument.Parse(response);
result = setting.RootElement.GetProperty(programSetting).ToString();
CacheHelper.SetCacheObject(programSetting, result);
}
else
{
result = data;
}
return result;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return result;
}
/// <summary>
/// This function is used to sync default configuration data with the user's local version
/// </summary>
/// <param name="type">string - Type of configuration data to sync</param>
public static async Task SyncConfigurationData(string type)
{
try
{
bool update = false;
switch (type)
{
case "ResourceComponent":
// Get all the existing components
List<ResourceComponent> currentComponents = new();
ServiceResponse serviceResponse = new();
serviceResponse = await ResourceComponentService.GetItems(true);
if (serviceResponse.Success)
{
currentComponents = serviceResponse.ResponseObject;
// Get the default component data
List<ResourceComponent> defaultComponents = new();
string data = await FileSystemHelper.ReadFile("resourcecomponents.json", "repository/");
if (!String.IsNullOrEmpty(data))
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
if (!String.IsNullOrEmpty(data))
{
defaultComponents = JsonSerializer.Deserialize<List<ResourceComponent>>(data, options);
}
// Loop over the existing components to verify the data is complete
foreach (ResourceComponent currentComponent in currentComponents)
{
// Create a new component for any updates
ResourceComponent newComponent = currentComponent;
// Get the matching default component for the current component
ResourceComponent defaultcomponent = defaultComponents.Find(x => x.Name == currentComponent.Name);
// Check the data to see if it's been configured
if (String.IsNullOrEmpty(currentComponent.MinLength))
{
if (defaultcomponent != null)
{
newComponent.MinLength = defaultcomponent.MinLength;
}
else
{
newComponent.MinLength = "1";
}
update = true;
}
// Check the data to see if it's been configured
if (String.IsNullOrEmpty(currentComponent.MaxLength))
{
if (defaultcomponent != null)
{
newComponent.MaxLength = defaultcomponent.MaxLength;
}
else
{
newComponent.MaxLength = "10";
}
update = true;
}
if (update)
{
await ResourceComponentService.PostItem(newComponent);
}
}
}
}
break;
}
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}
}

133
Helpers/FileSystemHelper.cs Normal file
View File

@@ -0,0 +1,133 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.NetworkInformation;
using System.Reflection.Metadata;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
namespace AzureNamingTool.Helpers
{
public class FileSystemHelper
{
public static async Task<string> ReadFile(string fileName, string folderName = "settings/")
{
await CheckFile(folderName + fileName);
string data = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName + fileName));
return data;
}
public static async Task WriteFile(string fileName, string content, string folderName = "settings/")
{
await CheckFile(folderName + fileName);
int retries = 0;
while (retries < 10)
{
try
{
using FileStream fstr = File.Open(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName + fileName), FileMode.Truncate, FileAccess.Write);
StreamWriter sw = new(fstr);
sw.Write(content);
sw.Flush();
sw.Dispose();
return;
}
catch (Exception)
{
Thread.Sleep(50);
retries++;
}
}
}
public static async Task CheckFile(string fileName)
{
if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName)))
{
var file = File.Create(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName));
file.Close();
for (int numTries = 0; numTries < 10; numTries++)
{
try
{
await File.WriteAllTextAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName), "[]");
return;
}
catch (IOException)
{
Thread.Sleep(50);
}
}
}
}
public static async Task<object> WriteConfiguation(object configdata, string configFileName)
{
try
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
await FileSystemHelper.WriteFile(configFileName, JsonSerializer.Serialize(configdata, options));
return "Config updated.";
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return ex;
}
}
public static bool ResetConfiguration(string filename)
{
bool result = false;
try
{
// Get all the files in the repository folder
DirectoryInfo dirRepository = new("repository");
foreach (FileInfo file in dirRepository.GetFiles())
{
if (file.Name == filename)
{
// Copy the repository file to the settings folder
file.CopyTo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/" + file.Name), true);
result = true;
// Clear any cached data
break;
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return result;
}
public static async Task MigrateDataToFile(string sourcefileName, string sourcefolderName, string destinationfilename, string destinationfolderName, bool delete)
{
// Get the source data
string data = await ReadFile(sourcefileName, sourcefolderName);
// Write the destination data
await WriteFile(destinationfilename, data, destinationfolderName);
// Check if the source file should be removed (In repository and settings folders)
if (delete)
{
File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "repository/" + sourcefileName));
File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/" + sourcefileName));
}
}
}
}

116
Helpers/GeneralHelper.cs Normal file
View File

@@ -0,0 +1,116 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using System.Security.Cryptography;
using System.Text;
namespace AzureNamingTool.Helpers
{
public class GeneralHelper
{
//Function to get the Property Value
public static object GetPropertyValue(object SourceData, string propName)
{
try
{
return SourceData.GetType().GetProperty(propName).GetValue(SourceData, null);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return null;
}
}
public static string EncryptString(string text, string keyString)
{
byte[] iv = new byte[16];
byte[] array;
using (Aes aes = Aes.Create())
{
aes.KeySize = 256;
aes.Key = Encoding.UTF8.GetBytes(keyString);
aes.IV = iv;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using MemoryStream memoryStream = new();
using CryptoStream cryptoStream = new((Stream)memoryStream, encryptor, CryptoStreamMode.Write);
using (StreamWriter streamWriter = new((Stream)cryptoStream))
{
streamWriter.Write(text);
}
array = memoryStream.ToArray();
}
return Convert.ToBase64String(array);
}
public static string DecryptString(string cipherText, string keyString)
{
byte[] iv = new byte[16];
byte[] buffer = Convert.FromBase64String(cipherText);
using Aes aes = Aes.Create();
aes.KeySize = 256;
aes.Key = Encoding.UTF8.GetBytes(keyString);
aes.IV = iv;
aes.Padding = PaddingMode.PKCS7;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using MemoryStream memoryStream = new(buffer);
using CryptoStream cryptoStream = new((Stream)memoryStream, decryptor, CryptoStreamMode.Read);
using StreamReader streamReader = new((Stream)cryptoStream);
return streamReader.ReadToEnd();
}
public static bool IsBase64Encoded(string value)
{
bool base64encoded = false;
try
{
byte[] byteArray = Convert.FromBase64String(value);
base64encoded = true;
}
catch (FormatException)
{
// The string is not base 64. Dismiss the error and return false
}
return base64encoded;
}
public static async Task<string> DownloadString(string url)
{
string data;
try
{
HttpClient httpClient = new();
data = await httpClient.GetStringAsync(url);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
data = "";
}
return data;
}
public static string NormalizeName(string name, bool lowercase)
{
string newname = name.Replace("Resource", "").Replace(" ", "");
if (lowercase)
{
newname = newname.ToLower();
}
return newname;
}
public static string SetTextEnabledClass(bool enabled)
{
if (enabled)
{
return "";
}
else
{
return "disabled-text";
}
}
}
}

53
Helpers/IdentityHelper.cs Normal file
View File

@@ -0,0 +1,53 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using System;
namespace AzureNamingTool.Helpers
{
public class IdentityHelper
{
public static async Task<bool> IsAdminUser(StateContainer state, ProtectedSessionStorage session, string name)
{
bool result = false;
try
{
// Check if the username is in the list of Admin Users
ServiceResponse serviceResponse = await AdminUserService.GetItems();
if (serviceResponse.Success)
{
List<AdminUser> adminusers = serviceResponse.ResponseObject;
if (adminusers.Exists(x => x.Name.ToLower() == name.ToLower()))
{
state.SetAdmin(true);
await session.SetAsync("admin", true);
result = true;
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return result;
}
public static async Task<string> GetCurrentUser(ProtectedSessionStorage session)
{
string currentuser = "System";
try
{
var currentuservalue = await session.GetAsync<string>("currentuser");
if (!String.IsNullOrEmpty(currentuservalue.Value))
{
currentuser = currentuservalue.Value;
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return currentuser;
}
}
}

160
Helpers/LogHelper.cs Normal file
View File

@@ -0,0 +1,160 @@
using AzureNamingTool.Models;
using System.Text.Json;
namespace AzureNamingTool.Helpers
{
public class LogHelper
{
/// <summary>
/// This function gets the gernated names log.
/// </summary>
/// <returns>List of GeneratedNames - List of generated names</returns>
public static async Task<List<GeneratedName>> GetGeneratedNames()
{
List<GeneratedName> lstGeneratedNames = new();
try
{
string data = await FileSystemHelper.ReadFile("generatednames.json");
var items = new List<GeneratedName>();
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
lstGeneratedNames = JsonSerializer.Deserialize<List<GeneratedName>>(data, options).OrderByDescending(x => x.CreatedOn).ToList();
}
catch (Exception ex)
{
LogHelper.LogAdminMessage("ERROR", ex.Message);
}
return lstGeneratedNames;
}
/// <summary>
/// This function logs the generated name.
/// </summary>
/// <param name="lstGeneratedName">GeneratedName - Generated name and components.</param>
public static async void LogGeneratedName(GeneratedName lstGeneratedName)
{
try
{
// Log the created name
var lstGeneratedNames = new List<GeneratedName>();
lstGeneratedNames = await GetGeneratedNames();
if (lstGeneratedNames.Count > 0)
{
lstGeneratedName.Id = lstGeneratedNames.Max(x => x.Id) + 1;
}
else
{
lstGeneratedName.Id = 1;
}
lstGeneratedNames.Add(lstGeneratedName);
var jsonGeneratedNames = JsonSerializer.Serialize(lstGeneratedNames);
await FileSystemHelper.WriteFile("generatednames.json", jsonGeneratedNames);
CacheHelper.InvalidateCacheObject("GeneratedName");
}
catch (Exception ex)
{
LogHelper.LogAdminMessage("ERROR", ex.Message);
}
}
/// <summary>
/// This function prugres the generated names log.
/// </summary>
/// <returns>void</returns>
public static async Task PurgeGeneratedNames()
{
try
{
await FileSystemHelper.WriteFile("generatednames.json", "[]");
CacheHelper.InvalidateCacheObject("GeneratedName");
}
catch (Exception ex)
{
LogHelper.LogAdminMessage("ERROR", ex.Message);
}
}
/// <summary>
/// This function returns the Admin log.
/// </summary>
/// <returns>List of AdminLogMessages - List of Adming Log messages.</returns>
public static async Task<List<AdminLogMessage>> GetAdminLog()
{
List<AdminLogMessage> lstAdminLogMessages = new();
try
{
string data = await FileSystemHelper.ReadFile("adminlogmessages.json");
var items = new List<AdminLogMessage>();
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
lstAdminLogMessages = JsonSerializer.Deserialize<List<AdminLogMessage>>(data, options).OrderByDescending(x => x.CreatedOn).ToList();
}
catch (Exception ex)
{
LogHelper.LogAdminMessage("ERROR", ex.Message);
}
return lstAdminLogMessages;
}
/// <summary>
/// This function logs the Admin message.
/// </summary>
/// <param name="title">string - Message title</param>
/// <param name="message">string - MEssage body</param>
public static async void LogAdminMessage(string title, string message)
{
try
{
AdminLogMessage adminmessage = new()
{
Id = 1,
Title = title,
Message = message
};
// Log the created name
var lstAdminLogMessages = new List<AdminLogMessage>();
lstAdminLogMessages = await GetAdminLog();
if (lstAdminLogMessages.Count > 0)
{
adminmessage.Id = lstAdminLogMessages.Max(x => x.Id) + 1;
}
lstAdminLogMessages.Add(adminmessage);
var jsonAdminLogMessages = JsonSerializer.Serialize(lstAdminLogMessages);
await FileSystemHelper.WriteFile("adminlogmessages.json", jsonAdminLogMessages);
CacheHelper.InvalidateCacheObject("AdminLogMessage");
}
catch (Exception)
{
// No exception is logged due to this function being the function that would complete the action.
}
}
/// <summary>
/// This function purges the Admin log.
/// </summary>
/// <returns>void</returns>
public static async Task PurgeAdminLog()
{
try
{
await FileSystemHelper.WriteFile("adminlogmessages.json", "[]");
CacheHelper.InvalidateCacheObject("AdminLogMessage");
}
catch (Exception ex)
{
LogHelper.LogAdminMessage("ERROR", ex.Message);
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace AzureNamingTool.Helpers
{
public enum MessageTypesEnum
{
SUCCESS,
INFORMATION,
WARNING,
ERROR
}
}

49
Helpers/ServicesHelper.cs Normal file
View File

@@ -0,0 +1,49 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
namespace AzureNamingTool.Helpers
{
public class ServicesHelper
{
private static ServiceResponse serviceReponse = new();
public static async Task<ServicesData> LoadServicesData(ServicesData servicesData, bool admin)
{
try
{
serviceReponse = await ResourceComponentService.GetItems(admin);
servicesData.ResourceComponents = (List<ResourceComponent>)serviceReponse.ResponseObject;
serviceReponse = await ResourceDelimiterService.GetItems(admin);
servicesData.ResourceDelimiters = (List<ResourceDelimiter>)serviceReponse.ResponseObject;
serviceReponse = await ResourceEnvironmentService.GetItems();
servicesData.ResourceEnvironments = (List<ResourceEnvironment>)serviceReponse.ResponseObject;
serviceReponse = await ResourceLocationService.GetItems(admin);
servicesData.ResourceLocations = (List<ResourceLocation>)serviceReponse.ResponseObject;
serviceReponse = await ResourceOrgService.GetItems();
servicesData.ResourceOrgs = (List<ResourceOrg>)serviceReponse.ResponseObject;
serviceReponse = await ResourceProjAppSvcService.GetItems();
servicesData.ResourceProjAppSvcs = (List<ResourceProjAppSvc>)serviceReponse.ResponseObject;
serviceReponse = await ResourceTypeService.GetItems(admin);
servicesData.ResourceTypes = (List<ResourceType>)serviceReponse.ResponseObject;
serviceReponse = await ResourceUnitDeptService.GetItems();
servicesData.ResourceUnitDepts = (List<ResourceUnitDept>)serviceReponse.ResponseObject;
serviceReponse = await ResourceFunctionService.GetItems();
servicesData.ResourceFunctions = (List<ResourceFunction>)serviceReponse.ResponseObject;
serviceReponse = await CustomComponentService.GetItems();
servicesData.CustomComponents = (List<CustomComponent>)serviceReponse.ResponseObject;
serviceReponse = await GeneratedNamesService.GetItems();
servicesData.GeneratedNames = (List<GeneratedName>)serviceReponse.ResponseObject;
serviceReponse = await AdminLogService.GetItems();
servicesData.AdminLogMessages = (List<AdminLogMessage>)serviceReponse.ResponseObject;
serviceReponse = await AdminUserService.GetItems();
servicesData.AdminUsers = (List<AdminUser>)serviceReponse.ResponseObject;
return servicesData;
}
catch(Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return servicesData;
}
}
}
}

228
Helpers/ValidationHelper.cs Normal file
View File

@@ -0,0 +1,228 @@
using AzureNamingTool.Models;
using AzureNamingTool.Services;
using System.Text.RegularExpressions;
using System.Text;
namespace AzureNamingTool.Helpers
{
public class ValidationHelper
{
public static bool ValidatePassword(string text)
{
var hasNumber = new Regex(@"[0-9]+");
var hasUpperChar = new Regex(@"[A-Z]+");
var hasMinimum8Chars = new Regex(@".{8,}");
var isValidated = hasNumber.IsMatch(text) && hasUpperChar.IsMatch(text) && hasMinimum8Chars.IsMatch(text);
return isValidated;
}
public static async Task<bool> ValidateShortName(string type, string value, string parentcomponent = null)
{
bool valid = false;
try
{
ResourceComponent resourceComponent = new();
List<ResourceComponent> resourceComponents = new();
ServiceResponse serviceResponse;
// Get the current components
serviceResponse = await ResourceComponentService.GetItems(true);
if (serviceResponse.Success)
{
resourceComponents = (List<ResourceComponent>)serviceResponse.ResponseObject;
// Check if it's a custom component
if (type == "CustomComponent")
{
resourceComponent = resourceComponents.Find(x => GeneralHelper.NormalizeName(x.Name, true) == GeneralHelper.NormalizeName(parentcomponent, true));
}
else
{
resourceComponent = resourceComponents.Find(x => x.Name == type);
}
if (resourceComponent != null)
{
// Check if the name mathces the length requirements for the component
if ((value.Length >= (Convert.ToInt32(resourceComponent.MinLength)) && (value.Length <= Convert.ToInt32(resourceComponent.MaxLength))))
{
valid = true;
}
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
return valid;
}
public static Tuple<bool, string, StringBuilder> ValidateGeneratedName(Models.ResourceType resourceType, string name, string delimiter)
{
try
{
bool valid = true;
StringBuilder sbMessage = new();
// Check regex
// Validate the name against the resource type regex
Regex regx = new(resourceType.Regx);
Match match = regx.Match(name);
if (!match.Success)
{
// Strip the delimiter in case that is causing the issue
if (delimiter != "")
{
// Strip the delimiter in case that is causing the issue
name = name.Replace(delimiter, "");
Match match2 = regx.Match(name);
if (!match2.Success)
{
sbMessage.Append("Regex failed - Please review the Resource Type Naming Guidelines.");
sbMessage.Append(Environment.NewLine);
valid = false;
}
else
{
sbMessage.Append("The specified delimiter is not allowed for this resource type and has been removed.");
sbMessage.Append(Environment.NewLine);
}
}
else
{
sbMessage.Append("The specified delimiter is not allowed for this resource type and has been removed.");
sbMessage.Append(Environment.NewLine);
}
}
// Check min length
if (int.TryParse(resourceType.LengthMin, out _))
{
if (name.Length < int.Parse(resourceType.LengthMin))
{
sbMessage.Append("Generated name is less than the minimum length for the selected resource type.");
sbMessage.Append(Environment.NewLine);
valid = false;
}
}
// Check max length
if (int.TryParse(resourceType.LengthMax, out _))
{
if (name.Length > int.Parse(resourceType.LengthMax))
{
// Strip the delimiter in case that is causing the issue
name = name.Replace(delimiter, "");
if (name.Length > int.Parse(resourceType.LengthMax))
{
sbMessage.Append("Generated name is more than the maximum length for the selected resource type.");
sbMessage.Append(Environment.NewLine);
sbMessage.Append("Please remove any optional components or contact your admin to update the required components for this resource type.");
sbMessage.Append(Environment.NewLine);
valid = false;
}
else
{
sbMessage.Append("Generated name with the selected delimiter is more than the maximum length for the selected resource type. The delimiter has been removed.");
sbMessage.Append(Environment.NewLine);
}
}
}
// Check invalid characters
if (resourceType.InvalidCharacters != "")
{
// Loop through each character
foreach (char c in resourceType.InvalidCharacters)
{
// Check if the name contains the character
if (name.Contains(c))
{
sbMessage.Append("Name cannot contain the following character: " + c);
sbMessage.Append(Environment.NewLine);
valid = false;
}
}
}
// Check start character
if (resourceType.InvalidCharactersStart != "")
{
// Loop through each character
foreach (char c in resourceType.InvalidCharactersStart)
{
// Check if the name contains the character
if (name.StartsWith(c))
{
sbMessage.Append("Name cannot start with the following character: " + c);
sbMessage.Append(Environment.NewLine);
valid = false;
}
}
}
// Check start character
if (resourceType.InvalidCharactersEnd != "")
{
// Loop through each character
foreach (char c in resourceType.InvalidCharactersEnd)
{
// Check if the name contains the character
if (name.EndsWith(c))
{
sbMessage.Append("Name cannot end with the following character: " + c);
sbMessage.Append(Environment.NewLine);
valid = false;
}
}
}
// Check consecutive character
if (resourceType.InvalidCharactersConsecutive != "")
{
// Loop through each character
foreach (char c in resourceType.InvalidCharactersConsecutive)
{
// Check if the name contains the character
char current = name[0];
for (int i = 1; i < name.Length; i++)
{
char next = name[i];
if ((current == next) && (current == c))
{
sbMessage.Append("Name cannot contain the following consecutive character: " + next);
sbMessage.Append(Environment.NewLine);
valid = false;
break;
}
current = next;
}
}
}
return new Tuple<bool, string, StringBuilder>(valid, name, sbMessage);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
return new Tuple<bool, string, StringBuilder>(false, name, new StringBuilder("There was a problem validating the name."));
}
}
public static bool CheckNumeric(string value)
{
Regex regx = new("^[0-9]+$");
Match match = regx.Match(value);
return match.Success;
}
public static bool CheckAlphanumeric(string value)
{
Regex regx = new("^[a-zA-Z0-9]+$");
Match match = regx.Match(value);
return match.Success;
}
}
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Azure Naming Tool
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.

11
Models/AdminLogMessage.cs Normal file
View File

@@ -0,0 +1,11 @@
namespace AzureNamingTool.Models
{
public class AdminLogMessage
{
public long? Id { get; set; } = 0;
public DateTime CreatedOn { get; set; } = DateTime.Now;
public string Title { get; set; }
public string Message { get; set; }
public string? Source { get; set; } = "System";
}
}

8
Models/AdminUser.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace AzureNamingTool.Models
{
public class AdminUser
{
public int Id { get; set; }
public string? Name { get; set; }
}
}

View File

@@ -0,0 +1,33 @@
using AzureNamingTool.Models;
using AzureNamingTool.Pages;
using System.Collections.Generic;
namespace AzureNamingTool.Models
{
public class ConfigurationData
{
public List<ResourceComponent> ResourceComponents { get; set; }
public List<ResourceDelimiter> ResourceDelimiters { get; set; }
public List<ResourceEnvironment> ResourceEnvironments { get; set; }
public List<ResourceLocation> ResourceLocations { get; set; }
public List<ResourceOrg> ResourceOrgs { get; set; }
public List<ResourceProjAppSvc> ResourceProjAppSvcs { get; set; }
public List<ResourceType> ResourceTypes { get; set; }
public List<ResourceUnitDept> ResourceUnitDepts { get; set; }
public List<ResourceFunction> ResourceFunctions { get; set; }
public List<CustomComponent> CustomComponents { get; set; }
public List<GeneratedName> GeneratedNames { get; set; }
public List<AdminLogMessage> AdminLogs { get; set; }
public List<AdminUser> AdminUsers { get; set; }
public string? SALTKey { get; set; }
public string? AdminPassword { get; set; }
public string? APIKey { get; set; }
public string? DismissedAlerts { get; set; }
public string? DuplicateNamesAllowed { get; set; } = "false";
public string? GenerationWebhook { get; set; } = string.Empty;
public string? ConnectivityCheckEnabled { get; set; } = "true";
public string? IdentityHeaderName { get; set; } = "X-MS-CLIENT-PRINCIPAL-NAME";
public string? ResourceTypeEditingAllowed { get; set; } = "false";
}
}

View File

@@ -0,0 +1,8 @@
namespace AzureNamingTool.Models
{
public class ConfigurationFileVersionData
{
public string resourcetypes { get; set; } = "0.0.0";
public string resourcelocations { get; set; } = "0.0.0";
}
}

23
Models/CustomComponent.cs Normal file
View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace AzureNamingTool.Models
{
public class CustomComponent
{
public long Id { get; set; }
[Required()]
public string ParentComponent { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
public string MinLength { get; set; } = "1";
public string MaxLength { get; set; } = "10";
}
}

View File

@@ -0,0 +1,8 @@
namespace AzureNamingTool.Models
{
public class CustomComponmentConfig
{
public List<ResourceComponent>? ParentComponents { get; set; }
public List<CustomComponent>? CustomComponents { get; set; }
}
}

12
Models/GeneratedName.cs Normal file
View File

@@ -0,0 +1,12 @@
namespace AzureNamingTool.Models
{
public class GeneratedName
{
public long Id { get; set; }
public DateTime CreatedOn { get; set; }
public string ResourceName { get; set; }
public string? ResourceTypeName { get; set; }
public List<string[]> Components { get; set; }
public string? User { get; set; } = "General";
}
}

View File

@@ -0,0 +1,8 @@
namespace AzureNamingTool.Models
{
public class IdentityProviderDetails
{
public string CurrentUser { get; set; } = "System";
public string? CurrentIdentityProvider { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureNamingTool.Models
{
public class PolicyDefinition
{
public List<PolicyProperty> Properties { get; set; } = new List<PolicyProperty>();
public PolicyDefinition(PolicyProperty property)
{
Properties.Add(property);
}
public PolicyDefinition(List<PolicyProperty> property)
{
Properties.AddRange(property);
}
public override string ToString() { return "{ \"properties\": " + String.Join(",", Properties) + "}"; }
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureNamingTool.Models
{
public enum PolicyEffects
{
Audit,
Deny
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AzureNamingTool.Models
{
public class PolicyProperty
{
public PolicyProperty(String name, string description, String mode = "all")
{
DisplayName = name;
Description = description;
Mode = mode;
}
public string DisplayName { get; set; }
public String PolicyType { get; set; }
public String Mode { get; set; }
public String Description { get; set; }
public PropertyMetadata Metadata { get; set; } = new PropertyMetadata() { Version = "1.0.0", Category = "Azure" };
public String PolicyRule { get; set; }
public override string ToString() { return "{"+ PolicyRule + "}"; }
}
public class PropertyMetadata
{
public String Version { get; set; } = "1.0.0";
public String Category { get; set; } = "Name";
public override string ToString() { return "\"metadata\": { \"version\": \""+Version+"\", \"category\": \""+Category+"\" }"; }
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Linq;
namespace AzureNamingTool.Models
{
public class PolicyRule
{
public PolicyRule(String fullName, Char delimeter = '-')
{
FullName = fullName;
Delimeter = delimeter;
}
public Char Delimeter { get; set; }
public String FullName { get; set; }
public int Level { get { return FullName.Count(p => p == Delimeter); } }
public String Name { get { return FullName.Substring(StartIndex, LastIndex - StartIndex); } }
public int Length { get { return Name.Length; } }
public int FullLength { get { return FullName.Length-1; } }
public int StartIndex { get { return BeforeLastIndexOf(FullName, Delimeter); } }
public int LastIndex { get { return FullName.LastIndexOf(Delimeter); } }
int BeforeLastIndexOf(String value, Char toFind)
{
int result = 0;
for (int i = value.LastIndexOf(toFind) - 1; i > 0; i--)
{
if (value[i] == toFind)
{
result = i+1;
break;
}
}
return result;
}
public int[] Group { get { return new int[] { Level, StartIndex, Length }; } }
public override int GetHashCode()
{
return (String.Join(',', Group) + Name).GetHashCode();
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AzureNamingTool.Models
{
public class PolicyRuleFactory
{
internal static string GetNameValidationRules(List<PolicyRule> policies, Char delimeter, PolicyEffects effect = PolicyEffects.Deny)
{
var ifHeader = "\"if\": {\"allOf\": [";
var policyGroups = policies.GroupBy(x => String.Join(',', x.Group));
var ifContent = GenerateConditions(policyGroups);
var ifFooter = "]}";
var thenContent = ", \"then\": {\"effect\":\"" + effect.ToString().ToLower() + "\"}";
return "\"policyRule\": {" + ifHeader + ifContent + ifFooter + thenContent + "}";
}
static string GetMainCondition(List<PolicyRule> conditions)
{
return "{\"not\": { \"value\": \"[substring(field('name'), " + conditions.First().StartIndex + ", " + conditions.First().Length + ")]\",\"in\": [" + String.Join(',', conditions.Select(x => "\"" + x.Name + "\"").Distinct()) + "]}}";
}
private static string GenerateConditions(IEnumerable<IGrouping<string, PolicyRule>> policyGroups, int level = 1, int startIndex = 0)
{
String result = String.Empty;
var list = policyGroups.Where(x => x.Key.StartsWith($"{level},{startIndex}")).ToList();
foreach (var levelConditions in list)
{
var header = "{\"allOf\": [";
var mainCondition = GetMainCondition(levelConditions.ToList());
var insideConditions = String.Empty;
var fullLength = levelConditions.FirstOrDefault().FullLength;
var startIndexes = policyGroups.Where(x => x.Key.StartsWith($"{level + 1}")).Select(x => Convert.ToInt32(x.Key.Split(',')[1])).Where(x => x == fullLength).Distinct().ToList();
foreach (var nextStartIndex in startIndexes)
{
insideConditions += GenerateConditions(policyGroups, level + 1, nextStartIndex);
if (startIndexes.Last() != nextStartIndex)
insideConditions += ",";
}
var footer = "]}";
if (list.Last().Key != levelConditions.Key)
footer += ",";
result += header + mainCondition + (insideConditions == String.Empty ? String.Empty : "," + insideConditions) + footer;
}
return result;
}
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace AzureNamingTool.Models
{
public class ResourceComponent
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
[Required()]
public string DisplayName { get; set; }
[Required()]
public bool Enabled { get; set; }
public int SortOrder { get; set; } = 0;
public bool IsCustom { get; set; } = false;
public bool IsFreeText { get; set; } = false;
public string? MinLength { get; set; }
public string? MaxLength { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace AzureNamingTool.Models
{
public class ResourceDelimiter
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
public string Delimiter { get; set; }
[Required()]
public bool Enabled { get; set; }
public int SortOrder { get; set; } = 0;
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceEnvironment
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceFunction
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
}
}

View File

@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace AzureNamingTool.Models
{
public class ResourceInstance
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
public int SortOrder { get; set; } = 0;
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceLocation
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public bool Enabled { get; set; } = true;
}
}

View File

@@ -0,0 +1,23 @@
namespace AzureNamingTool.Models
{
public class ResourceNameRequest
{
public string? ResourceEnvironment { get; set; }
public string? ResourceFunction { get; set; }
public string? ResourceInstance { get; set; }
public string? ResourceLocation { get; set; }
public string? ResourceOrg { get; set; }
public string? ResourceProjAppSvc { get; set; }
public string ResourceType { get; set; }
public string? ResourceUnitDept { get; set; }
/// <summary>
/// Dictionary [Custom Component Type Name],[Custom Component Short Name Value]
/// </summary>
public Dictionary<string, string>? CustomComponents { get; set; }
/// <summary>
/// long - Resource Id (example: 14)
/// </summary>
public long? ResourceId { get; set; } = 0;
public string? CreatedBy { get; set; } = "System";
}
}

View File

@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace AzureNamingTool.Models
{
public class ResourceNameRequestWithComponents
{
public ResourceDelimiter ResourceDelimiter { get; set; }
public ResourceEnvironment? ResourceEnvironment { get; set; }
public ResourceFunction? ResourceFunction { get; set; }
public string? ResourceInstance { get; set; }
public ResourceLocation? ResourceLocation { get; set; }
public ResourceOrg? ResourceOrg { get; set; }
public ResourceProjAppSvc? ResourceProjAppSvc { get; set; }
public ResourceType ResourceType { get; set; }
public ResourceUnitDept? ResourceUnitDept { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace AzureNamingTool.Models
{
public class ResourceNameResponse
{
public string ResourceName { get; set; }
public string Message { get; set; }
public bool Success { get; set; }
public GeneratedName? resourceNameDetails { get; set; }
}
}

20
Models/ResourceOrg.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceOrg
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceProjAppSvc
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
}
}

34
Models/ResourceType.cs Normal file
View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceType
{
public long Id { get; set; }
[Required()]
public string Resource { get; set; }
public string Optional { get; set; }
public string Exclude { get; set; }
public string Property { get; set; }
private string _ShortName;
[JsonPropertyName("ShortName")]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public string Scope { get; set; }
public string LengthMin { get; set; }
public string LengthMax { get; set; }
public string ValidText { get; set; }
public string InvalidText { get; set; }
public string InvalidCharacters { get; set; }
public string InvalidCharactersStart { get; set; }
public string InvalidCharactersEnd { get; set; }
public string InvalidCharactersConsecutive{ get; set; }
public string Regx { get; set; }
public string StaticValues { get; set; }
public bool Enabled { get; set; } = true;
}
}

View File

@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Models
{
public class ResourceUnitDept
{
public long Id { get; set; }
[Required()]
public string Name { get; set; }
private string _ShortName;
[Required()]
public string ShortName
{
get { return _ShortName; } // get method
set => _ShortName = value?.ToLower(); // set method
}
public int SortOrder { get; set; } = 0;
}
}

13
Models/ResponseMessage.cs Normal file
View File

@@ -0,0 +1,13 @@
using AzureNamingTool.Helpers;
using System.ComponentModel;
namespace AzureNamingTool.Models
{
public class ResponseMessage
{
public MessageTypesEnum Type { get; set; } = MessageTypesEnum.INFORMATION;
public string Header { get; set; } = "Message";
public string Message { get; set; }
public string? MessageDetails { get; set; }
}
}

11
Models/ServiceResponse.cs Normal file
View File

@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AzureNamingTool.Models
{
public class ServiceResponse
{
public bool Success { get; set; }
public dynamic ResponseObject { get; set; }
public string? ResponseMessage { get; set; }
}
}

19
Models/ServicesData.cs.cs Normal file
View File

@@ -0,0 +1,19 @@
namespace AzureNamingTool.Models
{
public class ServicesData
{
public List<ResourceComponent>? ResourceComponents { get; set; }
public List<ResourceDelimiter>? ResourceDelimiters { get; set; }
public List<ResourceEnvironment>? ResourceEnvironments { get; set; }
public List<ResourceLocation>? ResourceLocations { get; set; }
public List<ResourceOrg>? ResourceOrgs { get; set; }
public List<ResourceProjAppSvc>? ResourceProjAppSvcs { get; set; }
public List<ResourceType>? ResourceTypes { get; set; }
public List<ResourceUnitDept>? ResourceUnitDepts { get; set; }
public List<ResourceFunction>? ResourceFunctions { get; set; }
public List<CustomComponent>? CustomComponents { get; set; }
public List<GeneratedName>? GeneratedNames { get; set; }
public List<AdminLogMessage>? AdminLogMessages { get; set; }
public List<AdminUser>? AdminUsers { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
namespace AzureNamingTool.Models
{
public class SiteConfiguration
{
public string? SALTKey { get; set; }
public string? AdminPassword { get; set; }
public string? APIKey { get; set; }
public string? AppTheme { get; set; }
public bool? DevMode { get; set; } = false;
public string? DismissedAlerts { get; set; }
public string? DuplicateNamesAllowed { get; set; } = "false";
public string? GenerationWebhook { get; set; } = string.Empty;
public string? ConnectivityCheckEnabled { get; set; } = "true";
public string IdentityHeaderName { get; set; } = "X-MS-CLIENT-PRINCIPAL-NAME";
public string? ResourceTypeEditingAllowed { get; set; } = "false";
}
}

119
Models/StateContainer.cs Normal file
View File

@@ -0,0 +1,119 @@
namespace AzureNamingTool.Models
{
public class StateContainer
{
private bool? _verified;
private bool? _admin;
private bool? _password;
private string? _apptheme;
private bool? _newsenabled;
public bool _reloadnav;
public bool? _configurationdatasynced;
public bool Verified
{
get => _verified ?? false;
set
{
_verified = value;
NotifyStateChanged();
}
}
public void SetVerified(bool verified)
{
_verified = verified;
NotifyStateChanged();
}
public bool Admin
{
get => _admin ?? false;
set
{
_admin = value;
NotifyStateChanged();
}
}
public void SetAdmin(bool admin)
{
_admin = admin;
NotifyStateChanged();
}
public bool Password
{
get => _password ?? false;
set
{
_password = value;
NotifyStateChanged();
}
}
public void SetPassword(bool password)
{
_password = password;
NotifyStateChanged();
}
public string AppTheme
{
get => _apptheme ?? "bg-default text-dark";
set
{
_apptheme = value;
NotifyStateChanged();
}
}
public void SetAppTheme(string value)
{
_apptheme = value;
NotifyStateChanged();
}
public bool NewsEnabled
{
get => _newsenabled ?? true;
set
{
_newsenabled = value;
NotifyStateChanged();
}
}
public void SetNewsEnabled(bool newsenabled)
{
_newsenabled = newsenabled;
NotifyStateChanged();
}
public void SetNavReload(bool reloadnav)
{
_reloadnav = reloadnav;
NotifyStateChanged();
}
public bool ConfigurationDataSynced
{
get => _configurationdatasynced ?? false;
set
{
_configurationdatasynced = value;
NotifyStateChanged();
}
}
public void SetConfigurationDataSynced(bool configurationdatasynced)
{
_configurationdatasynced = configurationdatasynced;
NotifyStateChanged();
}
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
}
}

8
Models/ThemeInfo.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace AzureNamingTool.Models
{
public class ThemeInfo
{
public string? ThemeName { get; set; }
public string? ThemeStyle { get; set; }
}
}

9
Models/VersionAlert.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace AzureNamingTool.Models
{
public class VersionAlert
{
public int Id { get; set; }
public string? Version { get; set; }
public string? Alert { get; set; }
}
}

909
Pages/Admin.razor Normal file
View File

@@ -0,0 +1,909 @@
@page "/admin"
@using AzureNamingTool.Helpers;
@using AzureNamingTool.Models;
@using System.Runtime.Caching;
@using AzureNamingTool.Services;
@using Blazored.Toast.Configuration;
@using Microsoft.AspNetCore.Html
@using Microsoft.Extensions.Logging
@using System.Reflection;
@inject IJSRuntime JsRuntime
@inject StateContainer state
@inject ProtectedSessionStorage session
@inject IToastService toastService
@inject NavigationManager NavigationManager
@inject ProtectedLocalStorage storage
<PageTitle>Azure Naming Tool - Admin</PageTitle>
<div class="card @theme.ThemeStyle">
<div class="card-body">
<!-- Pop it above the backdrop -->
<div class="@theme.ThemeStyle">
<div style="float:left;">
<h1>
Admin
</h1>
</div>
@if (admin)
{
<div style="float:right;">
<button class="btn btn-secondary" title="Instructions" @onclick="@(e => ShowInstructionsModal())">
<span class="oi oi-document" aria-hidden="true"></span> Documentation
</button>
</div>
}
<div style="clear:both;"></div>
<div>
@if (!admin)
{
<p>
Enter the Global Admin Password to configure the Azure Naming Tool site.
</p>
<div class="form-group">
<label for="password" style="font-weight:bold">Global Admin Password</label>
<input title="Password" class="form-control mb-3" type="password" @onchange="@((ui) => { SetFormValue("login",(string)ui.Value);})" />
</div>
}
else
{
<p>
This page allows you to security settings, view cache data, and site configurations for the Azure Naming Tool.
</p>
<div class="card mb-3" style="width:auto;">
<div class="card-header bg-default text-dark">
<a id="versiondetailslink" class="link-dark text-decoration-none" data-bs-toggle="collapse" href="#versiondetails" role="button" aria-expanded="false" aria-controls="versiondetails">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> <strong>Version Details</strong>
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="versiondetails">
<div class="mb-3">
<strong>Current version:</strong> @appversion
</div>
@if (versionalert != "")
{
<div class="alert alert-warning" role="alert">
<div>
@((MarkupString)versionalert)
</div>
</div>
}
<div class="mb-3">
<button type="button" class="btn btn-success" @onclick="@(e => SubmitIssue("feature"))" title="Feature Request">Feature Request</button>
<button type="button" class="btn btn-danger" @onclick="@(e => SubmitIssue("bug"))" title="Submit a bug">Submit a bug</button>
</div>
</div>
</div>
<div class="card mb-3">
<h5 class="card-header text-white bg-secondary text-white">
<a class="text-decoration-none text-white" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#security" role="button" aria-expanded="false" aria-controls="security">
<h5><span class="oi oi-chevron-bottom" aria-hidden="true"></span> Security</h5>
</a>
</h5>
<div class="collapse show card card-body @theme.ThemeStyle" id="security">
@if (currentuser == "GlobalAdmin")
{
<div class="card mb-3">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#updatepassword" role="button" aria-expanded="false" aria-controls="updatepassword">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Global Admin Password
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="updatepassword">
<p>
Enter a new <strong>Global Admin</strong> password for the site.
</p>
<p><strong>Requirements</strong></p>
<ul>
<li>Contain a number</li>
<li>Contain one upper case letter</li>
<li>Be at least 8 characters</li>
</ul>
<div class="form-group">
<input title="New Password" id="newpassword" class="form-control mb-3" type="password" @onchange="@((ui) => { SetFormValue("password",(string)ui.Value);})" />
<button title="Save Password" type="button" disabled="@disabled" class="btn btn-success" @onclick="@(e => AdminFormAction("password"))">Save</button>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#updateapikey" role="button" aria-expanded="false" aria-controls="updateapikey">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> API Key
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="updateapikey">
<p>
The current API key is displayed. Click <strong>Generate</strong> to generate a new random API Key, or update the text to the desired value.
</p>
<div class="form-group">
<input title="API Key" id="currentapikey" class="form-control mb-3" type="text" @onchange="@((ui) => { SetFormValue("apikey",(string)ui.Value);})" value="@currentapikey" />
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("apikeysave"))" title="Save API Key">Save</button>
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("apikeygenerate"))" title="Generate API Key">Generate</button>
</div>
</div>
</div>
}
else
{
<div>
<span style="font-style:italic;">Log in with the Global Admin password to update these settings.</span>
</div>
}
</div>
</div>
<div class="card mb-3">
<h5 class="card-header text-white bg-secondary text-white">
<a class="text-decoration-none text-white" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#identityprovidersettings" role="button" aria-expanded="false" aria-controls="identityprovidersettings">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Identity Provider Settings
</a>
</h5>
<div class="collapse show card card-body @theme.ThemeStyle" id="identityprovidersettings">
@if (currentuser == "GlobalAdmin")
{
<p>
This section is used to configure the <strong>Identity Provider</strong> settings for the site if authentication is implemented. The Azure Naming Tool is designed to work with <strong><a href="https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization" target="_blank">Azure App Service Authentication</a></strong>, by default. If the Azure Naming Tool is configured to identify users, the site will track user activity. Users can also be designated as Admins using the <strong>Admin Users</strong> configuration.
</p>
<p>
You can learn more about the new Identity Provider Integration <a href=\"https://soltisweb.com/blog/detail/2023-06-azurenamingtool-identityproviderintegrationdeepdive\" target=\"_blank\"><strong>HERE</strong></a>.
</p>
@if (!String.IsNullOrEmpty(currentidentityprovider))
{
<div class="mb-3">
<p class="alert alert-success alert-dismissible fade show" role="alert">
<strong>Azure App Service Authentication Enabled!</strong><br /><br />
<strong>Current Identity provider:</strong> @currentidentityprovider.ToUpper()<br /><br />
<strong>Default Idenitty Header Name:</strong> X-MS-CLIENT-PRINCIPAL-NAME
</p>
</div>
}
<div class="card mb-3">
<div class="card card-header bg-default text-dark" style="font-weight:bold;">
<a id="identityheadername" class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;" href="#identityheadername" role="button" aria-expanded="false" aria-controls="identityheadername">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Identity Header Name
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="identityheadername">
<p>
This setting is used to identify site users when authenticating using an Identity provider. Authentication using an Identity provider will often inject headers into the request. The site will check for the specified header name and assign the value as the user's identity. This value will be used to log user activity.
</p>
<div class="form-group mb-3">
<input title="Identity Header Name" id="currentidentityheadername" class="form-control mb-3" type="text" @onchange="@((ui) => { SetFormValue("identityheadername",(string)ui.Value);})" value="@currentidentityheadername" />
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("identityheadernamesave"))" title="Save Identity Header Name">Save</button>
</div>
<p class="alert alert-info" role="alert">
<strong>NOTE</strong><br />
The Azure Naming Tool is configured for <strong><a href="https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization" target="_blank">Azure App Service Authentication</a></strong> using the <a href="https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-user-identities#access-user-claims-in-app-code" target="_blank"><strong>X-MS-CLIENT-PRINCIPAL-NAME</strong></a> header name. If using a different Identity provider, update the setting to the header name that contains the user's id.
</p>
</div>
</div>
<div class="card mb-3">
<div class="card card-header bg-default text-dark" style="font-weight:bold;">
<a id="adminusers" class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;" href="#adminusers" role="button" aria-expanded="false" aria-controls="adminusers">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Admin Users
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="adminusers">
<div class="mb-3">
<p>
When authenticating users with an Identity provider, the <strong>Identity Header Name</strong> setting can be used to idenity site users. Enter the user id below to assign the user as an <strong>Admin</strong> for the Azure Naming Tool.
</p>
@if (!String.IsNullOrEmpty(currentidentityprovider))
{
@switch (currentidentityprovider.ToLower())
{
case "aad":
<p class="alert alert-info alert-dismissible fade show" role="alert">
When using <strong>@currentidentityprovider.ToUpper()</strong> for Azure App Service Authentication, the <strong>X-MS-CLIENT-PRINCIPAL-NAME</strong> header value will be the user's email address.<br /><br /><strong>Example:</strong> example@xyz.com<br /><br />This value should be used to designate the user as an <strong>Admin</strong>.
</p>
break;
case "github":
<p class="alert alert-info alert-dismissible fade show" role="alert">
When using <strong>@currentidentityprovider.ToUpper()</strong> for Azure App Service Authentication, the <strong>X-MS-CLIENT-PRINCIPAL-NAME</strong> header value will be the user's name.<br /><br /><strong>Example:</strong> John Smith)<br /><br />This value should be used to designate the user as an <strong>Admin</strong>.
</p>
break;
case "google":
<p class="alert alert-info alert-dismissible fade show" role="alert">
When using <strong>@currentidentityprovider.ToUpper()</strong> for Azure App Service Authentication, the <strong>X-MS-CLIENT-PRINCIPAL-NAME</strong> header value will be the user's email address.<br /><br /><strong>Example:</strong>: example@gmail.com<br /><br />This value should be used to designate the user as an <strong>Admin</strong>.
</p>
break;
}
}
</div>
<div class="mb-3">
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("adminuseradd"))" title="Add Admin User">
<span class="oi oi-plus" aria-hidden="true"></span> Add Admin User
</button>
</div>
@if (servicesData.AdminUsers.Count > 0)
{
<div class="table-responsive">
<table class="table @theme.ThemeStyle">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var item in @servicesData.AdminUsers)
{
<tr>
<td>@item.Name</td>
<td>
@if (GeneralHelper.NormalizeName(currentuser, true) != GeneralHelper.NormalizeName(@item.Name, true))
{
<button type="button" class="btn btn-danger" @onclick="@(e => AdminFormAction("adminuserdelete",item.Id))" title="Delete">
<span class="oi oi-x" aria-hidden="true"></span>
</button>
}
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p style="font-style:italic;">There currently no assigned admin users.</p>
}
<p class="alert alert-warning">
<strong>NOTE</strong><br />
Added users will need to close their browser and re-open the site for the change to take effect.
</p>
</div>
</div>
}
else
{
<div>
<span style="font-style:italic;">Log in with the Global Admin password to update these settings.</span>
</div>
}
</div>
</div>
<div class="card mb-3">
<h5 class="card-header text-white bg-secondary text-white">
<a class="text-decoration-none text-white" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#cache" role="button" aria-expanded="false" aria-controls="cache">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Cache
</a>
</h5>
<div class="collapse show card card-body @theme.ThemeStyle" id="cache">
<p>
The tool uses caching for data to improve performance. Click <strong>Clear</strong> to clear all cached data.
</p>
<div class="form-group">
<p>
<button type="button" class="btn btn-primary" @onclick="@(e => AdminFormAction("viewcachedata"))" title="View Cache Data">View Cache Data</button>
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("clearcache"))" title="Clear Cache">Clear Cache Data</button>
</p>
</div>
</div>
</div>
<div class="card mb-3">
<h5 class="card-header text-white bg-secondary text-white">
<a class="text-decoration-none text-white" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#sitesettings" role="button" aria-expanded="false" aria-controls="sitesettings">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Site Settings
</a>
</h5>
<div class="collapse show card card-body @theme.ThemeStyle" id="sitesettings">
<div class="card mb-3" style="width:auto;">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#duplicatenames" role="button" aria-expanded="false" aria-controls="duplicatenames">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Allow Duplicate Names
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="duplicatenames">
<div class="mb-3">
<p>
By default, the Azure Naming Tool prevents duplicate resource names from being generated. This setting can be used to allow duplicate names.
</p>
</div>
<div>
<label class="switch" title="Duplicate Names Allowed">
<input type="checkbox" checked="@duplicatenamesallowed" @oninput='args => SettingChanged(args, "duplicatenamesallowed")'>
<span class="slider round"></span>
</label>
<span class="align-text-top"> Enable</span>
</div>
</div>
</div>
<div class="card mb-3" style="width:auto;">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#resourcetypeediting" role="button" aria-expanded="false" aria-controls="resourcetypeediting">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Resource Type Editing
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="resourcetypeediting">
<div class="mb-3">
<p>
By default, Resource Type Validation is configured to match the Azure portal. These values can be overriden by enabling the following setting.
</p>
</div>
<div>
<label class="switch" title="Resource Type Editing Enabled">
<input type="checkbox" checked="@resourcetypeeditingallowed" @oninput='args => SettingChanged(args, "resourcetypeeditingallowed")'>
<span class="slider round"></span>
</label>
<span class="align-text-top"> Enable</span>
</div>
</div>
</div>
<div class="card mb-3" style="width:auto;">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#connectivitycheck" role="button" aria-expanded="false" aria-controls="connectivitycheck">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Connectivity Check
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="connectivitycheck">
<div class="mb-3">
<p>
The Azure Naming Tool will verify the tool has internet connectivity to enable update features. Use this setting to disable the connectivity check functionality.
</p>
</div>
<div>
<label class="switch" title="Connectivity Check Enabled">
<input type="checkbox" checked="@connectivitycheckenabled" @oninput='args => SettingChanged(args, "connectivitycheckenabled")'>
<span class="slider round"></span>
</label>
<span class="align-text-top"> Enable</span>
</div>
</div>
</div>
<div class="card mb-3" style="width:auto;">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#generationwebhook" role="button" aria-expanded="false" aria-controls="generationwebhook">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Generation Webhook
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="generationwebhook">
<div class="mb-3">
<p>
The Azure Naming Tool can post generated names to a webhook. This can be used to integrate with other systems. Enter a valid URL below to save the webhook setting.
</p>
<p>
Generated names will be posted using the <strong><a href="https://github.com/mspnp/AzureNamingTool/Models/GeneratedName.cs" target="_blank">GeneratedName</a></strong> JSON format.
</p>
<p>
<button type="button" class="btn btn-primary" @onclick="@(e => AdminFormAction("samplepost"))" title="Sample Post">Sample Post</button>
</p>
</div>
<div class="mb-3">
<input title="Generation Webhook" id="currentgenerationwebhook" class="form-control" type="text" @onchange="@((ui) => { SetFormValue("generationwebhook",(string)ui.Value);})" value="@currentgenerationwebhook" />
</div>
<div>
<button type="button" class="btn btn-success w-auto" @onclick="@(e => AdminFormAction("generationwebhooksave"))" title="Save Generate Webhook">Save</button>
</div>
</div>
</div>
</div>
</div>
}
</div>
<div>
@if (!admin)
{
<button type="button" class="btn btn-success" @onclick="@(e => AdminFormAction("login"))" title="Login">Login</button>
}
</div>
</div>
</div>
</div>
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
[CascadingParameter]
private IdentityProviderDetails? identityProviderDetails { get; set; }
private ServicesData servicesData = new();
private string? currentpassword;
private string? newpassword;
private string? currentapikey;
private string? currentidentityheadername;
private string? currentidentityprovider;
private ResponseMessage message = new();
private bool admin;
private bool versionalertshown = false;
private bool disabled = true;
private bool showversionalert = false;
private bool dismissalert = false;
private bool duplicatenamesallowed = false;
private bool connectivitycheckenabled = true;
private bool resourcetypeeditingallowed = false;
private string currentgenerationwebhook = string.Empty;
private string versionalert = string.Empty;
private string? appversion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
private SiteConfiguration config = ConfigurationHelper.GetConfigurationData();
private string? currentuser;
protected override async void OnInitialized()
{
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
currentuser = await IdentityHelper.GetCurrentUser(session);
if (firstRender)
{
servicesData = await ServicesHelper.LoadServicesData(servicesData, true);
var result = await session.GetAsync<bool>("admin");
admin = (bool)result.Value;
if (admin)
{
currentapikey = GeneralHelper.DecryptString(config.APIKey, config.SALTKey);
// Determine if the version notification has been dismissed
var resultVersionAlert = await session.GetAsync<bool>("versionalert-" + appversion + "-shown");
versionalertshown = resultVersionAlert.Success ? resultVersionAlert.Value : false;
versionalert = ConfigurationHelper.GetVersionAlert(true).Result;
if (versionalert != "")
{
showversionalert = true;
}
duplicatenamesallowed = Convert.ToBoolean(ConfigurationHelper.GetAppSetting("DuplicateNamesAllowed"));
connectivitycheckenabled = Convert.ToBoolean(ConfigurationHelper.GetAppSetting("ConnectivityCheckEnabled"));
resourcetypeeditingallowed = Convert.ToBoolean(ConfigurationHelper.GetAppSetting("ResourceTypeEditingAllowed"));
currentgenerationwebhook = ConfigurationHelper.GetAppSetting("GenerationWebhook", true);
currentidentityheadername = ConfigurationHelper.GetAppSetting("IdentityHeaderName", true);
if (identityProviderDetails != null)
{
if (!String.IsNullOrEmpty(identityProviderDetails.CurrentIdentityProvider))
{
currentidentityprovider = identityProviderDetails.CurrentIdentityProvider;
}
}
}
await storage.SetAsync("apptheme", theme.ThemeStyle);
state.AppTheme = theme.ThemeStyle;
StateHasChanged();
}
}
private async void AdminFormAction(string action, int id = 0)
{
message = new ResponseMessage();
message.Header = "INFORMATION";
message.Type = MessageTypesEnum.INFORMATION;
ServiceResponse serviceResponse = new();
bool redirect = false;
bool confirm = false;
switch (action)
{
case "login":
// Check the password
if (currentpassword == GeneralHelper.DecryptString(config.AdminPassword, config.SALTKey))
{
state.SetAdmin(true);
await session.SetAsync("admin", true);
await session.SetAsync("currentuser", "GlobalAdmin");
// Load the current API Key
currentapikey = GeneralHelper.DecryptString(config.APIKey, config.SALTKey);
message.Type = MessageTypesEnum.INFORMATION;
message.Header = "INFORMATION";
message.Message = "Global Admin logged in.";
admin = true;
redirect = true;
}
else
{
state.SetAdmin(false);
await session.SetAsync("admin", false);
message.Type = MessageTypesEnum.ERROR;
message.Header = "ERROR";
message.Message = "Login failed!";
}
break;
case "logout":
state.SetAdmin(false);
await session.SetAsync("admin", false);
message.Type = MessageTypesEnum.INFORMATION;
message.Header = "INFORMATION";
message.Message = "User Logged out.";
redirect = true;
break;
case "password":
bool confirmPassword = await JsRuntime.InvokeAsync<bool>("confirm", "This will change the Global Admin Password. Are you sure?");
if (confirmPassword)
{
// Set the new admin password
serviceResponse = await AdminService.UpdatePassword(newpassword);
if (serviceResponse.Success)
{
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "Global Admin Password updated!";
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Header = "ERROR";
message.Message = "There was a problem updating the Global Admin Password!";
}
}
break;
case "identityheadernamesave":
bool confirmIdentityHeaderNameSave = await JsRuntime.InvokeAsync<bool>("confirm", "This will update the current Identity Header Name. Are you sure?");
if (confirmIdentityHeaderNameSave)
{
serviceResponse = await AdminService.UpdateIdentityHeaderName(currentidentityheadername);
if (serviceResponse.Success)
{
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "Identity Header Name updated!";
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Header = "ERROR";
message.Message = "There was a problem updating the Identity Header Name!";
}
}
break;
case "apikeysave":
bool confrimAPIKeySave = await JsRuntime.InvokeAsync<bool>("confirm", "This will update the current API Key. Are you sure?");
if (confrimAPIKeySave)
{
serviceResponse = await AdminService.UpdateAPIKey(currentapikey);
if (serviceResponse.Success)
{
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "API Key updated!";
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Header = "ERROR";
message.Message = "There was a problem updating the API Key!";
}
}
break;
case "apikeygenerate":
bool confrimAPIKeyGenerate = await JsRuntime.InvokeAsync<bool>("confirm", "This will generate a new API Key. Are you sure?");
if (confrimAPIKeyGenerate)
{
serviceResponse = await AdminService.GenerateAPIKey();
if (serviceResponse.Success)
{
currentapikey = serviceResponse.ResponseObject;
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "API Key updated!";
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Header = "ERROR";
message.Message = "There was a problem generating the API Key!";
}
}
break;
case "generationwebhooksave":
bool confirmGenerationWebhook = await JsRuntime.InvokeAsync<bool>("confirm", "This will update the current Generation Webhook. Are you sure?");
if (confirmGenerationWebhook)
{
if (currentgenerationwebhook != "")
{
if (!Uri.IsWellFormedUriString(currentgenerationwebhook, UriKind.Absolute))
{
message.Header = "ERROR";
message.Type = MessageTypesEnum.ERROR;
message.Message = "You must enter a valid URL!";
break;
}
}
ConfigurationHelper.SetAppSetting("GenerationWebhook", currentgenerationwebhook, true);
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "Generation Webhook configuration saved!";
}
break;
case "clearcache":
ObjectCache memoryCache = MemoryCache.Default;
List<string> cacheKeys = memoryCache.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
memoryCache.Remove(cacheKey);
}
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
message.Message = "Cache cleared!";
break;
case "viewcachedata":
string cachedata = CacheHelper.GetAllCacheData();
await ShowInformationModal("bg-navcolor", "Cache Data", "<p>This is the current cache data.</p>" + cachedata);
break;
case "samplepost":
string webhookdata = await FileSystemHelper.ReadFile("samplewebhookdata.json", "");
await ShowInformationModal("bg-navcolor", "Sample Post", "<p>This is a sample Generation Webhook post.</p><code style=\"display: block; white-space: pre-wrap;\">" + webhookdata + "</code>");
break;
case "adminuseradd":
ShowAddModal(id, "AdminUser", "Add Admin User", "<p>Add an Admin User.</p>");
break;
case "adminuserdelete":
confirm = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
if (confirm)
{
serviceResponse = await AdminUserService.DeleteItem(Convert.ToInt32(id));
if (serviceResponse.Success)
{
message.Type = MessageTypesEnum.SUCCESS;
message.Message = "Admin User deleted!";
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Message = "Admin User deletion failed!";
message.MessageDetails = serviceResponse.ResponseMessage;
}
}
break;
}
config = ConfigurationHelper.GetConfigurationData();
if (message.Message != null)
{
switch (message.Type)
{
case MessageTypesEnum.INFORMATION:
toastService.ShowInfo(message.Message);
break;
case MessageTypesEnum.SUCCESS:
toastService.ShowSuccess(message.Message);
break;
case MessageTypesEnum.WARNING:
toastService.ShowWarning(message.Message);
break;
case MessageTypesEnum.ERROR:
toastService.ShowError(message.Message);
break;
}
AdminLogService.PostItem(new AdminLogMessage() { Title = message.Type.ToString(), Message = message.Message, Source = currentuser });
}
servicesData = await ServicesHelper.LoadServicesData(servicesData, admin);
state.SetNavReload(true);
StateHasChanged();
if (redirect)
{
if (admin)
{
NavigationManager.NavigateTo(NavigationManager.Uri, true);
}
else
{
NavigationManager.NavigateTo("/", true);
}
}
}
private void SetFormValue(string type, string value)
{
switch (type)
{
case "login":
currentpassword = value;
break;
case "password":
newpassword = value;
// Validate the password complexity
if (ValidationHelper.ValidatePassword(newpassword))
{
disabled = false;
}
else
{
disabled = true;
}
break;
case "identityheadername":
currentidentityheadername = value;
break;
case "apikey":
currentapikey = value;
break;
case "generationwebhook":
currentgenerationwebhook = value;
break;
}
}
private async void SettingChanged(ChangeEventArgs e, string setting)
{
message = new ResponseMessage();
message.Header = "INFORMATION";
message.Type = MessageTypesEnum.INFORMATION;
try
{
if (e.Value != null)
{
switch (setting)
{
case "duplicatenamesallowed":
ConfigurationHelper.SetAppSetting("DuplicateNamesAllowed", e.Value.ToString());
message.Message = "Duplicate Names Allowed setting updated to " + e.Value.ToString().ToUpper() + "!";
break;
case "connectivitycheckenabled":
ConfigurationHelper.SetAppSetting("ConnectivityCheckEnabled", e.Value.ToString());
message.Message = "Connectivity Check Enabled setting updated to " + e.Value.ToString().ToUpper() + "!";
// Update the tool connection setting to true, if the connecvity check is disabled. This will cause the stie to always assume there is connectivity and attempt external connections.
if (!(bool)e.Value)
{
CacheHelper.SetCacheObject("isconnected", true);
}
else
{
CacheHelper.InvalidateCacheObject("isconnected");
}
if (!(bool)e.Value)
{
await ShowInformationModal("bg-danger", "ATTENTION", "<p>Disabling this setting may result in connectivity errors being logged to the Admin Log.<p>");
}
break;
case "resourcetypeeditingallowed":
ConfigurationHelper.SetAppSetting("ResourceTypeEditingAllowed", e.Value.ToString());
message.Message = "Resource Type Editing Allowed setting updated to " + e.Value.ToString().ToUpper() + "!";
if ((bool)e.Value)
{
await ShowInformationModal("bg-danger", "ATTENTION", "<p>Disabling this setting may result in name generation that will fail regex validation for the resource type.</p><p>Please use caution when editing Resource Type validation settings.");
}
break;
}
message.Type = MessageTypesEnum.SUCCESS;
message.Header = "SUCCESS";
}
else
{
message.Message = "There was a problem updating the setting!";
message.Header = "ERROR";
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
message.Message = "There was a problem updating the setting!";
message.Header = "ERROR";
}
if (message.Message != null)
{
switch (message.Type)
{
case MessageTypesEnum.INFORMATION:
toastService.ShowInfo(message.Message);
break;
case MessageTypesEnum.SUCCESS:
toastService.ShowSuccess(message.Message);
break;
case MessageTypesEnum.WARNING:
toastService.ShowWarning(message.Message);
break;
case MessageTypesEnum.ERROR:
toastService.ShowError(message.Message);
break;
}
AdminLogService.PostItem(new AdminLogMessage() { Title = message.Type.ToString(), Message = message.Message, Source = currentuser });
}
servicesData = await ServicesHelper.LoadServicesData(servicesData, admin);
state.SetNavReload(true);
StateHasChanged();
}
private async Task OnConfigurationAlertDismissed()
{
await session.SetAsync("versionalert-" + appversion + "-shown", true);
if (dismissalert)
{
ConfigurationHelper.DismissVersionAlert();
}
}
async Task ShowInformationModal(string headerstyle, string title, string message)
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), message);
parameters.Add(nameof(InformationModal.title), title);
parameters.Add(nameof(InformationModal.headerstyle), headerstyle);
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>(title, parameters, options);
var result = await modal.Result;
if (!result.Cancelled)
{
StateHasChanged();
}
}
private async void ShowInstructionsModal()
{
try
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), null);
parameters.Add(nameof(InformationModal.title), "Admin");
parameters.Add(nameof(InformationModal.headerstyle), "bg-navcolor");
parameters.Add(nameof(InformationModal.component), "adminconfigurationinstructions");
parameters.Add(nameof(InformationModal.admin), admin);
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>("Instructions", parameters, options);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
async void ShowAddModal(int id, string type, string title, string message, string parentcomponent = "")
{
var parameters = new ModalParameters();
parameters.Add(nameof(AddModal.id), id);
parameters.Add(nameof(AddModal.type), type);
parameters.Add(nameof(AddModal.message), message);
parameters.Add(nameof(AddModal.title), title);
if (parentcomponent != null)
{
parameters.Add(nameof(AddModal.parentcomponent), parentcomponent);
}
parameters.Add(nameof(AddModal.servicesData), servicesData);
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<AddModal>(title, parameters, options);
var result = await modal.Result;
if (!result.Cancelled)
{
servicesData = await ServicesHelper.LoadServicesData(servicesData, admin);
state.SetNavReload(true);
StateHasChanged();
}
}
private async void SubmitIssue(string type)
{
string issueURL = "";
switch(type)
{
case "bug":
issueURL = "https://github.com/mspnp/AzureNamingTool/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=";
break;
case "feature":
issueURL = "https://github.com/mspnp/AzureNamingTool/issues/new?assignees=&labels=&projects=&template=feature_request.md&title=";
break;
}
if (!String.IsNullOrEmpty(issueURL))
{
await JsRuntime.InvokeVoidAsync("open", issueURL, "_blank");
}
}
}

264
Pages/AdminLog.razor Normal file
View File

@@ -0,0 +1,264 @@
@page "/adminlog"
@using AzureNamingTool.Helpers
@using AzureNamingTool.Models
@using AzureNamingTool.Services
@using System.IO
@using BlazorDownloadFile
@using Microsoft.AspNetCore.Components.Authorization
@using System.Text.Json
@using System.Text.Json.Serialization
@using System.Text
@inject ILogger<Configuration> Logger
@inject IJSRuntime JsRuntime
@inject IConfiguration _config
@inject StateContainer state
@inject ProtectedSessionStorage session
@inject IToastService toastService
@implements IDisposable
@inject NavigationManager NavigationManager
<PageTitle>Azure Naming Tool - Admin Log</PageTitle>
<div class="card @theme.ThemeStyle">
<div class="card-body">
<div style="float:left;">
<h1>
Admin Log
</h1>
</div>
<div style="float:right;">
<button class="btn btn-secondary" title="Instructions" @onclick="@(e => ShowInstructionsModal())">
<span class="oi oi-document" aria-hidden="true"></span> Documentation
</button>
</div>
<div style="clear:both;"></div>
<div class="mb-3">This page displays a log of Admin/Configuration changes.</div>
@if (!dataLoaded)
{
<div class="spinner-border blue" role="status">
<span class="sr-only"></span>
</div>
}
else
{
<div>
@if (lstAdminLogMessages.Count > 0)
{
<div class="mb-3">
<button class="btn btn-primary" title="Export Admin Log" @onclick="@(e => OnExportLogClick())">
Export Admin Log
</button>
<button class="btn btn-danger" title="Purge Admin Log" @onclick="@(e => OnPurgeLogClick())">
Purge Admin Log
</button>
</div>
<div class="card mb-3">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#filter" role="button" aria-expanded="false" aria-controls="filter">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Filter Data
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="filter">
<div class="form-group row mb-3">
<div style="width:auto;">
<input class="form-control" type="date" style="width: 150px;" @bind="startdate" @bind:event="oninput" />
</div>
<div style="width:auto;padding-top:7px;">
to
</div>
<div style="width:auto;">
<input class="form-control" type="date" style="width: 150px;" @bind="enddate" @bind:event="oninput" />
</div>
</div>
<div class="form-group row mb-3">
<div class="input-group flex-nowrap">
<div class="input-group flex-nowrap">
<span class="input-group-text" id="addon-wrapping">
<span class="oi oi-magnifying-glass" id="addon-wrapping"></span>
</span>
<input class="form-control" type="search" placeholder="Filter by Source, Title, Message"
@bind="filterData"
@bind:event="oninput">
</div>
</div>
</div>
<div class="form-group row">
<div>
<button class="btn btn-success" title="Reset" @onclick="@(e => OnResetClick())">
Reset
</button>
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table @theme.ThemeStyle">
<thead>
<tr>
<th scope="col">Created On</th>
<th scope="col">Source</th>
<th scope="col">Title</th>
<th scope="col">Message</th>
</tr>
</thead>
<tbody>
@foreach (AdminLogMessage message in lstAdminLogMessages)
{
if (!IsVisible(message))
continue;
<tr>
<td class="align-top">
@message.CreatedOn.ToString()
</td>
<td class="align-top">
@message.Source
</td>
<td class="align-top">
@message.Title
</td>
<td class="align-top">
@message.Message
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p style="font-style:italic">
There are no log items to display.
</p>
}
</div>
}
</div>
</div>
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
private bool admin;
private bool dataLoaded = false;
List<AdminLogMessage> lstAdminLogMessages = new();
public string filterData { get; set; }
public DateTime startdate { get; set; } = DateTime.Today.AddDays(-30);
public DateTime enddate { get; set; } = DateTime.Today;
protected override async Task OnInitializedAsync()
{
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var result = await session.GetAsync<bool>("admin");
admin = (bool)result.Value;
if (!admin)
{
NavigationManager.NavigateTo("/");
}
ServiceResponse serviceResponse = new();
serviceResponse = await AdminLogService.GetItems();
lstAdminLogMessages = (List<AdminLogMessage>)serviceResponse.ResponseObject;
dataLoaded = true;
StateHasChanged();
}
}
private async Task OnInstructionsClick()
{
NavigationManager.NavigateTo("/instructions");
}
public void Dispose()
{
StateHasChanged();
}
private async Task OnPurgeLogClick()
{
ServiceResponse serviceResponse = new();
bool confirm = false;
confirm = await JsRuntime.InvokeAsync<bool>("confirm", "This will purge the Admin Log. Are you sure?");
if (confirm)
{
await LogHelper.PurgeAdminLog();
toastService.ShowSuccess("Admin Log purged!");
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Admin Log purged." });
CacheHelper.InvalidateCacheObject("AdminLogMessage");
serviceResponse = await AdminLogService.GetItems();
lstAdminLogMessages = (List<AdminLogMessage>)serviceResponse.ResponseObject;
dataLoaded = true;
StateHasChanged();
}
}
private async Task OnExportLogClick()
{
await JsRuntime.InvokeVoidAsync("htmlToCSV", "adminlog.csv");
}
public bool IsVisible(AdminLogMessage message)
{
bool visible = true;
if (startdate != null)
{
if (message.CreatedOn < startdate)
visible = false;
}
if (enddate != null)
{
if (message.CreatedOn > (enddate + new TimeSpan(23, 59, 59)))
visible = false;
}
if (!string.IsNullOrEmpty(filterData))
{
if ((!message.Source.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!message.Title.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!message.Message.Contains(filterData, StringComparison.OrdinalIgnoreCase)))
visible = false;
}
return visible;
}
private async Task OnResetClick()
{
startdate = DateTime.Today.AddDays(-30);
enddate = DateTime.Today;
filterData = "";
}
private async void ShowInstructionsModal()
{
try
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), null);
parameters.Add(nameof(InformationModal.title), "Admin Log");
parameters.Add(nameof(InformationModal.headerstyle), "bg-navcolor");
parameters.Add(nameof(InformationModal.component), "adminloginstructions");
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>("Instructions", parameters, options);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}

3599
Pages/Configuration.razor Normal file

File diff suppressed because it is too large Load Diff

42
Pages/Error.cshtml Normal file
View File

@@ -0,0 +1,42 @@
@page
@model AzureNamingTool.Pages.ErrorModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<div class="main">
<div class="content px-4">
<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 the <strong>Development</strong> environment displays 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>
</div>
</div>
</body>
</html>

27
Pages/Error.cshtml.cs Normal file
View File

@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace AzureNamingTool.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

1170
Pages/Generate.razor Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
@page "/generatednameslog"
@using AzureNamingTool.Helpers
@using AzureNamingTool.Models
@using AzureNamingTool.Services
@using System.IO
@using BlazorDownloadFile
@using Microsoft.AspNetCore.Components.Authorization
@using System.Text.Json
@using System.Text.Json.Serialization
@using System.Text
@inject ILogger<Configuration> Logger
@inject IJSRuntime JsRuntime
@inject IConfiguration _config
@inject StateContainer state
@inject ProtectedSessionStorage session
@inject IToastService toastService
@implements IDisposable
@inject NavigationManager NavigationManager
<PageTitle>Azure Naming Tool - Generated Names Log</PageTitle>
<div class="card @theme.ThemeStyle">
<div class="card-body">
<div style="float:left;">
<h1>
Generated Names Log
</h1>
</div>
<div style="float:right;">
<button class="btn btn-secondary" title="Instructions" @onclick="@(e => ShowInstructionsModal())">
<span class="oi oi-document" aria-hidden="true"></span> Documentation
</button>
</div>
<div style="clear:both;"></div>
<div class="mb-3">This page displays a log of generated resouce type names.</div>
@if (!dataLoaded)
{
<div class="spinner-border blue" role="status">
<span class="sr-only"></span>
</div>
}
else
{
<div class="mb-3">
@if (lstNameRequests.Count > 0)
{
<div class="mb-3">
<button class="btn btn-primary" title="Export Generated Names Log" @onclick="@(e => OnExportLogClick())">
Export Generated Names Log
</button>
@if (admin)
{
if (duplicatenamesallowed)
{
<button class="btn btn-danger" style="margin-left:5px;" title="Purge Generated Names Log" @onclick="@(e => OnPurgeLogClick())">
Purge Generated Names Log
</button>
}
else
{
<button class="btn btn-danger" style="margin-left:5px;" title="Purge Generated Names Log" disabled>
Purge Generated Names Log
</button>
<div class="mt-3">
<div class="alert alert-warning">
<strong>NOTE</strong>:
Duplicate names are not currently allowed in the site settings. Purging of the log is restricted.
</div>
</div>
}
}
</div>
<div class="card mb-3">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#filter" role="button" aria-expanded="false" aria-controls="filter">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Filter Data
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="filter">
<div class="form-group row mb-3">
<div style="width:auto;">
<input class="form-control" type="date" style="width: 150px;" @bind="startdate" @bind:event="oninput" />
</div>
<div style="width:auto;padding-top:7px;">
to
</div>
<div style="width:auto;">
<input class="form-control" type="date" style="width: 150px;" @bind="enddate" @bind:event="oninput" />
</div>
</div>
<div class="form-group row mb-3">
<div class="input-group flex-nowrap">
<div class="input-group flex-nowrap">
<span class="input-group-text" id="addon-wrapping">
<span class="oi oi-magnifying-glass" id="addon-wrapping"></span>
</span>
<input class="form-control" type="search" placeholder="Filter by Created By, Generated Name, Resource Type, Components"
@bind="filterData"
@bind:event="oninput">
</div>
</div>
</div>
<div class="form-group row">
<div>
<button class="btn btn-success" title="Reset" @onclick="@(e => OnResetClick())">
Reset
</button>
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table @theme.ThemeStyle">
<thead>
<tr>
@if ((admin) && (duplicatenamesallowed))
{
<th scope="col"></th>
}
<th scope="col">Created On</th>
<th scope="col">Created By</th>
<th scope="col">Generated Name</th>
<th scope="col">Resource Type</th>
<th scope="col">Components</th>
</tr>
</thead>
<tbody>
@foreach (GeneratedName name in lstNameRequests)
{
if (!IsVisible(name))
continue;
<tr>
@if ((admin) && (duplicatenamesallowed))
{
<td class="align-top">
<button type="button" class="btn btn-danger" @onclick="@(e => DeleteGeneratedName(name.Id))" title="Delete">
<span class="oi oi-x" aria-hidden="true"></span>
</button>
</td>
}
<td class="align-top">
@name.CreatedOn.ToString()
</td>
<td class="align-top">
@name.User
</td>
<td class="align-top">
@name.ResourceName
</td>
<td class="align-top">
@if (name.ResourceTypeName != null)
{
@name.ResourceTypeName
}
</td>
<td class="align-top">
@foreach (string[] component in name.Components.ToList<string[]>())
{
<div style="line-break:anywhere;">
@component[0] : @component[1]
</div>
}
</td>
</tr>
}
</tbody>
</table>
</div>
}
else
{
<p style="font-style:italic">
There are no log items to display.
</p>
}
</div>
}
</div>
</div>
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
private bool admin;
private bool dataLoaded = false;
private bool duplicatenamesallowed = false;
List<GeneratedName> lstNameRequests = new();
public string filterData { get; set; }
public DateTime startdate { get; set; } = DateTime.Today.AddDays(-30);
public DateTime enddate { get; set; } = DateTime.Today;
protected override async Task OnInitializedAsync()
{
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var result = await session.GetAsync<bool>("admin");
admin = (bool)result.Value;
LoadData();
duplicatenamesallowed = Convert.ToBoolean(ConfigurationHelper.GetAppSetting("DuplicateNamesAllowed"));
StateHasChanged();
}
}
private async Task LoadData()
{
ServiceResponse serviceResponse = new();
serviceResponse = await GeneratedNamesService.GetItems();
lstNameRequests = (List<GeneratedName>)serviceResponse.ResponseObject;
dataLoaded = true;
StateHasChanged();
}
private async Task OnInstructionsClick()
{
NavigationManager.NavigateTo("/instructions");
}
public void Dispose()
{
StateHasChanged();
}
private async Task DeleteGeneratedName(long id)
{
ServiceResponse serviceResponse = new();
ResponseMessage message = new();
bool confirm = false;
confirm = await JsRuntime.InvokeAsync<bool>("confirm", "Are you sure?");
if (confirm)
{
serviceResponse = await GeneratedNamesService.GetItem(Convert.ToInt32(id));
if (serviceResponse.Success)
{
GeneratedName generatedname = (GeneratedName)serviceResponse.ResponseObject;
serviceResponse = await GeneratedNamesService.DeleteItem(Convert.ToInt32(generatedname.Id));
if (serviceResponse.Success)
{
message.Type = MessageTypesEnum.SUCCESS;
message.Message = "Generated name deleted!";
toastService.ShowSuccess(message.Message);
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Generated Name (" + generatedname.ResourceName + ") deleted." });
CacheHelper.InvalidateCacheObject("GeneratedName");
LoadData();
state.SetNavReload(true);
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Message = "Generated name deletion failed!";
message.MessageDetails = serviceResponse.ResponseMessage;
toastService.ShowError(message.Message);
}
}
else
{
message.Type = MessageTypesEnum.ERROR;
message.Message = "Generated name deletion failed!";
message.MessageDetails = serviceResponse.ResponseMessage;
toastService.ShowError(message.Message);
}
}
}
private async Task OnPurgeLogClick()
{
ServiceResponse serviceResponse = new();
bool confirm = false;
confirm = await JsRuntime.InvokeAsync<bool>("confirm", "This will purge the Generated Names log. Are you sure?");
if (confirm)
{
await LogHelper.PurgeGeneratedNames();
toastService.ShowSuccess("Generated Log purged!");
AdminLogService.PostItem(new AdminLogMessage() { Title = "INFORMATION", Message = "Generated Names log purged." });
await LoadData();
StateHasChanged();
}
}
private async Task OnExportLogClick()
{
await JsRuntime.InvokeVoidAsync("htmlToCSV", "generatednameslog.csv");
}
public bool IsVisible(GeneratedName name)
{
bool visible = true;
if (startdate != null)
{
if (name.CreatedOn < startdate)
visible = false;
}
if (enddate != null)
{
if (name.CreatedOn > (enddate + new TimeSpan(23, 59, 59)))
visible = false;
}
if (!string.IsNullOrEmpty(filterData))
{
if ((!name.User.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!name.ResourceName.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!name.ResourceTypeName.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!string.Join(",", name.Components).Contains(filterData, StringComparison.OrdinalIgnoreCase)))
visible = false;
}
return visible;
}
private async Task OnResetClick()
{
startdate = DateTime.Today.AddDays(-30);
enddate = DateTime.Today;
filterData = "";
}
private async void ShowInstructionsModal()
{
try
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), null);
parameters.Add(nameof(InformationModal.title), "Generated Names Log");
parameters.Add(nameof(InformationModal.headerstyle), "bg-navcolor");
parameters.Add(nameof(InformationModal.component), "generatednamesloginstructions");
parameters.Add(nameof(InformationModal.admin), admin);
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>("Instructions", parameters, options);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}

130
Pages/Index.razor Normal file
View File

@@ -0,0 +1,130 @@
@page "/"
@using System.Reflection
@using AzureNamingTool.Helpers
@using AzureNamingTool.Models
@using AzureNamingTool.Services
@inject ILogger<Index> Logger
@inject NavigationManager NavigationManager
@inject StateContainer state
@inject ProtectedSessionStorage session
<PageTitle>Azure Naming Tool - Home</PageTitle>
<div class="card @theme.ThemeStyle">
<div class="card-body">
<p>
<img src="images/AzureNamingToolLogo.png" height="160px;" alt="Azure Naming Tool" />
</p>
<p class="alert alert-info">
<h4>Disclaimer</h4>
This tool was designed using the best practices in the <a href="https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/" target="_blank">Azure Cloud Adoption Framework</a> which adheres to a few rules and allows the complete customization of your naming convention. While most of the customizations can be made in your web browser, this tool also includes an API.
<br />
<br />
For guidance on the API, review your swagger page at: <strong><a href="@(NavigationManager.Uri)swagger/index.html" target="_blank">@(NavigationManager.Uri)swagger/index.html</a></strong>
</p>
<h4>Admin</h4>
<p>
The Admin page is used to configure the Azure Naming Tool. This page is only accessible by Admin users.
</p>
<h4>Instructions</h4>
<p>
The Instructions page provides documentation for the site pages. Each page of the site contains a documentation link (top right) that will open contextual instructions for the current page.
</p>
<h4>Configuration</h4>
<p>
The Configuration page provides all the data points that make up your naming convention. There are 8 standard components that may be used to name each Azure resource. The data within each of those components can be completely customized, except for the resource types. For the resource types, only the short name, optional, and excluded values may be updated. A delimiter may be selected to separate each component in your resource names. However, the delimiter will be included in a resource name if that character is allowed. The tool also supports the ability to define custom components. This allows administratos to create new components for the naming convention. Your configuration may be exported under the Global Configuration header. We recommend doing that after defining your naming convention as a backup. @*Lastly, use the Policy export feature to create an Azure Policy Definition. That can be applied to your Azure environment to enforce your naming convention.*@
</p>
<h4>Reference</h4>
<p>
The Reference page provides an example of each Azure resource type using your defined naming convention. The example values do not include any excluded naming components. Optional components are always displayed and are identified below the example. Since unique names are only required at specific scopes, the examples provided are only generated for the scopes above the resource scope: resource group, resource group & region, region, global, subscription, and tenant.
</p>
<h4>Generate</h4>
<p>
The Generate page provides a drop-down menu to select an Azure resource type. Once a resource is selected, the naming component options are provided. Read-only components cannot be changed, like the value for a resource type. Optional components, if unselected, will be null and not shown in the output. Required components do not allow a null value and will require a selection from the menu.
</p>
<h4>Generated Names Log</h4>
<p>
The Generated Names Log contains a record of all names generated in the Azure Naming Tool. This includes the date, user, generated name, resource type, and components selected. This log can be exported to a .csv file.
</p>
<p>
<button class="btn btn-primary" title="Instructions" @onclick="@(e => OnInstructionsClick())">
View Instructions
</button>
</p>
</div>
</div>
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
private bool admin;
private string? appversion = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
private string? updateurl = "https://github.com/mspnp/AzureNamingTool/blob/master/ready/AzNamingTool/UPDATING.md";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
var resultAdmin = await session.GetAsync<bool>("admin");
admin = (bool)resultAdmin.Value;
// Determine if the version notification has been dismissed
var resultNotification = await session.GetAsync<bool>("versionnotification");
bool notificationshown = resultNotification.Success ? resultNotification.Value : false;
if (firstRender)
{
try
{
if (admin)
{
if (ConfigurationHelper.VerifyConnectivity())
{
var latestversion = await ConfigurationHelper.GetToolVersion();
if (!notificationshown)
{
// Check if the client's version matches the current offical version
if (appversion != latestversion)
{
ShowVersionModal("bg-navcolor", "ATTENTION", "<p style='font-weight:bold;'>Your current version is out of date!</p><p>Please click <a href='" + updateurl + "' target='_blank'>here</a> to update to the current version for the latest features!</p><p><span style='font-weight:bold;'>Installed Version:</span> <span style='font-style:italic; '>" + appversion + "</p><p><span style='font-weight:bold;'>Latest version:</span> <span style='font-style:italic; '>" + latestversion + "</span></p>");
}
}
}
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}
private async Task OnInstructionsClick()
{
NavigationManager.NavigateTo("/instructions");
}
async void ShowVersionModal(string headerstyle, string title, string message)
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), message);
parameters.Add(nameof(InformationModal.title), title);
parameters.Add(nameof(InformationModal.headerstyle), headerstyle);
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>(title, parameters, options);
var result = await modal.Result;
await session.SetAsync("versionnotification", true);
if (!result.Cancelled)
{
StateHasChanged();
}
}
}

107
Pages/Instructions.razor Normal file
View File

@@ -0,0 +1,107 @@
@page "/instructions"
@using AzureNamingTool.Helpers
@using AzureNamingTool.Models
@using AzureNamingTool.Shared.Components
@inject StateContainer state
@inject ProtectedSessionStorage session
<div class="card @theme.ThemeStyle">
<div class="card-body">
<h1>Instructions</h1>
<div class="mb-3">This page provides instructions on how to configure and use the Azure Naming Tool.</div>
@if (admin)
{
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="adminpasswordcontainer">
<a id="ResourceComponent" class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#adminpassword" role="button" aria-expanded="true" aria-controls="adminpassword">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Admin Password
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="adminpassword">
<AdminPasswordInstructions theme="@theme" admin="@admin" />
</div>
</div>
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="adminconfigurationcontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#adminconfiguration" role="button" aria-expanded="true" aria-controls="adminconfiguration">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Admin Configuration
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="adminconfiguration">
<AdminConfigurationInstructions theme="@theme" admin="@admin" />
</div>
</div>
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="adminlogcontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#adminlog" role="button" aria-expanded="true" aria-controls="adminlog">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Admin Log
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="adminlog">
<AdminLogInstructions theme="@theme" admin="@admin" />
</div>
</div>
}
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="configurationcontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#configuration" role="button" aria-expanded="true" aria-controls="configuration">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Configuration
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="configuration">
<ConfigurationInstructions theme="@theme" admin="@admin" />
</div>
</div>
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="generatecontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#generate" role="button" aria-expanded="true" aria-controls="generate">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Generate
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="generate">
<GenerateInstructions theme="@theme" admin="@admin" />
</div>
</div>
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="generatednameslogcontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#generatednameslog" role="button" aria-expanded="true" aria-controls="generatednameslog">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Generated Names Log
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="generatednameslog">
<GeneratedNamesLogInstructions theme="@theme" admin="@admin" />
</div>
</div>
<div class="card mb-3">
<h5 class="card-header bg-secondary text-white" id="referencecontainer">
<a class="link-light text-decoration-none" data-bs-toggle="collapse" style="display:block;" href="#reference" role="button" aria-expanded="true" aria-controls="reference">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Reference
</a>
</h5>
<div class="card card-body collapse show @theme.ThemeStyle" id="reference">
<ReferenceInstructions theme="@theme" admin="@admin" />
</div>
</div>
</div>
</div>
<AnchorNavigation />
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
private bool admin = false;
protected override async Task OnInitializedAsync()
{
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var result = await session.GetAsync<bool>("admin");
admin = (bool)result.Value;
StateHasChanged();
}
}
}

537
Pages/Reference.razor Normal file
View File

@@ -0,0 +1,537 @@
@page "/reference"
@using AzureNamingTool.Helpers
@using AzureNamingTool.Models
@using AzureNamingTool.Services
@using System.Text.RegularExpressions
@inject IJSRuntime JsRuntime
@inject ILogger<Reference> Logger
@inject NavigationManager NavigationManager
@inject StateContainer state
@inject IToastService toastService
<PageTitle>Azure Naming Tool - Reference</PageTitle>
<div class="card @theme.ThemeStyle">
<div class="card-body">
<div style="float:left;">
<h1>
Reference
</h1>
</div>
<div style="float:right;">
<button class="btn btn-secondary" title="Instructions" @onclick="@(e => ShowInstructionsModal())">
<span class="oi oi-document" aria-hidden="true"></span> Documentation
</button>
</div>
<div style="clear:both;"></div>
<div class="mb-3">This page provides an example name for each resource type using the defined configuration.</div>
@if (servicesData.ResourceTypes == null)
{
<div class="spinner-border" role="status">
<span class="sr-only"></span>
</div>
}
else
{
<div class="card mb-3">
<div class="card-header bg-default text-dark">
<a class="text-decoration-none text-dark" data-bs-toggle="collapse" style="display:block;font-weight:bold;" href="#jumpto" role="button" aria-expanded="false" aria-controls="jumpto">
<span class="oi oi-chevron-bottom" aria-hidden="true"></span> Jump To
</a>
</div>
<div class="collapse show card card-body @theme.ThemeStyle" id="jumpto">
<p>
Use the filters below to jump to a specifc resource type.
</p>
<select @onchange="OnCategoryChanged" class="form-control @theme.ThemeStyle" aria-label="Select category">
<option value="">Select a resource type provider (OPTIONAL)</option>
@foreach (var category in @categories)
{
<option value="@category">@category</option>
}
</select>
<select @onchange="OnTypeChanged" class="form-control @theme.ThemeStyle" aria-label="Select type">
<option value="">Select a resource type</option>
@foreach (var type in @ResourceTypeService.GetFilteredResourceTypes(servicesData.ResourceTypes, selectedResourceTypeCategory))
{
@if (type.Property != "")
{
<option value="@type.Id">@type.Resource - @type.Property (@type.ShortName)</option>
}
else
{
<option value="@type.Id">@type.Resource (@type.ShortName)</option>
}
}
</select>
</div>
</div>
<table class="table table-responsive @theme.ThemeStyle w-100">
<tbody>
@foreach (var resourcetype in servicesData.ResourceTypes)
{
if (!IsTypeVisible(resourcetype))
continue;
<tr>
<td>
<table class="table table-responsive">
<tbody>
<tr class="bg-secondary">
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;">
@if (resourcetype.Property != "")
{
<h5 class="text-white bg-secondary" style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;">@resourcetype.Resource - @resourcetype.Property </h5>
}
else
{
<h5 class="text-white bg-secondary" style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;">@resourcetype.Resource </h5>
}
</td>
</tr>
<tr>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;font-style:italic;">
@if (filteredscopes.Contains(resourcetype.Scope))
{
<table class="table table-success w-100" style="margin-bottom:0;">
<tbody>
<tr>
<td>
<strong>Example: @GenerateName(resourcetype, resourcetype.ShortName, resourcetype.StaticValues)</strong><a id="@resourcetype.Id"></a>
</td>
</tr>
</tbody>
</table>
}
else
{
<table class="table table-warning w-100" style="margin-bottom:0;">
<tbody>
<tr>
<td>
<strong>This resource has a lower-level scope and does not require a unique name.</strong><a id="@resourcetype.Id"></a>
</td>
</tr>
</tbody>
</table>
}
</td>
</tr>
<tr>
<td>
@if (resourcetype.StaticValues == "")
{
<table class="table @theme.ThemeStyle w-100">
<tbody>
@if (filteredscopes.Contains(resourcetype.Scope))
{
<tr>
<td colspan="2" class="table-secondary">
<strong>Components</strong>
</td>
</tr>
@foreach (var resourceComponent in servicesData.ResourceComponents)
{
if (!resourcetype.Exclude.ToLower().Split(',').Contains(GeneralHelper.NormalizeName(resourceComponent.Name, true)))
{
if (!resourcetype.Optional.ToLower().Split(',').Contains(GeneralHelper.NormalizeName(resourceComponent.Name, true)))
{
switch (resourceComponent.Name)
{
case "ResourceEnvironment":
<tr>
<td class="align-top w-25">
Environment:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceEnvironments[0].Name (@servicesData.ResourceEnvironments[0].ShortName)
</td>
</tr>
break;
case "ResourceInstance":
<tr>
<td class="align-top w-25">
Instance:
</td>
<td style="word-wrap:anywhere;white-space: normal;" class="align-top">
001
</td>
</tr>
break;
case "ResourceLocation":
<tr>
<td class="align-top w-25">
Location:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceLocations[0].Name (@servicesData.ResourceLocations[0].ShortName)
</td>
</tr>
break;
case "ResourceOrg":
<tr>
<td class="align-top w-25">
Org:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceOrgs[0].Name (@servicesData.ResourceOrgs[0].ShortName)
</td>
</tr>
break;
case "ResourceProjAppSvc":
<tr>
<td class="align-top w-25">
ProjAppSvc:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceProjAppSvcs[0].Name (@servicesData.ResourceProjAppSvcs[0].ShortName)
</td>
</tr>
break;
case "ResourceType":
<tr>
<td class="align-top w-25">
Type:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.Resource (@resourcetype.ShortName)
</td>
</tr>
break;
case "ResourceUnitDept":
<tr>
<td class="align-top w-25">
Unit/Dept:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceUnitDepts[0].Name (@servicesData.ResourceUnitDepts[0].ShortName)
</td>
</tr>
break;
case "ResourceFunction":
<tr>
<td class="align-top w-25">
Function:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.ResourceFunctions[0].Name (@servicesData.ResourceFunctions[0].ShortName)
</td>
</tr>
break;
default:
if (resourceComponent.IsCustom)
{
if (servicesData.CustomComponents.Where(x => x.ParentComponent == GeneralHelper.NormalizeName(resourceComponent.Name, true)).Count() > 0)
{
<tr>
<td class="align-top w-25">
@resourceComponent.Name:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@servicesData.CustomComponents.Where(x=>x.ParentComponent == GeneralHelper.NormalizeName(resourceComponent.Name, true)).FirstOrDefault().Name (@servicesData.CustomComponents.Where(x=>x.ParentComponent == GeneralHelper.NormalizeName(resourceComponent.Name, true)).FirstOrDefault().ShortName)
</td>
</tr>
}
}
break;
}
}
}
}
}
<tr>
<td colspan="2" class="table-secondary">
<strong>Naming Guidelines</strong>
</td>
</tr>
@if (@resourcetype.Optional != "")
{
<tr>
<td class="align-top w-25">
Optional Components:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.Optional
</td>
</tr>
}
@if (@resourcetype.Exclude != "")
{
<tr>
<td class="align-top w-25">
Excluded Components:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.Exclude
</td>
</tr>
}
@if (@resourcetype.ShortName != "")
{
<tr>
<td class="align-top w-25">
Short Name:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.ShortName
</td>
</tr>
}
@if (@resourcetype.Scope != "")
{
<tr>
<td class="align-top w-25">
Scope:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.Scope
</td>
</tr>
}
@if ((@resourcetype.LengthMin != "") && (@resourcetype.LengthMax != ""))
{
<tr>
<td class="align-top w-25">
Length:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.LengthMin - @resourcetype.LengthMax characters
</td>
</tr>
}
@if (@resourcetype.ValidText != "")
{
<tr>
<td class="align-top w-25">
Valid Characters:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.ValidText
</td>
</tr>
}
@if (@resourcetype.InvalidCharacters != "")
{
<tr>
<td class="align-top w-25">
Invalid Characters:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.InvalidCharacters
</td>
</tr>
}
@if (@resourcetype.Regx != "")
{
<tr>
<td class="align-top w-25">
Regx:
</td>
<td style="word-wrap:anywhere;overflow-wrap:anywhere;white-space: normal;" class="align-top">
@resourcetype.Regx
</td>
</tr>
}
</tbody>
</table>
}
else
{
<span>This resource type has a unique naming pattern and must be manually named.</span>
}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
@code {
[CascadingParameter]
protected ThemeInfo? theme { get; set; }
[CascadingParameter]
public IModalService Modal { get; set; }
private ServicesData servicesData = new();
private ResourceDelimiter? resourceDelimiter;
public string filterData { get; set; }
private string? selectedResourceTypeCategory = "";
private string[] filteredscopes = new string[] { "resource group", "resource group & region", "region", "global", "subscription", "tenant" };
private List<string> categories = new List<string>();
protected override async Task OnInitializedAsync()
{
await SetServicesData();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (servicesData == null)
{
await SetServicesData();
}
}
private async Task SetServicesData()
{
try
{
servicesData = await ServicesHelper.LoadServicesData(servicesData, false);
resourceDelimiter = (ResourceDelimiter)servicesData.ResourceDelimiters[0];
// Get the list of categories
categories = ResourceTypeService.GetTypeCategories(servicesData.ResourceTypes);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
toastService.ShowError("There was a problem loading the page. Please try again");
}
}
protected string GenerateName(ResourceType type, string shortName, string staticvalues)
{
var name = "";
if (staticvalues == "")
{
foreach (ResourceComponent resourceComponent in servicesData.ResourceComponents)
{
string normalizedcomponentname = GeneralHelper.NormalizeName(resourceComponent.Name, true);
// Check if the component is excluded for the resource type
if (!type.Exclude.ToLower().Split(',').Contains(normalizedcomponentname))
{
// Remove any optional components
if (!type.Optional.ToLower().Split(',').Contains(normalizedcomponentname))
{
if (name != "")
{
name += resourceDelimiter.Delimiter;
}
if (resourceComponent.Name != "ResourceType" && resourceComponent.Name != "ResourceInstance")
{
switch (resourceComponent.Name)
{
case "ResourceEnvironment":
name += servicesData.ResourceEnvironments[0].ShortName;
break;
case "ResourceFunction":
name += servicesData.ResourceFunctions[0].ShortName;
break;
case "ResourceLocation":
name += servicesData.ResourceLocations[0].ShortName;
break;
case "ResourceOrg":
name += servicesData.ResourceOrgs[0].ShortName;
break;
case "ResourceProjAppSvc":
name += servicesData.ResourceProjAppSvcs[0].ShortName;
break;
case "ResourceUnitDept":
name += servicesData.ResourceUnitDepts[0].ShortName;
break;
default:
if (resourceComponent.IsCustom)
{
if (servicesData.CustomComponents.Where(x => x.ParentComponent == normalizedcomponentname).Count() > 0)
{
name += servicesData.CustomComponents.Where(x => x.ParentComponent == normalizedcomponentname).FirstOrDefault().ShortName;
}
}
break;
}
}
else
{
if (resourceComponent.Name != "ResourceInstance")
{
name += shortName;
}
}
}
}
}
if (name != "")
{
name += "1";
}
// Apply the regex to the generate name
Regex regx = new(type.Regx);
Match match = regx.Match(name);
if (!match.Success)
{
// Strip the delimiter in case that is causing the issue
if (resourceDelimiter.Delimiter != "")
{
name = name.Replace(resourceDelimiter.Delimiter, "");
}
}
}
else
{
name = staticvalues;
}
return name;
}
private void OnCategoryChanged(ChangeEventArgs e)
{
selectedResourceTypeCategory = e.Value.ToString();
}
private async void OnTypeChanged(ChangeEventArgs e)
{
await JsRuntime.InvokeVoidAsync("scrollIntoView", e.Value.ToString());
}
private async Task OnInstructionsClick()
{
NavigationManager.NavigateTo("/instructions");
}
public bool IsTypeVisible(ResourceType type)
{
bool visible = true;
if (!string.IsNullOrEmpty(filterData))
{
if ((!type.Resource.Contains(filterData, StringComparison.OrdinalIgnoreCase)) && (!type.ShortName.Contains(filterData, StringComparison.OrdinalIgnoreCase)))
visible = false;
}
return visible;
}
private async void ShowInstructionsModal()
{
try
{
var parameters = new ModalParameters();
parameters.Add(nameof(InformationModal.message), null);
parameters.Add(nameof(InformationModal.title), "Reference");
parameters.Add(nameof(InformationModal.headerstyle), "bg-navcolor");
parameters.Add(nameof(InformationModal.component), "referenceinstructions");
parameters.Add("theme", theme);
var options = new ModalOptions()
{
HideCloseButton = true,
UseCustomLayout = true
};
var modal = Modal.Show<InformationModal>("Instructions", parameters, options);
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
}
}
}

162
Pages/_Host.cshtml Normal file
View File

@@ -0,0 +1,162 @@
@page "/"
@namespace AzureNamingTool.Pages
@using AzureNamingTool.Helpers;
@using AzureNamingTool.Models;
@inject StateContainer state
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
// Verify the site configration files are present
ConfigurationHelper.VerifyConfiguration(state);
ConfigurationHelper.VerifySecurity(state);
var identityProviderDetails = new IdentityProviderDetails();
// Check if there is an HttpContext object
if (this.HttpContext != null)
{
if(this.HttpContext.Request.Headers != null)
{
// Get the Identity provider, if using Azure App Service Authentication
if (this.HttpContext.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-IDP"))
{
// Set the identity provider
identityProviderDetails.CurrentIdentityProvider = this.HttpContext.Request.Headers["X-MS-CLIENT-PRINCIPAL-IDP"];
}
}
// Check if there is an IdentityHeaderName set
if (!String.IsNullOrEmpty(ConfigurationHelper.GetAppSetting("IdentityHeaderName", true)))
{
string headername = ConfigurationHelper.GetAppSetting("IdentityHeaderName", true);
// Check if the IdentityHeaderName header is present
if (this.HttpContext.Request.Headers.ContainsKey(headername))
{
// Set the current user
identityProviderDetails.CurrentUser = this.HttpContext.Request.Headers[headername];
}
}
}
}
<link href="AzureNamingTool.styles.css" rel="stylesheet">
<component type="typeof(App)" render-mode="ServerPrerendered" param-IdentityProviderDetails="identityProviderDetails" />
<script>
//Get the button:
scrollToTop = document.getElementById("btnScrollToTop");
// When the user scrolls down 20px from the top of the document, show the button
window.onscroll = function () { scrollFunction() };
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
scrollToTop.style.display = "block";
} else {
scrollToTop.style.display = "none";
}
}
// When the user clicks on the button, scroll to the top of the document
function topFunction() {
document.body.scrollTop = 0; // For Safari
document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
}
function scrollIntoView(id) {
const element = document.getElementById(id);
var headerOffset = 70;
var elementPosition = element.getBoundingClientRect().top;
var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: "smooth"
});
}
window.IsElementChecked = (id) => {
console.log(id);
console.log(document.getElementById(id));
console.log(document.getElementById(id).checked);
return document.getElementById(id).checked;
};
function htmlToCSV(filename, tableclass = "") {
var data = [];
var rows;
if (tableclass != "") {
rows = document.querySelectorAll("table[class*=" + tableclass + "] tr ");
}
else {
rows = document.querySelectorAll("table tr");
}
for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll("td, th");
for (var j = 0; j < cols.length; j++) {
row.push(cols[j].innerText);
}
data.push(row.join(","));
}
downloadCSVFile(data.join("\n"), filename);
}
function downloadCSVFile(csv, filename) {
var csv_file, download_link;
csv_file = new Blob([csv], { type: "text/csv" });
download_link = document.createElement("a");
download_link.download = filename;
download_link.href = window.URL.createObjectURL(csv_file);
download_link.style.display = "none";
document.body.appendChild(download_link);
download_link.click();
}
function loadJs(sourceUrl) {
if (sourceUrl.Length == 0) {
console.error("Invalid source URL");
return;
}
var tag = document.createElement('script');
tag.src = sourceUrl;
tag.type = "text/javascript";
tag.onload = function () {
console.log("Script loaded successfully");
}
tag.onerror = function () {
console.error("Failed to load script");
}
document.body.appendChild(tag);
}
function ShowAlert(message) {
alert(message);
}
function checkboxConfirmChange(checkbox, message) {
if (checkbox.checked) {
var confirmBox = confirm(message);
if (confirmBox == true) {
checkbox.checked = true;
}
else {
checkbox.checked = false;
};
}
}
function expandMultiSelectParent(sender) {
console.log("test!");
}
</script>

44
Pages/_Layout.cshtml Normal file
View File

@@ -0,0 +1,44 @@
@using AzureNamingTool.Models;
@using Microsoft.AspNetCore.Components.Web
@using AzureNamingTool.Helpers
@inject StateContainer state
@namespace AzureNamingTool.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="css/site.css" rel="stylesheet" />
<link href="AzureNamingTool.styles.css" rel="stylesheet" />
<script>
// When the user clicks the button, the page scrolls to the top
function OnScrollEvent() {
document.documentElement.scrollTop = 0;
}
</script>
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
<ErrorBoundary>
@RenderBody()
</ErrorBoundary>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
<script src="js/jquery-3.7.0.min.js"></script>
<script src="js/bootstrap.bundle.min.js"></script>
</body>
</html>

85
Program.cs Normal file
View File

@@ -0,0 +1,85 @@
using AzureNamingTool.Attributes;
using BlazorDownloadFile;
using Blazored.Toast;
using Microsoft.OpenApi.Models;
using Blazored.Modal;
using Swashbuckle.AspNetCore.Swagger;
using System.Reflection;
using AzureNamingTool.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMvcCore().AddApiExplorer();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor().AddHubOptions(x => x.MaximumReceiveMessageSize = 102400000);
builder.Services.AddBlazorDownloadFile();
builder.Services.AddBlazoredToast();
builder.Services.AddBlazoredModal();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<StateContainer>();
builder.Services.AddSwaggerGen(c =>
{
c.OperationFilter<CustomHeaderSwaggerAttribute>();
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v" + Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion,
Title = "Azure Naming Tool API",
Description = "An ASP.NET Core Web API for managing the Azure Naming tool configuration. All API requests require the configured API Key (found in the site Admin configuration). You can find more details in the <a href=\"https://github.com/mspnp/AzureNamingTool/USINGTHEAPI.md\" target=\"_new\">Azure Naming Tool API documentation</a>."
});
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("http://localhost:44332")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddMemoryCache();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true) // allow any origin
.AllowCredentials()); // allow credentials
}
else
{
app.UseExceptionHandler("/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.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AzureNamingToolAPI"));
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
//app.UseAuthentication();
//app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"AzureNamingTool": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:51656;http://localhost:51657"
}
}
}

View File

@@ -1 +1,94 @@
# AzureNamingTool
[Overview](/ready/AzNamingTool/README.md) | [Installation](/ready/AzNamingTool/docs/INSTALLATION.md) | [Updating](/ready/AzNamingTool/docs/UPDATING.md) | [Using the API](/ready/AzNamingTool/docs/USINGTHEAPI.md) | [Release Notes](/ready/AzNamingTool/RELEASENOTES.md) | [Version History](/ready/AzNamingTool/docs/VERSIONHISTORY.md) | [FAQ](/ready/AzNamingTool/docs/FAQ.md) | [Contributors](/ready/AzNamingTool/docs/CONTRIBUTORS.md)
# Azure Naming Tool
<img src="./wwwroot/images/AzureNamingToolLogo.png?raw=true" alt="Image representing the Azure Naming Tool" title="Azure Naming Tool" height="150"/>
>
>Want to know about the latest news and features? Follow us on Twitter!
>
>![2021 Twitter logo - blue](https://user-images.githubusercontent.com/13591910/199250655-6f60c584-3a47-46af-a161-64908b7894fc.png) [AzureNamingTool](https://twitter.com/azurenamingtool)
>
[Overview](#overview)
[Azure Academy Video](#azure-academy-video)
[Project Structure](#project-structure)
[Installation](#installation)
[Important Notes](#important-notes)
[Pages](#pages)
## Overview
The Azure Naming Tool was created to help administrators define and manage their naming conventions, while providing a simple interface for users to generate a compliant name. The tool was developed using a naming pattern based on [Microsoft's best practices](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/naming-and-tagging). Once an administrator has defined the organizational components, users can use the tool to generate a name for the desired Azure resource.
**Want a deep dive into the tool?**
Check out [Bryan Soltis'](https://github.com/BryanSoltis) blog here: [Deep Diving into the Azure Naming Tool](https://soltisweb.com/blog/detail/2022-12-02-deep-diving-into-the-azure-naming-tool)
## Azure Academy Video
[Dean Cefola](https://github.com/DeanCefola) with [Azure Academy](https://www.youtube.com/c/AzureAcademy) made a great overview video. Click the image below to view the video on installing and configuring the tool as a container.
[![Azure Academy Overview Video](/ready/AzNamingTool/wwwroot/Screenshots/AzureAcademyVideo.png)](https://youtu.be/Ztmxx_KhZdE)
## Project Structure
The Azure Naming Tool is a .NET 6 Blazor application with a RESTful API. The UI consists of several pages to allow the configuration and generation of Azure Resource names. The API provides a programmatic interface for the functionality. Additionally, the application contains Docker support, allowing the site to be run as a stand-alone application or a container.
### Project Components
* UI/Admin
* API
* JSON configuration files
* Dockerfile
## Installation
[Choosing an Installation Option](/ready/AzNamingTool/docs/INSTALLATION.md)
## Important Notes
The following are important notes/aspects of the Azure Naming Tool:
* The application is designed to run as a stand-alone solution, with no internet/Azure connection.
* The application can be run as a .NET 6 site, or as a Docker container.
* The site can be hosted in any environment, including internal or in a public/private cloud.
* The application uses local JSON files to store the configuration of the components.
* The application requires persistent storage. A volume is required to store configuration files, if running as a container.
* The application contains a *repository* folder, which contains the default component configuration JSON files. When deployed, these files are then copied to the *settings* folder.
* The Admin interface allows configurations to be "reset", if needed. This process copies the configuration from the *repository* folder to the *settings* folder.
* The API requires an API Key for all executions. A default API Key (guid) will be generated on first launch. This value can be updated in the Admin section.
* On the first launch, the application will prompt for the Admin password to be set.
![Admin Password Prompt](/ready/AzNamingTool/wwwroot/Screenshots/AdminPasswordPrompt.png)
## Pages
### Home Page
The Home Page provides an overview of the tool and the components.
![Home Page](/ready/AzNamingTool/wwwroot/Screenshots/HomePage.png)
### Configuration
The Configuration Page shows the current Name Generation configuration. This page also provides an Admin section for updating the configuration.
![Configuration Page](/ready/AzNamingTool/wwwroot/Screenshots/ConfigurationPage.png)
### Reference
The Reference Page provides examples for each type of Azure resource. The example values do not include any excluded naming components. Optional components are always displayed and are identified below the example. Since unique names are only required at specific scopes, the examples provided are only generated for the scopes above the resource scope: resource group, resource group & region, region, global, subscription, and tenant.
![Reference Page](/ready/AzNamingTool/wwwroot/Screenshots/ReferencePage.png)
### Generate
The Generate Page provides a dropdown menu to select an Azure resource. Once a resource is selected, naming component options are provided. Read-only components, like the value for a resource type or organization, cannot be changed. Optional components, if left blank, will be null and not shown in the output. Required components do not allow a null value, and the first value in the array is set as the default.
![Generate Page](/ready/AzNamingTool/wwwroot/Screenshots/GeneratePage1.png)

36
RELEASENOTES.md Normal file
View File

@@ -0,0 +1,36 @@
[Overview](/ready/AzNamingTool/README.md) | [Installation](/ready/AzNamingTool/docs/INSTALLATION.md) | [Updating](/ready/AzNamingTool/docs/UPDATING.md) | [Using the API](/ready/AzNamingTool/docs/USINGTHEAPI.md) | [Release Notes](/ready/AzNamingTool/RELEASENOTES.md) | [Version History](/ready/AzNamingTool/docs/VERSIONHISTORY.md) | [FAQ](/ready/AzNamingTool/docs/FAQ.md) | [Contributors](/ready/AzNamingTool/docs/CONTRIBUTORS.md)
# Release Notes
## Version 2.8.0 (current)
### IMPORTANT NOTES
- The Azure Naming Tool will moving to a new GitHub repo in the next version! This change will allow us more frequent updates, enhanced features, and the ability to request new features and report bugs. [**View the Azure Naming Tool V3 Migration Guide**](https://github.com/mspnp/AzureNamingTool/docs/V3REPOSITORYMIGRATION.md).
- You can learn more about the new Identity Provider Integration in [**HERE**](https://soltisweb.com/blog/detail/2023-06-azurenamingtool-identityproviderintegrationdeepdive).
### Features
- Added Identity Settings to site (to identify users when authenticating using an Identity provider)
- Added functionality to set Identity Header Name
- Added functionality to assign user identity as Admin
- Added logging for users accessing the site
- Added logging for users updating configuration
- Added logging for Name Generation
- Updated Admin Page layout/styling
- Added User name to header, when using Identity provider
- Added functionality to configure min/max lengths for components
- Added site setting to enable/disable resource type editing
- Added functionality to configure min/max lengths for resource types
- Updated site messaging
- Updated default configuration data
- Updated Installation documentation
- Updated documentation structure/formatting
### Bug fixes
- Updated Resource Name Generation to include component details
- Optimized code in several files
- Updated Home page documentation
- Updated Name Generation process to properly handle optional custom components
- Updated Bootstrap references to 5.3
- Updated JS/CSS references to local file versions
[RELEASE NOTES ARCHIVE](/ready/AzNamingTool/docs/RELEASENOTESARCHIVE.md)

41
SECURITY.md Normal file
View File

@@ -0,0 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

116
Services/AdminLogService.cs Normal file
View File

@@ -0,0 +1,116 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using AzureNamingTool.Pages;
using System.Text.Json;
namespace AzureNamingTool.Services
{
public class AdminLogService
{
private static ServiceResponse serviceResponse = new();
/// <summary>
/// This function returns the Admin log.
/// </summary>
/// <returns>List of AdminLogMessages - List of Adming Log messages.</returns>
public static async Task<ServiceResponse> GetItems()
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<AdminLogMessage>();
serviceResponse.ResponseObject = items.OrderByDescending(x => x.CreatedOn).ToList();
serviceResponse.Success = true;
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage { Title = "Error", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
/// <summary>
/// This function logs the Admin message.
/// </summary>
public static async Task<ServiceResponse> PostItem(AdminLogMessage adminlogMessage)
{
ServiceResponse serviceReponse = new();
try
{
// Log the created name
var items = await ConfigurationHelper.GetList<AdminLogMessage>();
if (items != null)
{
if (items.Count > 0)
{
adminlogMessage.Id = items.Max(x => x.Id) + 1;
}
}
items.Add(adminlogMessage);
// Write items to file
await ConfigurationHelper.WriteList<AdminLogMessage>(items);
serviceReponse.Success = true;
}
catch (Exception)
{
// No exception is logged due to this function being the function that would complete the action.
serviceReponse.Success = false;
}
return serviceReponse;
}
/// <summary>
/// This function clears the Admin log.
/// </summary>
/// <returns>void</returns>
public static async Task<ServiceResponse> DeleteAllItems()
{
ServiceResponse serviceReponse = new();
try
{
List<AdminLogMessage> lstAdminLogMessages = new();
await ConfigurationHelper.WriteList<AdminLogMessage>(lstAdminLogMessages);
serviceReponse.Success = true;
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
}
return serviceReponse;
}
public static async Task<ServiceResponse> PostConfig(List<AdminLogMessage> items)
{
try
{
// Get list of items
var newitems = new List<AdminLogMessage>();
int i = 1;
// Determine new item id
foreach (AdminLogMessage item in items)
{
item.Id = i;
newitems.Add(item);
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<AdminLogMessage>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
}
}

95
Services/AdminService.cs Normal file
View File

@@ -0,0 +1,95 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using Microsoft.AspNetCore.Mvc;
using System;
namespace AzureNamingTool.Services
{
public class AdminService
{
private static ServiceResponse serviceResponse = new();
private static SiteConfiguration config = ConfigurationHelper.GetConfigurationData();
public static async Task<ServiceResponse> UpdatePassword(string password)
{
try
{
if (ValidationHelper.ValidatePassword(password))
{
config.AdminPassword = GeneralHelper.EncryptString(password, config.SALTKey);
ConfigurationHelper.UpdateSettings(config);
serviceResponse.Success = true;
}
else
{
serviceResponse.Success = false;
serviceResponse.ResponseObject = "The pasword does not meet the security requirements.";
}
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GenerateAPIKey()
{
try
{
// Set the new api key
Guid guid = Guid.NewGuid();
config.APIKey = GeneralHelper.EncryptString(guid.ToString(), config.SALTKey);
ConfigurationHelper.UpdateSettings(config);
serviceResponse.ResponseObject = guid.ToString();
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> UpdateAPIKey(string apikey)
{
try
{
config.APIKey = GeneralHelper.EncryptString(apikey, config.SALTKey);
ConfigurationHelper.UpdateSettings(config);
serviceResponse.ResponseObject = apikey;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> UpdateIdentityHeaderName(string identityheadername)
{
try
{
config.IdentityHeaderName = GeneralHelper.EncryptString(identityheadername, config.SALTKey);
ConfigurationHelper.UpdateSettings(config);
serviceResponse.ResponseObject = identityheadername;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
}
}

View File

@@ -0,0 +1,170 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
namespace AzureNamingTool.Services
{
public class AdminUserService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> GetItems()
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<AdminUser>();
serviceResponse.ResponseObject = items.OrderBy(x => x.Name).ToList();
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItem(string name)
{
try
{
// Get list of items
var data = await ConfigurationHelper.GetList<AdminUser>();
var item = data.Find(x => x.Name == name);
serviceResponse.ResponseObject = item;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostItem(AdminUser item)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<AdminUser>();
// Set the new id
if (item.Id == 0)
{
if (items.Count > 0)
{
item.Id = items.Max(t => t.Id) + 1;
}
else
{
item.Id = 1;
}
}
int position = 1;
items = items.OrderBy(x => x.Name).ToList();
// Determine new item id
if (items.Count > 0)
{
// Check if the item already exists
if (items.Exists(x => x.Id == item.Id))
{
// Remove the updated item from the list
var existingitem = items.Find(x => x.Id == item.Id);
int index = items.IndexOf(existingitem);
items.RemoveAt(index);
}
// Check for the new sort order
if (items.Exists(x => x.Id == item.Id))
{
// Remove the updated item from the list
items.Insert(items.IndexOf(items.FirstOrDefault(x => x.Id == item.Id)), item);
}
else
{
// Put the item at the end
items.Add(item);
}
}
else
{
item.Id = 1;
items.Add(item);
}
position = 1;
// Write items to file
await ConfigurationHelper.WriteList<AdminUser>(items);
serviceResponse.ResponseObject = "Item added!";
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> DeleteItem(int id)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<AdminUser>();
// Get the specified item
var item = items.Find(x => x.Id == id);
// Remove the item from the collection
items.Remove(item);
// Write items to file
await ConfigurationHelper.WriteList<AdminUser>(items);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostConfig(List<AdminUser> items)
{
try
{
// Get list of items
var newitems = new List<AdminUser>();
int i = 1;
// Determine new item id
foreach (AdminUser item in items)
{
item.Id = i;
newitems.Add(item);
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<AdminUser>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
}
}

View File

@@ -0,0 +1,233 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
namespace AzureNamingTool.Services
{
public class CustomComponentService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> GetItems()
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<CustomComponent>();
serviceResponse.ResponseObject = items.OrderBy(x => x.SortOrder).ToList();
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItemsByParentType(string parenttype)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<CustomComponent>();
serviceResponse.ResponseObject = items.Where(x => x.ParentComponent == parenttype).OrderBy(x => x.SortOrder).ToList();
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItem(int id)
{
try
{
// Get list of items
var data = await ConfigurationHelper.GetList<CustomComponent>();
var item = data.Find(x => x.Id == id);
serviceResponse.ResponseObject = item;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostItem(CustomComponent item)
{
try
{
// Make sure the new item short name only contains letters/numbers
if (!ValidationHelper.CheckAlphanumeric(item.ShortName))
{
serviceResponse.Success = false;
serviceResponse.ResponseObject = "Short name must be alphanumeric.";
return serviceResponse;
}
// Force lowercase on the shortname
item.ShortName = item.ShortName.ToLower();
// Get list of items
var items = await ConfigurationHelper.GetList<CustomComponent>();
// Set the new id
if (item.Id == 0)
{
item.Id = items.Count + 1;
}
int position = 1;
items = items.OrderBy(x => x.SortOrder).ToList();
if (item.SortOrder == 0)
{
if (items.Count > 0)
{
item.Id = items.Max(t => t.Id) + 1;
}
}
// Determine new item id
if (items.Count > 0)
{
// Check if the item already exists
if (items.Exists(x => x.Id == item.Id))
{
// Remove the updated item from the list
var existingitem = items.Find(x => x.Id == item.Id);
int index = items.IndexOf(existingitem);
items.RemoveAt(index);
}
// Reset the sort order of the list
foreach (CustomComponent thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
thisitem.SortOrder = position;
position += 1;
}
// Check for the new sort order
if (items.Exists(x => x.SortOrder == item.SortOrder))
{
// Remove the updated item from the list
items.Insert(items.IndexOf(items.FirstOrDefault(x => x.SortOrder == item.SortOrder)), item);
}
else
{
// Put the item at the end
items.Add(item);
}
}
else
{
item.Id = 1;
item.SortOrder = 1;
items.Add(item);
}
position = 1;
foreach (CustomComponent thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
thisitem.SortOrder = position;
position += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<CustomComponent>(items);
serviceResponse.ResponseObject = "Item added!";
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> DeleteItem(int id)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<CustomComponent>();
// Get the specified item
var item = items.Find(x => x.Id == id);
// Remove the item from the collection
items.Remove(item);
// Update all the sort order values to reflect the removal
int position = 1;
foreach (CustomComponent thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
thisitem.SortOrder = position;
position += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<CustomComponent>(items);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostConfig(List<CustomComponent> items)
{
try
{
// Get list of items
var newitems = new List<CustomComponent>();
int i = 1;
// Determine new item id
foreach (CustomComponent item in items)
{
// Make sure the new item short name only contains letters/numbers
if (!ValidationHelper.CheckAlphanumeric(item.ShortName))
{
serviceResponse.Success = false;
serviceResponse.ResponseObject = "Short name must be alphanumeric.";
return serviceResponse;
}
// Force lowercase on the shortname
item.ShortName = item.ShortName.ToLower();
item.Id = i;
item.SortOrder = i;
newitems.Add(item);
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<CustomComponent>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
}
}

View File

@@ -0,0 +1,163 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using System.Text.Json;
namespace AzureNamingTool.Services
{
public class GeneratedNamesService
{
private static ServiceResponse serviceResponse = new();
/// <summary>
/// This function gets the generated names log.
/// </summary>
/// <returns>List of GeneratedNames - List of generated names</returns>
public static async Task<ServiceResponse> GetItems()
{
List<GeneratedName> lstGeneratedNames = new();
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<GeneratedName>();
serviceResponse.ResponseObject = items.OrderByDescending(x => x.CreatedOn).ToList();
serviceResponse.Success = true;
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage{ Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItem(int id)
{
try
{
// Get list of items
var data = await ConfigurationHelper.GetList<GeneratedName>();
var item = data.Find(x => x.Id == id);
serviceResponse.ResponseObject = item;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
/// <summary>
/// This function logs the generated name.
/// </summary>
/// <param name="lstGeneratedName">GeneratedName - Generated name and components.</param>
public static async Task<ServiceResponse> PostItem(GeneratedName generatedName)
{
ServiceResponse serviceReponse = new();
try
{
/// Get the previously generated names
var items = await ConfigurationHelper.GetList<GeneratedName>();
if ((items != null) && (items.Count > 0))
{
generatedName.Id = items.Max(x => x.Id) + 1;
}
else
{
generatedName.Id = 1;
}
items.Add(generatedName);
// Write items to file
ConfigurationHelper.WriteList<GeneratedName>(items);
CacheHelper.InvalidateCacheObject("generatednames.json");
serviceReponse.Success = true;
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage { Title = "ERROR", Message = ex.Message });
serviceReponse.Success = false;
}
return serviceReponse;
}
public static async Task<ServiceResponse> DeleteItem(int id)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<GeneratedName>();
// Get the specified item
var item = items.Find(x => x.Id == id);
// Remove the item from the collection
items.Remove(item);
// Write items to file
await ConfigurationHelper.WriteList<GeneratedName>(items);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
/// <summary>
/// This function clears the generated names log.
/// </summary>
/// <returns>void</returns>
public static async Task<ServiceResponse> DeleteAllItems()
{
ServiceResponse serviceReponse = new();
try
{
List<GeneratedName> items = new List<GeneratedName>();
await ConfigurationHelper.WriteList<GeneratedName>(items);
serviceReponse.Success = true;
}
catch (Exception ex)
{
await AdminLogService.PostItem(new AdminLogMessage { Title = "Error", Message = ex.Message });
serviceResponse.Success = false;
}
return serviceReponse;
}
public static async Task<ServiceResponse> PostConfig(List<GeneratedName> items)
{
try
{
// Get list of items
var newitems = new List<GeneratedName>();
int i = 1;
// Determine new item id
foreach (GeneratedName item in items)
{
item.Id = i;
newitems.Add(item);
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<GeneratedName>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
}
}

View File

@@ -0,0 +1,160 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using Microsoft.AspNetCore.SignalR;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AzureNamingTool.Services
{
public class ImportExportService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> ExportConfig(bool includeadmin = false)
{
try
{
ConfigurationData configdata = new();
// Get the current data
//ResourceComponents
serviceResponse = await ResourceComponentService.GetItems(true);
configdata.ResourceComponents = serviceResponse.ResponseObject;
//ResourceDelimiters
serviceResponse = await ResourceDelimiterService.GetItems(true);
configdata.ResourceDelimiters = serviceResponse.ResponseObject;
//ResourceEnvironments
serviceResponse = await ResourceEnvironmentService.GetItems();
configdata.ResourceEnvironments = serviceResponse.ResponseObject;
// ResourceFunctions
serviceResponse = await ResourceFunctionService.GetItems();
configdata.ResourceFunctions = serviceResponse.ResponseObject;
// ResourceLocations
serviceResponse = await ResourceLocationService.GetItems();
configdata.ResourceLocations = serviceResponse.ResponseObject;
// ResourceOrgs
serviceResponse = await ResourceOrgService.GetItems();
configdata.ResourceOrgs = serviceResponse.ResponseObject;
// ResourceProjAppSvc
serviceResponse = await ResourceProjAppSvcService.GetItems();
configdata.ResourceProjAppSvcs = serviceResponse.ResponseObject;
// ResourceTypes
serviceResponse = await ResourceTypeService.GetItems();
configdata.ResourceTypes = serviceResponse.ResponseObject;
// ResourceUnitDepts
serviceResponse = await ResourceUnitDeptService.GetItems();
configdata.ResourceUnitDepts = serviceResponse.ResponseObject;
// CustomComponents
serviceResponse = await CustomComponentService.GetItems();
configdata.CustomComponents = serviceResponse.ResponseObject;
//GeneratedNames
serviceResponse = await GeneratedNamesService.GetItems();
configdata.GeneratedNames = serviceResponse.ResponseObject;
//AdminLogs
serviceResponse = await AdminLogService.GetItems();
configdata.AdminLogs = serviceResponse.ResponseObject;
// Get the current settings
var config = ConfigurationHelper.GetConfigurationData();
configdata.DismissedAlerts = config.DismissedAlerts;
configdata.DuplicateNamesAllowed = config.DuplicateNamesAllowed;
// Get the security settings
if (includeadmin)
{
configdata.SALTKey = config.SALTKey;
configdata.AdminPassword = config.AdminPassword;
configdata.APIKey = config.APIKey;
//IdentityHeaderName
configdata.IdentityHeaderName = config.IdentityHeaderName;
//AdminUsers
serviceResponse = await AdminUserService.GetItems();
configdata.AdminUsers = serviceResponse.ResponseObject;
}
serviceResponse.ResponseObject = configdata;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostConfig(ConfigurationData configdata)
{
try
{
// Write all the configurations
await ResourceComponentService.PostConfig(configdata.ResourceComponents);
await ResourceDelimiterService.PostConfig(configdata.ResourceDelimiters);
await ResourceEnvironmentService.PostConfig(configdata.ResourceEnvironments);
await ResourceFunctionService.PostConfig(configdata.ResourceFunctions);
await ResourceLocationService.PostConfig(configdata.ResourceLocations);
await ResourceOrgService.PostConfig(configdata.ResourceOrgs);
await ResourceProjAppSvcService.PostConfig(configdata.ResourceProjAppSvcs);
await ResourceTypeService.PostConfig(configdata.ResourceTypes);
await ResourceUnitDeptService.PostConfig(configdata.ResourceUnitDepts);
await CustomComponentService.PostConfig(configdata.CustomComponents);
await GeneratedNamesService.PostConfig(configdata.GeneratedNames);
await AdminUserService.PostConfig(configdata.AdminUsers);
await AdminLogService.PostConfig(configdata.AdminLogs);
var config = ConfigurationHelper.GetConfigurationData();
config.DismissedAlerts = configdata.DismissedAlerts;
config.DuplicateNamesAllowed = configdata.DuplicateNamesAllowed;
// Set the security settings
if (configdata.SALTKey != null)
{
config.SALTKey = configdata.SALTKey;
}
if(configdata.AdminPassword != null)
{
config.AdminPassword = configdata.AdminPassword;
}
if (configdata.APIKey != null)
{
config.APIKey = configdata.APIKey;
}
if (configdata.IdentityHeaderName != null)
{
config.IdentityHeaderName = configdata.IdentityHeaderName;
}
var jsonWriteOptions = new JsonSerializerOptions()
{
WriteIndented = true
};
jsonWriteOptions.Converters.Add(new JsonStringEnumConverter());
var newJson = JsonSerializer.Serialize(config, jsonWriteOptions);
var appSettingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings/appsettings.json");
File.WriteAllText(appSettingsPath, newJson);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
}
}

112
Services/PolicyService.cs Normal file
View File

@@ -0,0 +1,112 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using Microsoft.AspNetCore.Mvc;
namespace AzureNamingTool.Services
{
public class PolicyService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> GetPolicy()
{
try
{
var delimiter = '-';
var nameComponents = await Helpers.ConfigurationHelper.GetList<ResourceComponent>();
var resourceTypes = await Helpers.ConfigurationHelper.GetList<ResourceType>();
var unitDepts = await Helpers.ConfigurationHelper.GetList<ResourceUnitDept>();
var environments = await Helpers.ConfigurationHelper.GetList<ResourceEnvironment>();
var locations = await Helpers.ConfigurationHelper.GetList<ResourceLocation>();
var orgs = await Helpers.ConfigurationHelper.GetList<ResourceOrg>();
var Functions = await Helpers.ConfigurationHelper.GetList<ResourceFunction>();
var projectAppSvcs = await Helpers.ConfigurationHelper.GetList<ResourceProjAppSvc>();
List<String> validations = new();
var maxSortOrder = 0;
foreach (var nameComponent in nameComponents)
{
var name = (String)nameComponent.Name;
var isEnabled = (bool)nameComponent.Enabled;
var sortOrder = (int)nameComponent.SortOrder;
maxSortOrder = sortOrder - 1;
if (isEnabled)
{
switch (name)
{
case "ResourceType":
AddValidations(resourceTypes, validations, delimiter, sortOrder);
break;
case "ResourceUnitDept":
AddValidations(unitDepts, validations, delimiter, sortOrder);
break;
case "ResourceEnvironment":
AddValidations(environments, validations, delimiter, sortOrder);
break;
case "ResourceLocation":
AddValidations(locations, validations, delimiter, sortOrder);
break;
case "ResourceOrgs":
AddValidations(orgs, validations, delimiter, sortOrder);
break;
case "ResourceFunctions":
AddValidations(Functions, validations, delimiter, sortOrder);
break;
case "ResourceProjAppSvcs":
AddValidations(projectAppSvcs, validations, delimiter, sortOrder);
break;
default:
break;
}
}
}
var property = new PolicyProperty("Name Validation", "This policy enables you to restrict the name can be specified when deploying a Azure Resource.");
property.PolicyRule = PolicyRuleFactory.GetNameValidationRules(validations.Select(x => new PolicyRule(x, delimiter)).ToList(), delimiter);
PolicyDefinition definition = new(property);
//serviceResponse.ResponseObject = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(definition.ToString())).ToArray();
serviceResponse.ResponseObject = definition;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
private static void AddValidations(dynamic list, List<string> validations, Char delimiter, int level)
{
if (validations.Count == 0)
{
foreach (var item in list)
{
if (item.ShortName.Trim() != String.Empty)
{
var key = $"{item.ShortName}{delimiter}*";
if (!validations.Contains(key))
validations.Add(key);
}
}
}
else
{
foreach (var item in list)
{
if (item.ShortName.Trim() != String.Empty)
{
foreach (var validation in validations.Where(x => x.Count(p => p.ToString().Contains(delimiter)) == level - 1).ToList())
{
var key = $"{validation.Replace("*", "")}{item.ShortName}{delimiter}*";
if (!validations.Contains(key))
validations.Add(key);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,255 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
using System.Net.WebSockets;
using System.Security.AccessControl;
namespace AzureNamingTool.Services
{
public class ResourceComponentService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> GetItems(bool admin)
{
try
{
var items = await ConfigurationHelper.GetList<ResourceComponent>();
if (!admin)
{
serviceResponse.ResponseObject = items.Where(x => x.Enabled == true).OrderBy(y => y.SortOrder).ToList();
}
else
{
serviceResponse.ResponseObject = items.OrderBy(y => y.SortOrder).OrderByDescending(y => y.Enabled).ToList();
}
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItem(int id)
{
try
{
// Get list of items
var data = await ConfigurationHelper.GetList<ResourceComponent>();
var item = data.Find(x => x.Id == id);
serviceResponse.ResponseObject = item;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostItem(ResourceComponent item)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<ResourceComponent>();
// Set the new id
if (item.Id == 0)
{
item.Id = items.Count + 1;
}
int position = 1;
items = items.OrderBy(x => x.SortOrder).ToList();
if (item.SortOrder == 0)
{
item.SortOrder = items.Count + 1;
}
// Determine new item id
if (items.Count > 0)
{
// Check if the item already exists
if (items.Exists(x => x.Name == item.Name))
{
// Remove the updated item from the list
var existingitem = items.Find(x => (x.Name == item.Name));
int index = items.IndexOf(existingitem);
items.RemoveAt(index);
}
// Reset the sort order of the list
foreach (ResourceComponent thisitem in items.OrderBy(x => x.SortOrder).OrderByDescending(x => x.Enabled).ToList())
{
thisitem.SortOrder = position;
position += 1;
}
// Check for the new sort order
if (items.Exists(x => x.SortOrder == item.SortOrder))
{
// Insert the new item
items.Insert(items.IndexOf(items.FirstOrDefault(x => x.SortOrder == item.SortOrder)), item);
}
else
{
// Put the item at the end
items.Add(item);
}
}
else
{
item.Id = 1;
item.SortOrder = 1;
items.Add(item);
}
position = 1;
foreach (ResourceComponent thisitem in items.OrderBy(x => x.SortOrder).OrderByDescending(x => x.Enabled).ToList())
{
thisitem.SortOrder = position;
thisitem.Id = position;
position += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<ResourceComponent>(items);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> DeleteItem(int id)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<ResourceComponent>();
// Get the specified item
var item = items.Find(x => x.Id == id);
// Delete any resource type settings for the component
List<string> currentvalues = new();
serviceResponse = await ResourceTypeService.GetItems();
List<Models.ResourceType> resourceTypes = (List<Models.ResourceType>)serviceResponse.ResponseObject;
foreach (Models.ResourceType currenttype in resourceTypes)
{
currentvalues = new List<string>(currenttype.Optional.Split(','));
if (currentvalues.Contains(GeneralHelper.NormalizeName(item.Name, false)))
{
currentvalues.Remove(GeneralHelper.NormalizeName(item.Name, false));
currenttype.Optional = String.Join(",", currentvalues.ToArray());
}
currentvalues = new List<string>(currenttype.Exclude.Split(','));
if (currentvalues.Contains(GeneralHelper.NormalizeName(item.Name, false)))
{
currentvalues.Remove(GeneralHelper.NormalizeName(item.Name, false));
currenttype.Exclude = String.Join(",", currentvalues.ToArray());
}
await ResourceTypeService.PostItem(currenttype);
}
// Delete any custom components for this resource component
var components = await ConfigurationHelper.GetList<CustomComponent>();
components.RemoveAll(x => x.ParentComponent == GeneralHelper.NormalizeName(item.Name, true));
await ConfigurationHelper.WriteList<CustomComponent>(components);
// Remove the item from the collection
items.Remove(item);
// Update all the sort order values to reflect the removal
int position = 1;
foreach (ResourceComponent thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
thisitem.SortOrder = position;
thisitem.Id = position;
position += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<ResourceComponent>(items);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostConfig(List<ResourceComponent> items)
{
try
{
string[] componentnames = new string[8] { "ResourceEnvironment", "ResourceInstance", "ResourceLocation", "ResourceOrg", "ResourceProjAppSvc", "ResourceType", "ResourceUnitDept", "ResourceFunction" };
var newitems = new List<ResourceComponent>();
// Examine the current items
foreach (ResourceComponent item in items)
{
// Check if the item is valid
if (!componentnames.Contains(item.Name))
{
item.IsCustom = true;
}
// Add the item to the update list
newitems.Add(item);
}
// Make sure all the component names are present
foreach (string name in componentnames)
{
if (!newitems.Exists(x => x.Name == name))
{
// Create a component object
ResourceComponent newitem = new()
{
Name = name,
Enabled = false
};
newitems.Add(newitem);
}
}
// Determine new item ids
int i = 1;
foreach (ResourceComponent item in newitems.OrderByDescending(x => x.Enabled).OrderBy(x => x.SortOrder))
{
item.Id = i;
item.SortOrder = i;
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<ResourceComponent>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
}
}

View File

@@ -0,0 +1,218 @@
using AzureNamingTool.Helpers;
using AzureNamingTool.Models;
namespace AzureNamingTool.Services
{
public class ResourceDelimiterService
{
private static ServiceResponse serviceResponse = new();
public static async Task<ServiceResponse> GetItems(bool admin)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<ResourceDelimiter>();
if (!admin)
{
serviceResponse.ResponseObject = items.Where(x => x.Enabled == true).OrderBy(y => y.SortOrder).ToList();
}
else
{
serviceResponse.ResponseObject = items.OrderBy(y => y.SortOrder).OrderByDescending(y => y.Enabled).ToList();
}
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetItem(int id)
{
try
{
// Get list of items
var data = await ConfigurationHelper.GetList<ResourceDelimiter>();
var item = data.Find(x => x.Id == id);
serviceResponse.ResponseObject = item;
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> GetCurrentItem()
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<ResourceDelimiter>();
serviceResponse.ResponseObject = items.OrderBy(y => y.SortOrder).OrderByDescending(y => y.Enabled).ToList()[0];
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostItem(ResourceDelimiter item)
{
try
{
// Get list of items
var items = await ConfigurationHelper.GetList<ResourceDelimiter>();
// Set the new id
if (item.Id == 0)
{
item.Id = items.Count + 1;
}
item.Enabled = true;
int position = 1;
items = items.OrderBy(x => x.SortOrder).ToList();
if (item.SortOrder == 0)
{
item.SortOrder = items.Count + 1;
}
// Determine new item id
if (items.Count > 0)
{
// Check if the item already exists
if (items.Exists(x => x.Id == item.Id))
{
// Remove the updated item from the list
var existingitem = items.Find(x => x.Id == item.Id);
int index = items.IndexOf(existingitem);
items.RemoveAt(index);
}
// Reset the sort order of the list
foreach (ResourceDelimiter thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
if (item.Enabled && thisitem.Id != item.Id)
{
thisitem.Enabled = false;
}
thisitem.SortOrder = position;
position += 1;
}
// Check for the new sort order
if (items.Exists(x => x.SortOrder == item.SortOrder))
{
// Remove the updated item from the list
items.Insert(items.IndexOf(items.FirstOrDefault(x => x.SortOrder == item.SortOrder)), item);
}
else
{
// Put the item at the end
items.Add(item);
}
}
else
{
item.Id = 1;
item.SortOrder = 1;
items.Add(item);
}
position = 1;
foreach (ResourceDelimiter thisitem in items.OrderBy(x => x.SortOrder).ToList())
{
thisitem.SortOrder = position;
position += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<ResourceDelimiter>(items);
serviceResponse.ResponseObject = "Item added!";
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.ResponseObject = ex;
serviceResponse.Success = false;
}
return serviceResponse;
}
public static async Task<ServiceResponse> PostConfig(List<ResourceDelimiter> items)
{
try
{
string[,] delimiters = new string[4, 2] { { "dash", "-" }, { "underscore", "_" }, { "period", "." }, { "none", "" } };
var newitems = new List<ResourceDelimiter>();
// Examine the current items
foreach (ResourceDelimiter item in items)
{
// Check if the item is valid
for (int j = 0; j <= delimiters.GetUpperBound(0); j++)
{
if ((item.Name == delimiters[j, 0]) && (item.Delimiter == delimiters[j, 1]))
{
// Add the item to the update list
newitems.Add(item);
break;
}
}
}
// Make sure all the delimiters are present
for (int k = 0; k <= delimiters.GetUpperBound(0); k++)
{
if (!newitems.Exists(x => x.Name == delimiters[k, 0] && x.Delimiter == delimiters[k, 1]))
{
// Create a delimiter object
ResourceDelimiter newitem = new()
{
Name = delimiters[k, 0],
Delimiter = delimiters[k, 1],
Enabled = false
};
newitems.Add(newitem);
}
}
// Determine new item ids/order
int i = 1;
var sorteditems = newitems.OrderBy(x => x.SortOrder).OrderByDescending(x => x.Enabled);
foreach (ResourceDelimiter item in sorteditems)
{
item.Id = i;
item.SortOrder = i;
i += 1;
}
// Write items to file
await ConfigurationHelper.WriteList<ResourceDelimiter>(newitems);
serviceResponse.Success = true;
}
catch (Exception ex)
{
AdminLogService.PostItem(new AdminLogMessage() { Title = "ERROR", Message = ex.Message });
serviceResponse.Success = false;
serviceResponse.ResponseObject = ex;
}
return serviceResponse;
}
}
}

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