diff --git a/.gitignore b/.gitignore index add57be..22038ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,261 @@ -bin/ -obj/ -/packages/ -riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.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 + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# 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 +# TODO: 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 +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable 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 + +# 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 +node_modules/ +orleans.codegen.cs + +# 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 + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# 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 +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ diff --git a/src/AzureAppConfigurationEmulator/Components/App.razor b/src/AzureAppConfigurationEmulator/Components/App.razor new file mode 100644 index 0000000..b167d0e --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/App.razor @@ -0,0 +1,19 @@ + + + + + + + + + @* *@ + + + + + + + + + + \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureButton.razor b/src/AzureAppConfigurationEmulator/Components/AzureButton.razor new file mode 100644 index 0000000..bdd22ab --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureButton.razor @@ -0,0 +1,37 @@ + + +@code { + [Parameter] public AzureAppearance Appearance { get; set; } = AzureAppearance.Default; + + [Parameter] public bool IsDisabled { get; set; } + + [EditorRequired, Parameter] public string Label { get; set; } = null!; + + [Parameter] public EventCallback OnClick { get; set; } + + private string BackgroundClasses => Appearance switch + { + AzureAppearance.Default => "bg-white hover:bg-concrete disabled:bg-concrete dark:bg-cod-grey dark:hover:bg-shark dark:disabled:bg-shark", + AzureAppearance.Primary => "bg-lochmara hover:bg-science-blue active:bg-venice-blue disabled:bg-concrete dark:bg-lochmara dark:hover:bg-dodger-blue dark:active:bg-jordy-blue dark:disabled:bg-shark", + _ => throw new ArgumentOutOfRangeException() + }; + + private string BorderClasses => Appearance switch + { + AzureAppearance.Default => "border border-mine-shaft disabled:border-star-dust dark:border-desert-storm dark:disabled:border-natural-grey", + AzureAppearance.Primary => "border border-lochmara hover:border-science-blue active:border-venice-blue disabled:border-star-dust dark:border-lochmara dark:hover:border-dodger-blue dark:active:border-jordy-blue dark:disabled:border-natural-grey", + _ => throw new ArgumentOutOfRangeException() + }; + + private string ColorClasses => Appearance switch + { + AzureAppearance.Default => "text-mine-shaft disabled:text-star-dust dark:text-desert-storm dark:disabled:text-natural-grey", + AzureAppearance.Primary => "text-white disabled:text-star-dust dark:text-mine-shaft dark:disabled:text-natural-grey", + _ => throw new ArgumentOutOfRangeException() + }; + + public enum AzureAppearance { Default, Primary } + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureCheckbox.razor b/src/AzureAppConfigurationEmulator/Components/AzureCheckbox.razor new file mode 100644 index 0000000..8997aed --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureCheckbox.razor @@ -0,0 +1,7 @@ + + +@code { + [Parameter] public bool Checked { get; set; } + + [Parameter] public EventCallback OnChange { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureDescriptionList.razor b/src/AzureAppConfigurationEmulator/Components/AzureDescriptionList.razor new file mode 100644 index 0000000..60b63b1 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureDescriptionList.razor @@ -0,0 +1,11 @@ +
+ @foreach (var (key, value) in Descriptions) + { +
@key
+
@value
+ } +
+ +@code { + [EditorRequired, Parameter] public IDictionary Descriptions { get; set; } = null!; +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureDialog.razor b/src/AzureAppConfigurationEmulator/Components/AzureDialog.razor new file mode 100644 index 0000000..658066a --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureDialog.razor @@ -0,0 +1,27 @@ + + +
+
+ @HeaderContent +
+ +
+ @ChildContent +
+ +
+ @FooterContent +
+
+
+
+ +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } + + [Parameter] public RenderFragment? FooterContent { get; set; } + + [Parameter] public RenderFragment? HeaderContent { get; set; } + + [Parameter] public string Id { get; set; } = Guid.NewGuid().ToString(); +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureDialogFooter.razor b/src/AzureAppConfigurationEmulator/Components/AzureDialogFooter.razor new file mode 100644 index 0000000..78fbaf2 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureDialogFooter.razor @@ -0,0 +1,7 @@ +
+ @ChildContent +
+ +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureDialogHeader.razor b/src/AzureAppConfigurationEmulator/Components/AzureDialogHeader.razor new file mode 100644 index 0000000..7e2c396 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureDialogHeader.razor @@ -0,0 +1,29 @@ +@inject IDialogService DialogService +@rendermode InteractiveServer +@using AzureAppConfigurationEmulator.Services + +
+
+

@Title

+ + @if (!string.IsNullOrEmpty(Subtitle)) + { +
@Subtitle
+ } +
+ + +
+ +@code { + [CascadingParameter] public required AzureDialog AzureDialog { get; set; } + + [Parameter] public string? Subtitle { get; set; } + + [EditorRequired, Parameter] public required string Title { get; set; } + + private async Task HandleCloseClick(MouseEventArgs args) + { + await DialogService.CloseAsync(AzureDialog.Id); + } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureFilterChip.razor b/src/AzureAppConfigurationEmulator/Components/AzureFilterChip.razor new file mode 100644 index 0000000..b9d7b3a --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureFilterChip.razor @@ -0,0 +1,17 @@ + + +@code { + [EditorRequired, Parameter] public string Key { get; set; } = null!; + + [Parameter] public char Operator { get; set; } = ':'; + + [EditorRequired, Parameter] public string Value { get; set; } = null!; +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureTable.razor b/src/AzureAppConfigurationEmulator/Components/AzureTable.razor new file mode 100644 index 0000000..aaa9413 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureTable.razor @@ -0,0 +1,77 @@ +@typeparam TItem + + + + + + + @HeaderContent + + + + + @foreach (var item in Items) + { + + + + @RowTemplate(item) + + } + +
+
+ @if (Items.Any()) + { + + } +
+
+
+ +
+
+ +@code { + [EditorRequired, Parameter] public RenderFragment HeaderContent { get; set; } = null!; + + [Parameter] public IEnumerable Items { get; set; } = []; + + [EditorRequired, Parameter] public RenderFragment RowTemplate { get; set; } = null!; + + [Parameter] public ICollection SelectedItems { get; set; } = []; + + [Parameter] public EventCallback> SelectedItemsChanged { get; set; } + + private async Task HandleSelectedItemChange(ChangeEventArgs args, TItem item) + { + if (args is { Value: true }) + { + SelectedItems.Add(item); + } + else + { + SelectedItems.Remove(item); + } + + await SelectedItemsChanged.InvokeAsync(SelectedItems); + } + + private async Task HandleSelectedItemsChange(ChangeEventArgs args) + { + if (args is { Value: true }) + { + foreach (var value in Items) + { + SelectedItems.Add(value); + } + } + else + { + SelectedItems.Clear(); + } + + await SelectedItemsChanged.InvokeAsync(SelectedItems); + } + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureTableDataCell.razor b/src/AzureAppConfigurationEmulator/Components/AzureTableDataCell.razor new file mode 100644 index 0000000..75d0ec0 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureTableDataCell.razor @@ -0,0 +1,7 @@ + + @ChildContent + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureTableHeaderCell.razor b/src/AzureAppConfigurationEmulator/Components/AzureTableHeaderCell.razor new file mode 100644 index 0000000..a944b1a --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureTableHeaderCell.razor @@ -0,0 +1,7 @@ + + @ChildContent + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureTextField.razor b/src/AzureAppConfigurationEmulator/Components/AzureTextField.razor new file mode 100644 index 0000000..bc9d3f4 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureTextField.razor @@ -0,0 +1,35 @@ +
+ @if (!string.IsNullOrEmpty(Label)) + { + + } + + +
+ +@code { + [Parameter] public string Id { get; set; } = Guid.NewGuid().ToString(); + + [Parameter] public bool IsDisabled { get; set; } + + [Parameter] public bool IsReadOnly { get; set; } + + [Parameter] public bool IsRequired { get; set; } + + [Parameter] public string? Label { get; set; } + + [Parameter] public string? Placeholder { get; set; } + + [Parameter] public string Type { get; set; } = "text"; + + [Parameter] public string? Value { get; set; } + + [Parameter] public EventCallback ValueChanged { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureToolbar.razor b/src/AzureAppConfigurationEmulator/Components/AzureToolbar.razor new file mode 100644 index 0000000..c7245a8 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureToolbar.razor @@ -0,0 +1,7 @@ + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureToolbarButton.razor b/src/AzureAppConfigurationEmulator/Components/AzureToolbarButton.razor new file mode 100644 index 0000000..7d16818 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureToolbarButton.razor @@ -0,0 +1,15 @@ +
  • + +
  • + +@code { + [Parameter] public bool IsDisabled { get; set; } + + [EditorRequired, Parameter] public string Label { get; set; } = null!; + + [Parameter] public EventCallback OnClick { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/AzureToolbarDivider.razor b/src/AzureAppConfigurationEmulator/Components/AzureToolbarDivider.razor new file mode 100644 index 0000000..3c0b094 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/AzureToolbarDivider.razor @@ -0,0 +1,5 @@ +
  • + +
  • \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingCreateDialog.razor b/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingCreateDialog.razor new file mode 100644 index 0000000..7c0a796 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingCreateDialog.razor @@ -0,0 +1,59 @@ +@inject IConfigurationSettingFactory ConfigurationSettingFactory +@inject IDialogService DialogService +@rendermode InteractiveServer +@using AzureAppConfigurationEmulator.Entities +@using AzureAppConfigurationEmulator.Factories +@using AzureAppConfigurationEmulator.Services + + + + + + + +
    + + + + +
    +
    + + + + + + +
    + +@code { + [Parameter] public string Id { get; set; } = Guid.NewGuid().ToString(); + + [Parameter] public EventCallback OnCreate { get; set; } + + private bool IsDisabled => string.IsNullOrEmpty(Input.Key); + + private InputModel Input { get; } = new(); + + private async Task HandleCreateClick(MouseEventArgs args) + { + if (!string.IsNullOrEmpty(Input.Key)) + { + await OnCreate.InvokeAsync(ConfigurationSettingFactory.Create(Input.Key, Input.Label, Input.ContentType, Input.Value)); + + await DialogService.CloseAsync(Id); + } + } + + private class InputModel + { + public string? Key { get; set; } + + public string? Value { get; set; } + + public string? Label { get; set; } + + public string? ContentType { get; set; } + } + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingTable.razor b/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingTable.razor new file mode 100644 index 0000000..49557a2 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/ConfigurationSettingTable.razor @@ -0,0 +1,29 @@ +@using AzureAppConfigurationEmulator.Entities + + + + Key + Value + Label + Last modified + Content type + + + + @context.Key + @(IsValuesHidden ? "(Hidden value)" : context.Value) + @(!string.IsNullOrEmpty(context.Label) ? context.Label : "(No label)") + @context.LastModified.ToString("G") + @context.ContentType + + + +@code { + [Parameter] public IEnumerable ConfigurationSettings { get; set; } = []; + + [Parameter] public bool IsValuesHidden { get; set; } + + [Parameter] public ICollection SelectedConfigurationSettings { get; set; } = []; + + [Parameter] public EventCallback> SelectedConfigurationSettingsChanged { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/EssentialsAccordion.razor b/src/AzureAppConfigurationEmulator/Components/EssentialsAccordion.razor new file mode 100644 index 0000000..8981cb2 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/EssentialsAccordion.razor @@ -0,0 +1,40 @@ +@inject IServer Server +@using Microsoft.AspNetCore.Hosting.Server +@using Microsoft.AspNetCore.Hosting.Server.Features + +
    + + +
    + @foreach (var descriptions in Descriptions) + { +
    + +
    + } +
    +
    + +@code { + + private IEnumerable> Descriptions => new List> + { + new Dictionary + { + { "Resource group", "-" }, + { "Status", "Succeeded" }, + { "Location", "-" }, + { "Subscription", "-" }, + { "Subscription ID", "-" } + }, + new Dictionary + { + { "Endpoint", Server.Features.Get()?.Addresses.FirstOrDefault() ?? "-" }, + { "Pricing tier", "-" }, + { "Soft-delete", "Disabled" }, + { "Purge protection", "Disabled" }, + { "Geo-replication", "-" } + } + }; + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/EssentialsAccordionHeader.razor b/src/AzureAppConfigurationEmulator/Components/EssentialsAccordionHeader.razor new file mode 100644 index 0000000..a967d2e --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/EssentialsAccordionHeader.razor @@ -0,0 +1,9 @@ +
    + + +
    JSON View
    +
    \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/FeatureFlagConfigurationSettingTable.razor b/src/AzureAppConfigurationEmulator/Components/FeatureFlagConfigurationSettingTable.razor new file mode 100644 index 0000000..9227998 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/FeatureFlagConfigurationSettingTable.razor @@ -0,0 +1,31 @@ +@using AzureAppConfigurationEmulator.Entities + + + + Name + Label + Enabled + Feature filter(s) + Description + Last modified + Key + + + + @context.Id + @(!string.IsNullOrEmpty(context.Label) ? context.Label : "(No label)") + @context.Enabled + @context.ClientFilters.Count + @context.Description + @context.LastModified.ToString("G") + @context.Key + + + +@code { + [Parameter] public IEnumerable ConfigurationSettings { get; set; } = []; + + [Parameter] public ICollection SelectedConfigurationSettings { get; set; } = []; + + [Parameter] public EventCallback> SelectedConfigurationSettingsChanged { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Layout/MainLayout.razor b/src/AzureAppConfigurationEmulator/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..a56b950 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Layout/MainLayout.razor @@ -0,0 +1,38 @@ +@inherits LayoutComponentBase +@using System.Net + +
    +
    + +

    Emulator for Azure App Configuration

    +
    + +
    +
    Home
    +
    + +
    +
    +
    + +
    +

    @Dns.GetHostName()

    + +
    App Configuration
    +
    +
    + +
    +
    + +
    + +
    @Body
    +
    +
    + + \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Layout/NavGroup.razor b/src/AzureAppConfigurationEmulator/Components/Layout/NavGroup.razor new file mode 100644 index 0000000..65cc868 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Layout/NavGroup.razor @@ -0,0 +1,13 @@ +
    +
    @Header
    + +
      + @ChildContent +
    +
    + +@code { + [EditorRequired, Parameter] public string Header { get; set; } = null!; + + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Layout/NavItem.razor b/src/AzureAppConfigurationEmulator/Components/Layout/NavItem.razor new file mode 100644 index 0000000..504ebad --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Layout/NavItem.razor @@ -0,0 +1,15 @@ + + +@code { + [EditorRequired, Parameter] public string Href { get; set; } = null!; + + [EditorRequired, Parameter] public string Label { get; set; } = null!; + + [Parameter] public NavLinkMatch Match { get; set; } = NavLinkMatch.Prefix; +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Layout/NavMenu.razor b/src/AzureAppConfigurationEmulator/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..7a755d6 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Layout/NavMenu.razor @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Pages/AccessKeys.razor b/src/AzureAppConfigurationEmulator/Components/Pages/AccessKeys.razor new file mode 100644 index 0000000..aafcb8b --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Pages/AccessKeys.razor @@ -0,0 +1,63 @@ +@inject IOptionsMonitor HmacOptionsMonitor +@inject IServer Server +@page "/keys" +@rendermode InteractiveServer +@using AzureAppConfigurationEmulator.Authentication +@using Microsoft.AspNetCore.Hosting.Server +@using Microsoft.AspNetCore.Hosting.Server.Features +@using Microsoft.Extensions.Options + +Access keys + +
    + + + + +
    +
    +
    Access keys
    + +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    Primary key
    + +
    + + + +
    +
    +
    +
    +
    +
    + +@code { + private string ConnectionString => $"Endpoint={Endpoint};Id={HmacOptions.Credential};Secret={HmacOptions.Secret}"; + + private string? Endpoint => Server.Features.Get()?.Addresses.FirstOrDefault(); + + private HmacOptions HmacOptions => HmacOptionsMonitor.Get(HmacDefaults.AuthenticationScheme); + + private bool IsHidden { get; set; } = true; + + private void HandleValuesClick(MouseEventArgs args) + { + IsHidden = !IsHidden; + StateHasChanged(); + } + +} diff --git a/src/AzureAppConfigurationEmulator/Components/Pages/ConfigurationExplorer.razor b/src/AzureAppConfigurationEmulator/Components/Pages/ConfigurationExplorer.razor new file mode 100644 index 0000000..02cae48 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Pages/ConfigurationExplorer.razor @@ -0,0 +1,123 @@ +@attribute [StreamRendering] +@inject IConfigurationSettingRepository ConfigurationSettingRepository +@inject IDialogService DialogService +@page "/kvs" +@rendermode InteractiveServer +@using AzureAppConfigurationEmulator.Entities +@using AzureAppConfigurationEmulator.Repositories +@using AzureAppConfigurationEmulator.Services + +Configuration explorer + +
    + + + + + + + + + + + + + + + + + +
    +
    +
    Filter all key-values:
    + + + + +
    + +
    + +
    + +
    Loaded @FilteredConfigurationSettings.Count() key-values with @FilteredConfigurationSettings.Select(setting => setting.Key).Distinct().Count() unique keys.
    +
    + +
    + +
    +
    + + + +@code { + private ICollection ConfigurationSettings { get; } = []; + + private IEnumerable FilteredConfigurationSettings => ConfigurationSettings.Where(setting => string.IsNullOrEmpty(SearchQuery) || setting.Key.Contains(SearchQuery) || setting.Label?.Contains(SearchQuery) == true || setting.Value?.Contains(SearchQuery) == true).ToList(); + + private bool IsValuesHidden { get; set; } = true; + + private string? SearchQuery { get; set; } + + private ICollection SelectedConfigurationSettings { get; set; } = []; + + protected override async Task OnInitializedAsync() + { + await foreach (var setting in ConfigurationSettingRepository.Get()) + { + if (setting is not FeatureFlagConfigurationSetting) + { + ConfigurationSettings.Add(setting); + StateHasChanged(); + } + } + } + + private async Task HandleConfigurationSettingCreate(ConfigurationSetting setting) + { + await ConfigurationSettingRepository.AddAsync(setting); + + ConfigurationSettings.Add(setting); + StateHasChanged(); + } + + private async Task HandleCreateClick(MouseEventArgs args) + { + await DialogService.ShowAsync("ConfigurationSettingCreateDialog"); + } + + private async Task HandleDeleteClick(MouseEventArgs args) + { + foreach (var setting in SelectedConfigurationSettings.ToList()) + { + await ConfigurationSettingRepository.RemoveAsync(setting); + + if (ConfigurationSettings.Remove(setting) && SelectedConfigurationSettings.Remove(setting)) + { + StateHasChanged(); + } + } + } + + private async Task HandleRefreshClick(MouseEventArgs args) + { + ConfigurationSettings.Clear(); + StateHasChanged(); + + await foreach (var setting in ConfigurationSettingRepository.Get()) + { + if (setting is not FeatureFlagConfigurationSetting) + { + ConfigurationSettings.Add(setting); + StateHasChanged(); + } + } + } + + private void HandleValuesClick(MouseEventArgs args) + { + IsValuesHidden = !IsValuesHidden; + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Pages/Error.razor b/src/AzureAppConfigurationEmulator/Components/Pages/Error.razor new file mode 100644 index 0000000..2eff858 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/error" +@using System.Diagnostics + +Error + +

    Error.

    +

    An error occurred while processing your request.

    + +@if (ShowRequestId) +{ +

    + Request ID: @RequestId +

    +} + +

    Development Mode

    +

    + Swapping to Development environment will display more detailed information about the error that occurred. +

    +

    + The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

    + +@code{ + [CascadingParameter] private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Pages/FeatureManager.razor b/src/AzureAppConfigurationEmulator/Components/Pages/FeatureManager.razor new file mode 100644 index 0000000..04243e2 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Pages/FeatureManager.razor @@ -0,0 +1,94 @@ +@attribute [StreamRendering] +@inject IConfigurationSettingRepository ConfigurationSettingRepository +@page "/ff" +@rendermode InteractiveServer +@using AzureAppConfigurationEmulator.Entities +@using AzureAppConfigurationEmulator.Repositories + +Feature manager + +
    + + + + + + + + + + + + + +
    +
    +
    Filter all feature flags:
    + + + + +
    + +
    + +
    + +
    Loaded @FilteredConfigurationSettings.Count() feature flags.
    +
    + +
    + +
    +
    + +@code { + private ICollection ConfigurationSettings { get; } = []; + + private IEnumerable FilteredConfigurationSettings => ConfigurationSettings.Where(setting => string.IsNullOrEmpty(SearchQuery) || setting.Key.Contains(SearchQuery) || setting.Description?.Contains(SearchQuery) == true || setting.Label?.Contains(SearchQuery) == true).ToList(); + + private string? SearchQuery { get; set; } + + private ICollection SelectedConfigurationSettings { get; set; } = []; + + protected override async Task OnInitializedAsync() + { + await foreach (var setting in ConfigurationSettingRepository.Get()) + { + if (setting is FeatureFlagConfigurationSetting flag) + { + ConfigurationSettings.Add(flag); + StateHasChanged(); + } + } + } + + private async Task HandleDeleteClick(MouseEventArgs args) + { + foreach (var setting in SelectedConfigurationSettings.ToList()) + { + await ConfigurationSettingRepository.RemoveAsync(setting); + + if (ConfigurationSettings.Remove(setting) && SelectedConfigurationSettings.Remove(setting)) + { + StateHasChanged(); + } + } + } + + private async Task HandleRefreshClick(MouseEventArgs args) + { + ConfigurationSettings.Clear(); + StateHasChanged(); + + await foreach (var setting in ConfigurationSettingRepository.Get()) + { + if (setting is FeatureFlagConfigurationSetting flag) + { + ConfigurationSettings.Add(flag); + StateHasChanged(); + } + } + } + +} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Pages/Overview.razor b/src/AzureAppConfigurationEmulator/Components/Pages/Overview.razor new file mode 100644 index 0000000..97d1d09 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Pages/Overview.razor @@ -0,0 +1,17 @@ +@page "/" + +Overview + +
    + + + + + + + + +
    + +
    +
    \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/Routes.razor b/src/AzureAppConfigurationEmulator/Components/Routes.razor new file mode 100644 index 0000000..26a56e4 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/Routes.razor @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Components/_Imports.razor b/src/AzureAppConfigurationEmulator/Components/_Imports.razor new file mode 100644 index 0000000..21fb53a --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Components/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using AzureAppConfigurationEmulator +@using AzureAppConfigurationEmulator.Components \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/Constants/MediaType.cs b/src/AzureAppConfigurationEmulator/Constants/MediaType.cs new file mode 100644 index 0000000..12d1a5f --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Constants/MediaType.cs @@ -0,0 +1,19 @@ +namespace AzureAppConfigurationEmulator.Constants; + +/// +/// Defines well known media types that are used within Azure App Configuration. +/// +public static class MediaType +{ + public const string FeatureFlag = "application/vnd.microsoft.appconfig.ff+json"; + + public const string KeySet = "application/vnd.microsoft.appconfig.keyset+json"; + + public const string KeyValue = "application/vnd.microsoft.appconfig.kv+json"; + + public const string KeyValueSet = "application/vnd.microsoft.appconfig.kvset+json"; + + public const string LabelSet = "application/vnd.microsoft.appconfig.labelset+json"; + + public const string SecretReference = "application/vnd.microsoft.appconfig.keyvaultref+json"; +} diff --git a/src/AzureAppConfigurationEmulator/Entities/ConfigurationSetting.cs b/src/AzureAppConfigurationEmulator/Entities/ConfigurationSetting.cs index 6db870d..be81dbe 100644 --- a/src/AzureAppConfigurationEmulator/Entities/ConfigurationSetting.cs +++ b/src/AzureAppConfigurationEmulator/Entities/ConfigurationSetting.cs @@ -1,28 +1,48 @@ namespace AzureAppConfigurationEmulator.Entities; -public class ConfigurationSetting( - string etag, - string key, - string? label, - string? contentType, - string? value, - DateTimeOffset lastModified, - bool locked, - IDictionary? tags) -{ - public string Etag { get; set; } = etag; - - public string Key { get; set; } = key; - - public string? Label { get; set; } = label; - - public string? ContentType { get; set; } = contentType; - - public string? Value { get; set; } = value; - - public DateTimeOffset LastModified { get; set; } = lastModified; - - public bool Locked { get; set; } = locked; - - public IDictionary? Tags { get; set; } = tags; -} +/// +/// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Models/ConfigurationSetting.cs +/// +public record ConfigurationSetting( + string Etag, + string Key, + DateTimeOffset LastModified, + bool Locked, + string? Label = null, + string? ContentType = null, + string? Value = null, + IReadOnlyDictionary? Tags = null); + +/// +/// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Models/FeatureFlagConfigurationSetting.cs +/// +public record FeatureFlagConfigurationSetting( + string Id, + bool Enabled, + IReadOnlyCollection ClientFilters, + string Etag, + string Key, + string Value, + DateTimeOffset LastModified, + bool Locked, + string? Description = null, + string? DisplayName = null, + string? Label = null, + string? ContentType = null, + IReadOnlyDictionary? Tags = null) + : ConfigurationSetting( + Etag, + Key, + LastModified, + Locked, + Label, + ContentType, + Value, + Tags); + +/// +/// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Models/FeatureFlagFilter.cs +/// +public record FeatureFlagFilter( + string Name, + IReadOnlyDictionary? Parameters); diff --git a/src/AzureAppConfigurationEmulator/Factories/ConfigurationSettingFactory.cs b/src/AzureAppConfigurationEmulator/Factories/ConfigurationSettingFactory.cs new file mode 100644 index 0000000..a84cdf2 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Factories/ConfigurationSettingFactory.cs @@ -0,0 +1,95 @@ +using System.Net.Mime; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using AzureAppConfigurationEmulator.Constants; +using AzureAppConfigurationEmulator.Entities; + +namespace AzureAppConfigurationEmulator.Factories; + +public class ConfigurationSettingFactory : IConfigurationSettingFactory +{ + public ConfigurationSetting Create( + string key, + string? label = null, + string? contentType = null, + string? value = null, + IReadOnlyDictionary? tags = null) + { + var date = DateTimeOffset.UtcNow; + + return Create( + Convert.ToBase64String( + SHA256.HashData( + Encoding.UTF8.GetBytes( + date.UtcDateTime.ToString( + "yyyy-MM-dd HH:mm:ss")))), + key, + date, + false, + label, + contentType, + value, + tags); + } + + public ConfigurationSetting Create( + string etag, + string key, + DateTimeOffset lastModified, + bool locked, + string? label = null, + string? contentType = null, + string? value = null, + IReadOnlyDictionary? tags = null) + { + if (!string.IsNullOrEmpty(contentType) && !string.IsNullOrEmpty(value)) + { + switch (new ContentType(contentType).MediaType) + { + case MediaType.FeatureFlag: + { + using var document = JsonDocument.Parse(value); + + return new FeatureFlagConfigurationSetting( + document.RootElement.GetProperty("id").GetString()!, + document.RootElement.GetProperty("enabled").GetBoolean(), + document.RootElement.TryGetProperty("conditions", out var conditions) && conditions.TryGetProperty("client_filters", out var clientFilters) + ? clientFilters.EnumerateArray() + .Select(clientFilter => + new FeatureFlagFilter( + clientFilter.GetProperty("name").GetString()!, + clientFilter.TryGetProperty("properties", out var properties) + ? properties.Deserialize>() + : null)) + .ToList() + : [], + etag, + key, + value, + lastModified, + locked, + document.RootElement.TryGetProperty("description", out var description) + ? description.GetString() + : null, + document.RootElement.TryGetProperty("display_name", out var displayName) + ? displayName.GetString() + : null, + label, + contentType, + tags); + } + } + } + + return new ConfigurationSetting( + etag, + key, + lastModified, + locked, + label, + contentType, + value, + tags); + } +} diff --git a/src/AzureAppConfigurationEmulator/Factories/IConfigurationSettingFactory.cs b/src/AzureAppConfigurationEmulator/Factories/IConfigurationSettingFactory.cs new file mode 100644 index 0000000..876b6fe --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Factories/IConfigurationSettingFactory.cs @@ -0,0 +1,23 @@ +using AzureAppConfigurationEmulator.Entities; + +namespace AzureAppConfigurationEmulator.Factories; + +public interface IConfigurationSettingFactory +{ + public ConfigurationSetting Create( + string key, + string? label = null, + string? contentType = null, + string? value = null, + IReadOnlyDictionary? tags = null); + + public ConfigurationSetting Create( + string etag, + string key, + DateTimeOffset lastModified, + bool locked, + string? label = null, + string? contentType = null, + string? value = null, + IReadOnlyDictionary? tags = null); +} diff --git a/src/AzureAppConfigurationEmulator/Handlers/KeyValueHandler.cs b/src/AzureAppConfigurationEmulator/Handlers/KeyValueHandler.cs index e8e1b13..1d602b4 100644 --- a/src/AzureAppConfigurationEmulator/Handlers/KeyValueHandler.cs +++ b/src/AzureAppConfigurationEmulator/Handlers/KeyValueHandler.cs @@ -164,11 +164,11 @@ public static async Task? Tags); + public record SetInput(string? Value, string? ContentType, IReadOnlyDictionary? Tags); } diff --git a/src/AzureAppConfigurationEmulator/Handlers/LockHandler.cs b/src/AzureAppConfigurationEmulator/Handlers/LockHandler.cs index 911f0d9..c8b52d2 100644 --- a/src/AzureAppConfigurationEmulator/Handlers/LockHandler.cs +++ b/src/AzureAppConfigurationEmulator/Handlers/LockHandler.cs @@ -36,7 +36,7 @@ public static async Task(); + +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -25,6 +32,19 @@ var app = builder.Build(); +app.Map("/_explorer", app => +{ + app.UseRouting(); + app.UseStaticFiles(); + app.UseAntiforgery(); + + app.UseEndpoints(endpoints => + { + endpoints.MapRazorComponents().AddInteractiveServerRenderMode(); + }); +}); + +app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/src/AzureAppConfigurationEmulator/Properties/launchSettings.json b/src/AzureAppConfigurationEmulator/Properties/launchSettings.json index 98878fd..62e0b23 100644 --- a/src/AzureAppConfigurationEmulator/Properties/launchSettings.json +++ b/src/AzureAppConfigurationEmulator/Properties/launchSettings.json @@ -4,6 +4,8 @@ "AzureAppConfigurationEmulator": { "commandName": "Project", "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "_explorer", "applicationUrl": "https://localhost:7000;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/AzureAppConfigurationEmulator/Repositories/ConfigurationSettingRepository.cs b/src/AzureAppConfigurationEmulator/Repositories/ConfigurationSettingRepository.cs index 81037d9..7dd292e 100644 --- a/src/AzureAppConfigurationEmulator/Repositories/ConfigurationSettingRepository.cs +++ b/src/AzureAppConfigurationEmulator/Repositories/ConfigurationSettingRepository.cs @@ -12,12 +12,15 @@ namespace AzureAppConfigurationEmulator.Repositories; public partial class ConfigurationSettingRepository( IDbCommandFactory commandFactory, + IConfigurationSettingFactory configurationSettingFactory, IDbConnectionFactory connectionFactory, ILogger logger, IDbParameterFactory parameterFactory) : IConfigurationSettingRepository { private IDbCommandFactory CommandFactory { get; } = commandFactory; + private IConfigurationSettingFactory ConfigurationSettingFactory { get; } = configurationSettingFactory; + private IDbConnectionFactory ConnectionFactory { get; } = connectionFactory; private ILogger Logger { get; } = logger; @@ -114,15 +117,15 @@ public async IAsyncEnumerable Get( await foreach (var reader in ExecuteReader(text, parameters, cancellationToken)) { - yield return new ConfigurationSetting( + yield return ConfigurationSettingFactory.Create( reader.GetString(0), reader.GetString(1), + DateTimeOffset.Parse(reader.GetString(5), styles: DateTimeStyles.AssumeUniversal), + reader.GetBoolean(6), reader.IsDBNull(2) ? null : reader.GetString(2), reader.IsDBNull(3) ? null : reader.GetString(3), reader.IsDBNull(4) ? null : reader.GetString(4), - DateTimeOffset.Parse(reader.GetString(5), styles: DateTimeStyles.AssumeUniversal), - reader.GetBoolean(6), - reader.IsDBNull(7) ? null : JsonSerializer.Deserialize>(reader.GetString(7))); + reader.IsDBNull(7) ? null : JsonSerializer.Deserialize>(reader.GetString(7))); } } diff --git a/src/AzureAppConfigurationEmulator/Results/KeySetResult.cs b/src/AzureAppConfigurationEmulator/Results/KeySetResult.cs index 9aa2581..05e0e77 100644 --- a/src/AzureAppConfigurationEmulator/Results/KeySetResult.cs +++ b/src/AzureAppConfigurationEmulator/Results/KeySetResult.cs @@ -1,3 +1,5 @@ +using AzureAppConfigurationEmulator.Constants; + namespace AzureAppConfigurationEmulator.Results; public class KeySetResult(IEnumerable keys, DateTimeOffset? mementoDatetime = default) : @@ -22,7 +24,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await httpContext.Response.WriteAsJsonAsync(Value, options: default, ContentType); } - public string? ContentType => "application/vnd.microsoft.appconfig.keyset+json"; + public string? ContentType => MediaType.KeySet; public int? StatusCode => StatusCodes.Status200OK; diff --git a/src/AzureAppConfigurationEmulator/Results/KeyValueResult.cs b/src/AzureAppConfigurationEmulator/Results/KeyValueResult.cs index f70c575..c346e2d 100644 --- a/src/AzureAppConfigurationEmulator/Results/KeyValueResult.cs +++ b/src/AzureAppConfigurationEmulator/Results/KeyValueResult.cs @@ -1,3 +1,4 @@ +using AzureAppConfigurationEmulator.Constants; using AzureAppConfigurationEmulator.Entities; namespace AzureAppConfigurationEmulator.Results; @@ -27,7 +28,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await httpContext.Response.WriteAsJsonAsync(Value, options: default, ContentType); } - public string? ContentType => "application/vnd.microsoft.appconfig.kv+json"; + public string? ContentType => MediaType.KeyValue; public int? StatusCode => StatusCodes.Status200OK; diff --git a/src/AzureAppConfigurationEmulator/Results/KeyValueSetResult.cs b/src/AzureAppConfigurationEmulator/Results/KeyValueSetResult.cs index 9abab81..17d1ff7 100644 --- a/src/AzureAppConfigurationEmulator/Results/KeyValueSetResult.cs +++ b/src/AzureAppConfigurationEmulator/Results/KeyValueSetResult.cs @@ -1,3 +1,4 @@ +using AzureAppConfigurationEmulator.Constants; using AzureAppConfigurationEmulator.Entities; namespace AzureAppConfigurationEmulator.Results; @@ -24,7 +25,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await httpContext.Response.WriteAsJsonAsync(Value, options: default, ContentType); } - public string? ContentType => "application/vnd.microsoft.appconfig.kvset+json"; + public string? ContentType => MediaType.KeyValueSet; public int? StatusCode => StatusCodes.Status200OK; diff --git a/src/AzureAppConfigurationEmulator/Results/LabelSetResult.cs b/src/AzureAppConfigurationEmulator/Results/LabelSetResult.cs index 093ef34..a480a78 100644 --- a/src/AzureAppConfigurationEmulator/Results/LabelSetResult.cs +++ b/src/AzureAppConfigurationEmulator/Results/LabelSetResult.cs @@ -1,3 +1,5 @@ +using AzureAppConfigurationEmulator.Constants; + namespace AzureAppConfigurationEmulator.Results; public class LabelSetResult(IEnumerable labels, DateTimeOffset? mementoDatetime = default) : @@ -22,7 +24,7 @@ public async Task ExecuteAsync(HttpContext httpContext) await httpContext.Response.WriteAsJsonAsync(Value, options: default, ContentType); } - public string? ContentType => "application/vnd.microsoft.appconfig.labelset+json"; + public string? ContentType => MediaType.LabelSet; public int? StatusCode => StatusCodes.Status200OK; diff --git a/src/AzureAppConfigurationEmulator/Services/DialogService.cs b/src/AzureAppConfigurationEmulator/Services/DialogService.cs new file mode 100644 index 0000000..e4667f0 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Services/DialogService.cs @@ -0,0 +1,41 @@ +using Microsoft.JSInterop; + +namespace AzureAppConfigurationEmulator.Services; + +public class DialogService(IJSRuntime js) : IAsyncDisposable, IDialogService +{ + private Lazy> Module { get; } = new(() => js.InvokeAsync("import", "./scripts/dialog.js")); + + public async ValueTask DisposeAsync() + { + if (Module.IsValueCreated) + { + var module = await Module.Value; + + await module.DisposeAsync(); + } + + GC.SuppressFinalize(this); + } + + public async Task CloseAsync(string id) + { + var module = await Module.Value; + + await module.InvokeVoidAsync("close", id); + } + + public async Task CloseAsync(string id, TResult result) + { + var module = await Module.Value; + + await module.InvokeVoidAsync("close", id, result); + } + + public async Task ShowAsync(string id) + { + var module = await Module.Value; + + await module.InvokeVoidAsync("show", id); + } +} diff --git a/src/AzureAppConfigurationEmulator/Services/IDialogService.cs b/src/AzureAppConfigurationEmulator/Services/IDialogService.cs new file mode 100644 index 0000000..bde5bcc --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Services/IDialogService.cs @@ -0,0 +1,9 @@ +namespace AzureAppConfigurationEmulator.Services; + +public interface IDialogService +{ + public Task CloseAsync(string id); + public Task CloseAsync(string id, TResult result); + + public Task ShowAsync(string id); +} diff --git a/src/AzureAppConfigurationEmulator/Styles/app.css b/src/AzureAppConfigurationEmulator/Styles/app.css new file mode 100644 index 0000000..115a838 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/Styles/app.css @@ -0,0 +1,62 @@ +/*@import "tailwindcss";*/ + +/*@theme { + --color-*: initial; + --color-alizarin-crimson: "#e81123"; + --color-alto: "#e1dfdd"; + --color-black: "#000000"; + --color-cod-grey: "#1b1a19"; + --color-concrete: "#f3f2f1"; + --color-desert-storm: "#faf9f8"; + --color-dodger-blue: "#2899f5"; + --color-gallery: "#edebe9"; + --color-iron: "#cccccc"; + --color-jordy-blue: "#6cb8f6"; + --color-lochmara: "#0078d4"; + --color-masala: "#414141"; + --color-merlin: "#484644"; + --color-mine-shaft: "#292827"; + --color-natural-grey: "#8a8886"; + --color-science-blue: "#106ebe"; + --color-shark: "#252423"; + --color-solitude: "#e6f2fb"; + --color-star-dust: "#a19f9d"; + --color-storm-dust: "#646464"; + --color-transparent: "transparent"; + --color-tropical-blue: "#cce4f6"; + --color-tuatara: "#3b3a39"; + --color-venice-blue: "#005a93"; + --color-white: "#ffffff"; + + --font-family-*: initial; + --font-family-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; + --font-family-mono: Consolas, "Courier New", Courier, monospace; + + --font-size-*: initial; + --font-size-2xs: 0.625rem; + --font-size-2xs--line-height: normal; + --font-size-xs: 0.6875rem; + --font-size-xs--line-height: normal; + --font-size-sm: 0.75rem; + --font-size-sm--line-height: normal; + --font-size-base: 0.8125rem; + --font-size-base--line-height: normal; + --font-size-lg: 0.875rem; + --font-size-lg--line-height: normal; + --font-size-xl: 0.9375rem; + --font-size-xl--line-height: normal; + --font-size-2xl: 1rem; + --font-size-2xl--line-height: normal; + --font-size-3xl: 1.0625rem; + --font-size-3xl--line-height: normal; + --font-size-4xl: 1.125rem; + --font-size-4xl--line-height: normal; + --font-size-5xl: 1.1875rem; + --font-size-5xl--line-height: normal; + --font-size-6xl: 1.25rem; + --font-size-6xl--line-height: normal; +}*/ + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/AzureAppConfigurationEmulator/package-lock.json b/src/AzureAppConfigurationEmulator/package-lock.json new file mode 100644 index 0000000..7ee7cdb --- /dev/null +++ b/src/AzureAppConfigurationEmulator/package-lock.json @@ -0,0 +1,1445 @@ +{ + "name": "azure-app-configuration-emulator", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "azure-app-configuration-emulator", + "version": "0.0.0", + "devDependencies": { + "rustywind": "^0.21.0", + "tailwindcss": "^3.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rustywind": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/rustywind/-/rustywind-0.21.0.tgz", + "integrity": "sha512-RQU079TTleK4Pf24+/PtBnwzat5bN+fod+D8JycaT7WqDIEyU0aBNmoicrCaT1KOLX8mvSu5Qb9cjhn0fFebcg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "https-proxy-agent": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "bin": { + "rustywind": "bin/rustywind" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/src/AzureAppConfigurationEmulator/package.json b/src/AzureAppConfigurationEmulator/package.json new file mode 100644 index 0000000..2a5fdb6 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/package.json @@ -0,0 +1,13 @@ +{ + "name": "azure-app-configuration-emulator", + "version": "0.0.0", + "private": true, + "scripts": { + "build:css": "tailwindcss --input ./Styles/app.css --minify --output ./wwwroot/app.css", + "format:css": "rustywind --write ." + }, + "devDependencies": { + "rustywind": "^0.21.0", + "tailwindcss": "^3.0.0" + } +} diff --git a/src/AzureAppConfigurationEmulator/tailwind.config.js b/src/AzureAppConfigurationEmulator/tailwind.config.js new file mode 100644 index 0000000..1a0ac67 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/tailwind.config.js @@ -0,0 +1,61 @@ +module.exports = { + content: ["./**/*.razor"], + theme: { + colors: { + "alizarin-crimson": "#e81123", + "alto": "#e1dfdd", + "black": "#000000", + "cod-grey": "#1b1a19", + "concrete": "#f3f2f1", + "desert-storm": "#faf9f8", + "dodger-blue": "#2899f5", + "gallery": "#edebe9", + "iron": "#cccccc", + "jordy-blue": "#6cb8f6", + "lochmara": "#0078d4", + "masala": "#414141", + "merlin": "#484644", + "mine-shaft": "#292827", + "natural-grey": "#8a8886", + "science-blue": "#106ebe", + "shark": "#252423", + "solitude": "#e6f2fb", + "star-dust": "#a19f9d", + "storm-dust": "#646464", + "transparent": "transparent", + "tropical-blue": "#cce4f6", + "tuatara": "#3b3a39", + "venice-blue": "#005a93", + "white": "#ffffff", + }, + fontFamily: { + mono: [ + "Consolas", + '"Courier New"', + "Courier", + "monospace", + ], + sans: [ + '"Segoe UI"', + "-apple-system", + "BlinkMacSystemFont", + "Roboto", + '"Helvetica Neue"', + "sans-serif", + ], + }, + fontSize: { + "2xs": ["0.625rem", "normal"], + "xs": ["0.6875rem", "normal"], + "sm": ["0.75rem", "normal"], + "base": ["0.8125rem", "normal"], + "lg": ["0.875rem", "normal"], + "xl": ["0.9375rem", "normal"], + "2xl": ["1rem", "normal"], + "3xl": ["1.0625rem", "normal"], + "4xl": ["1.125rem", "normal"], + "5xl": ["1.1875rem", "normal"], + "6xl": ["1.25rem", "normal"], + }, + }, +}; diff --git a/src/AzureAppConfigurationEmulator/wwwroot/app.css b/src/AzureAppConfigurationEmulator/wwwroot/app.css new file mode 100644 index 0000000..95e63de --- /dev/null +++ b/src/AzureAppConfigurationEmulator/wwwroot/app.css @@ -0,0 +1 @@ +/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Helvetica Neue,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:Consolas,Courier New,Courier,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.bottom-0{bottom:0}.left-0{left:0}.right-3{right:.75rem}.top-2{top:.5rem}.z-\[1000\]{z-index:1000}.m-1{margin:.25rem}.m-1\.5{margin:.375rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-1\.5{margin-left:.375rem;margin-right:.375rem}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.my-0{margin-top:0;margin-bottom:0}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-6{margin-bottom:1.5rem}.ml-5{margin-left:1.25rem}.mr-0{margin-right:0}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-2\.5{margin-right:.625rem}.mr-4{margin-right:1rem}.mr-9{margin-right:2.25rem}.mt-1{margin-top:.25rem}.flex{display:flex}.table{display:table}.hidden{display:none}.h-\[12px\]{height:12px}.h-\[15px\]{height:15px}.h-\[16px\]{height:16px}.h-\[18px\]{height:18px}.h-\[20px\]{height:20px}.h-\[24px\]{height:24px}.h-\[28px\]{height:28px}.h-\[32px\]{height:32px}.h-\[36px\]{height:36px}.h-\[40px\]{height:40px}.h-\[48px\]{height:48px}.h-full{height:100%}.max-h-full{max-height:100%}.w-\[12px\]{width:12px}.w-\[15px\]{width:15px}.w-\[16px\]{width:16px}.w-\[18px\]{width:18px}.w-\[24px\]{width:24px}.w-\[265px\]{width:265px}.w-\[28px\]{width:28px}.w-\[32px\]{width:32px}.w-\[48px\]{width:48px}.w-\[585px\]{width:585px}.w-full{width:100%}.w-px{width:1px}.max-w-\[300px\]{max-width:300px}.max-w-\[728px\]{max-width:728px}.max-w-full{max-width:100%}.flex-1{flex:1 1 0%}.flex-none{flex:none}.table-fixed{table-layout:fixed}.border-collapse{border-collapse:collapse}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-4{gap:1rem}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-sm{border-radius:.125rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-lochmara{--tw-border-opacity:1;border-color:rgb(0 120 212/var(--tw-border-opacity))}.border-mine-shaft{--tw-border-opacity:1;border-color:rgb(41 40 39/var(--tw-border-opacity))}.border-storm-dust{--tw-border-opacity:1;border-color:rgb(100 100 100/var(--tw-border-opacity))}.border-b-alto{--tw-border-opacity:1;border-bottom-color:rgb(225 223 221/var(--tw-border-opacity))}.border-b-lochmara{--tw-border-opacity:1;border-bottom-color:rgb(0 120 212/var(--tw-border-opacity))}.border-t-alto{--tw-border-opacity:1;border-top-color:rgb(225 223 221/var(--tw-border-opacity))}.bg-alto{--tw-bg-opacity:1;background-color:rgb(225 223 221/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gallery{--tw-bg-opacity:1;background-color:rgb(237 235 233/var(--tw-bg-opacity))}.bg-lochmara{--tw-bg-opacity:1;background-color:rgb(0 120 212/var(--tw-bg-opacity))}.bg-merlin{--tw-bg-opacity:1;background-color:rgb(72 70 68/var(--tw-bg-opacity))}.bg-solitude{--tw-bg-opacity:1;background-color:rgb(230 242 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.p-5{padding:1.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.pb-1{padding-bottom:.25rem}.pb-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pb-5{padding-bottom:1.25rem}.pb-6{padding-bottom:1.5rem}.pl-2{padding-left:.5rem}.pl-2\.5{padding-left:.625rem}.pr-6{padding-right:1.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-2\.5{padding-top:.625rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1rem;line-height:normal}.text-\[24px\]{font-size:24px}.text-base{font-size:.8125rem;line-height:normal}.text-lg{font-size:.875rem;line-height:normal}.text-sm{font-size:.75rem;line-height:normal}.text-xl{font-size:.9375rem;line-height:normal}.font-bold{font-weight:700}.font-semibold{font-weight:600}.leading-5{line-height:1.25rem}.leading-8{line-height:2rem}.text-alizarin-crimson{--tw-text-opacity:1;color:rgb(232 17 35/var(--tw-text-opacity))}.text-mine-shaft{--tw-text-opacity:1;color:rgb(41 40 39/var(--tw-text-opacity))}.text-storm-dust{--tw-text-opacity:1;color:rgb(100 100 100/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.placeholder\:text-storm-dust::-moz-placeholder{--tw-text-opacity:1;color:rgb(100 100 100/var(--tw-text-opacity))}.placeholder\:text-storm-dust::placeholder{--tw-text-opacity:1;color:rgb(100 100 100/var(--tw-text-opacity))}.checked\:border-lochmara:checked{--tw-border-opacity:1;border-color:rgb(0 120 212/var(--tw-border-opacity))}.checked\:bg-lochmara:checked{--tw-bg-opacity:1;background-color:rgb(0 120 212/var(--tw-bg-opacity))}.checked\:bg-\[url\(\'data\:image\/svg\+xml\;base64\2c PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI\+PHBhdGggZD0iTTEzLjg2IDMuNjZhLjUuNSAwIDAxLS4wMi43bC03LjkzIDcuNDhhLjYuNiAwIDAxLS44NC0uMDJMMi40IDkuMWEuNS41IDAgMDEuNzItLjdsMi40IDIuNDQgNy42NS03LjJhLjUuNSAwIDAxLjcuMDJ6IiBmaWxsPSIjZmZmZmZmIj48L3BhdGg\+PC9zdmc\+\'\)\]:checked{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTEzLjg2IDMuNjZhLjUuNSAwIDAgMS0uMDIuN2wtNy45MyA3LjQ4YS42LjYgMCAwIDEtLjg0LS4wMkwyLjQgOS4xYS41LjUgMCAwIDEgLjcyLS43bDIuNCAyLjQ0IDcuNjUtNy4yYS41LjUgMCAwIDEgLjcuMDJ6Ii8+PC9zdmc+")}.checked\:bg-center:checked{background-position:50%}.checked\:bg-no-repeat:checked{background-repeat:no-repeat}.invalid\:border-alizarin-crimson:invalid{--tw-border-opacity:1;border-color:rgb(232 17 35/var(--tw-border-opacity))}.hover\:border-science-blue:hover{--tw-border-opacity:1;border-color:rgb(16 110 190/var(--tw-border-opacity))}.hover\:bg-alizarin-crimson:hover{--tw-bg-opacity:1;background-color:rgb(232 17 35/var(--tw-bg-opacity))}.hover\:bg-alto:hover{--tw-bg-opacity:1;background-color:rgb(225 223 221/var(--tw-bg-opacity))}.hover\:bg-concrete:hover{--tw-bg-opacity:1;background-color:rgb(243 242 241/var(--tw-bg-opacity))}.hover\:bg-science-blue:hover{--tw-bg-opacity:1;background-color:rgb(16 110 190/var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.active\:border-venice-blue:active{--tw-border-opacity:1;border-color:rgb(0 90 147/var(--tw-border-opacity))}.active\:bg-venice-blue:active{--tw-bg-opacity:1;background-color:rgb(0 90 147/var(--tw-bg-opacity))}.enabled\:hover\:bg-concrete:hover:enabled{--tw-bg-opacity:1;background-color:rgb(243 242 241/var(--tw-bg-opacity))}.enabled\:hover\:bg-tropical-blue:hover:enabled{--tw-bg-opacity:1;background-color:rgb(204 228 246/var(--tw-bg-opacity))}.disabled\:border-star-dust:disabled{--tw-border-opacity:1;border-color:rgb(161 159 157/var(--tw-border-opacity))}.disabled\:bg-concrete:disabled{--tw-bg-opacity:1;background-color:rgb(243 242 241/var(--tw-bg-opacity))}.disabled\:text-star-dust:disabled{--tw-text-opacity:1;color:rgb(161 159 157/var(--tw-text-opacity))}@media (prefers-color-scheme:dark){.dark\:border-desert-storm{--tw-border-opacity:1;border-color:rgb(250 249 248/var(--tw-border-opacity))}.dark\:border-lochmara{--tw-border-opacity:1;border-color:rgb(0 120 212/var(--tw-border-opacity))}.dark\:border-star-dust{--tw-border-opacity:1;border-color:rgb(161 159 157/var(--tw-border-opacity))}.dark\:border-b-masala{--tw-border-opacity:1;border-bottom-color:rgb(65 65 65/var(--tw-border-opacity))}.dark\:border-t-masala{--tw-border-opacity:1;border-top-color:rgb(65 65 65/var(--tw-border-opacity))}.dark\:bg-cod-grey{--tw-bg-opacity:1;background-color:rgb(27 26 25/var(--tw-bg-opacity))}.dark\:bg-lochmara{--tw-bg-opacity:1;background-color:rgb(0 120 212/var(--tw-bg-opacity))}.dark\:bg-masala{--tw-bg-opacity:1;background-color:rgb(65 65 65/var(--tw-bg-opacity))}.dark\:bg-mine-shaft{--tw-bg-opacity:1;background-color:rgb(41 40 39/var(--tw-bg-opacity))}.dark\:bg-shark{--tw-bg-opacity:1;background-color:rgb(37 36 35/var(--tw-bg-opacity))}.dark\:bg-tuatara{--tw-bg-opacity:1;background-color:rgb(59 58 57/var(--tw-bg-opacity))}.dark\:bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.dark\:text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.dark\:text-desert-storm{--tw-text-opacity:1;color:rgb(250 249 248/var(--tw-text-opacity))}.dark\:text-mine-shaft{--tw-text-opacity:1;color:rgb(41 40 39/var(--tw-text-opacity))}.dark\:text-star-dust{--tw-text-opacity:1;color:rgb(161 159 157/var(--tw-text-opacity))}.dark\:placeholder\:text-star-dust::-moz-placeholder{--tw-text-opacity:1;color:rgb(161 159 157/var(--tw-text-opacity))}.dark\:placeholder\:text-star-dust::placeholder{--tw-text-opacity:1;color:rgb(161 159 157/var(--tw-text-opacity))}.dark\:checked\:bg-\[url\(\'data\:image\/svg\+xml\;base64\2c PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI\+PHBhdGggZD0iTTEzLjg2IDMuNjZhLjUuNSAwIDAxLS4wMi43bC03LjkzIDcuNDhhLjYuNiAwIDAxLS44NC0uMDJMMi40IDkuMWEuNS41IDAgMDEuNzItLjdsMi40IDIuNDQgNy42NS03LjJhLjUuNSAwIDAxLjcuMDJ6IiBmaWxsPSIjMWIxYTE5Ij48L3BhdGg\+PC9zdmc\+\'\)\]:checked{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iIzFiMWExOSIgZD0iTTEzLjg2IDMuNjZhLjUuNSAwIDAgMS0uMDIuN2wtNy45MyA3LjQ4YS42LjYgMCAwIDEtLjg0LS4wMkwyLjQgOS4xYS41LjUgMCAwIDEgLjcyLS43bDIuNCAyLjQ0IDcuNjUtNy4yYS41LjUgMCAwIDEgLjcuMDJ6Ii8+PC9zdmc+")}.dark\:hover\:border-dodger-blue:hover{--tw-border-opacity:1;border-color:rgb(40 153 245/var(--tw-border-opacity))}.dark\:hover\:bg-dodger-blue:hover{--tw-bg-opacity:1;background-color:rgb(40 153 245/var(--tw-bg-opacity))}.dark\:hover\:bg-mine-shaft:hover{--tw-bg-opacity:1;background-color:rgb(41 40 39/var(--tw-bg-opacity))}.dark\:hover\:bg-shark:hover{--tw-bg-opacity:1;background-color:rgb(37 36 35/var(--tw-bg-opacity))}.dark\:active\:border-jordy-blue:active{--tw-border-opacity:1;border-color:rgb(108 184 246/var(--tw-border-opacity))}.dark\:active\:bg-jordy-blue:active{--tw-bg-opacity:1;background-color:rgb(108 184 246/var(--tw-bg-opacity))}.enabled\:dark\:hover\:bg-shark:hover:enabled{--tw-bg-opacity:1;background-color:rgb(37 36 35/var(--tw-bg-opacity))}.enabled\:dark\:hover\:bg-tuatara:hover:enabled{--tw-bg-opacity:1;background-color:rgb(59 58 57/var(--tw-bg-opacity))}.dark\:disabled\:border-natural-grey:disabled{--tw-border-opacity:1;border-color:rgb(138 136 134/var(--tw-border-opacity))}.dark\:disabled\:bg-shark:disabled{--tw-bg-opacity:1;background-color:rgb(37 36 35/var(--tw-bg-opacity))}.dark\:disabled\:text-natural-grey:disabled{--tw-text-opacity:1;color:rgb(138 136 134/var(--tw-text-opacity))}.disabled\:dark\:text-storm-dust:disabled{--tw-text-opacity:1;color:rgb(100 100 100/var(--tw-text-opacity))}} \ No newline at end of file diff --git a/src/AzureAppConfigurationEmulator/wwwroot/favicon.png b/src/AzureAppConfigurationEmulator/wwwroot/favicon.png new file mode 100644 index 0000000..8422b59 Binary files /dev/null and b/src/AzureAppConfigurationEmulator/wwwroot/favicon.png differ diff --git a/src/AzureAppConfigurationEmulator/wwwroot/scripts/dialog.js b/src/AzureAppConfigurationEmulator/wwwroot/scripts/dialog.js new file mode 100644 index 0000000..cdfb2e5 --- /dev/null +++ b/src/AzureAppConfigurationEmulator/wwwroot/scripts/dialog.js @@ -0,0 +1,7 @@ +export function close(id, result) { + document.getElementById(id)?.close(result); +} + +export function show(id) { + document.getElementById(id)?.showModal(); +} diff --git a/tests/AzureAppConfigurationEmulator.Tests/Handlers/KeyValueHandlerTests.cs b/tests/AzureAppConfigurationEmulator.Tests/Handlers/KeyValueHandlerTests.cs index fecd66d..567715f 100644 --- a/tests/AzureAppConfigurationEmulator.Tests/Handlers/KeyValueHandlerTests.cs +++ b/tests/AzureAppConfigurationEmulator.Tests/Handlers/KeyValueHandlerTests.cs @@ -24,7 +24,7 @@ public async Task Delete_KeyValueResult_ExistingConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -42,7 +42,7 @@ public async Task Delete_KeyValueResult_ExistingConfigurationSettingMatchingIfMa // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -59,7 +59,7 @@ public async Task Delete_KeyValueResult_ExistingConfigurationSettingNonMatchingI // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -119,7 +119,7 @@ public async Task Delete_PreconditionFailedResult_ExistingConfigurationSettingMa // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -136,7 +136,7 @@ public async Task Delete_PreconditionFailedResult_ExistingConfigurationSettingNo // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -181,7 +181,7 @@ public async Task Delete_ReadOnlyResult_LockedConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, true, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, true) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -220,7 +220,7 @@ public async Task Get_KeyValueResult_ExistingConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -238,7 +238,7 @@ public async Task Get_KeyValueResult_ExistingConfigurationSettingMatchingIfMatch // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -255,7 +255,7 @@ public async Task Get_KeyValueResult_ExistingConfigurationSettingNonMatchingIfNo // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -315,7 +315,7 @@ public async Task Get_NotModifiedResult_ExistingConfigurationSettingMatchingIfNo // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -332,7 +332,7 @@ public async Task Get_PreconditionFailedResult_ExistingConfigurationSettingNonMa // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -371,7 +371,7 @@ public async Task Set_KeyValueResult_ExistingConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); @@ -390,7 +390,7 @@ public async Task Set_KeyValueResult_ExistingConfigurationSettingMatchingIfMatch // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); @@ -408,7 +408,7 @@ public async Task Set_KeyValueResult_ExistingConfigurationSettingNonMatchingIfNo // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); @@ -472,7 +472,7 @@ public async Task Set_PreconditionFailedResult_ExistingConfigurationSettingMatch // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); @@ -490,7 +490,7 @@ public async Task Set_PreconditionFailedResult_ExistingConfigurationSettingNonMa // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); @@ -538,7 +538,7 @@ public async Task Set_ReadOnlyResult_LockedConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, true, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, true) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); var input = new KeyValueHandler.SetInput(null, null, null); diff --git a/tests/AzureAppConfigurationEmulator.Tests/Handlers/LockHandlerTests.cs b/tests/AzureAppConfigurationEmulator.Tests/Handlers/LockHandlerTests.cs index 3200f7a..4a457f2 100644 --- a/tests/AzureAppConfigurationEmulator.Tests/Handlers/LockHandlerTests.cs +++ b/tests/AzureAppConfigurationEmulator.Tests/Handlers/LockHandlerTests.cs @@ -24,7 +24,7 @@ public async Task Lock_KeyValueResult_ExistingConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -42,7 +42,7 @@ public async Task Lock_KeyValueResult_MatchingIfMatch(string ifMatch) // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -59,7 +59,7 @@ public async Task Lock_KeyValueResult_NonMatchingIfNoneMatch() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -91,7 +91,7 @@ public async Task Lock_PreconditionFailedResult_MatchingIfNoneMatch(string ifNon // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -108,7 +108,7 @@ public async Task Lock_PreconditionFailedResult_NonMatchingIfMatch() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -125,7 +125,7 @@ public async Task Unlock_KeyValueResult_ExistingConfigurationSetting() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -143,7 +143,7 @@ public async Task Unlock_KeyValueResult_MatchingIfMatch(string ifMatch) // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -160,7 +160,7 @@ public async Task Unlock_KeyValueResult_NonMatchingIfNoneMatch() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -192,7 +192,7 @@ public async Task Unlock_PreconditionFailedResult_MatchingIfNoneMatch(string ifN // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); @@ -209,7 +209,7 @@ public async Task Unlock_PreconditionFailedResult_NonMatchingIfMatch() // Arrange var settings = new List { - new("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null) + new("TestEtag", "TestKey", DateTimeOffset.UtcNow, false) }; Repository.Get(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(settings.ToAsyncEnumerable()); diff --git a/tests/AzureAppConfigurationEmulator.Tests/Repositories/ConfigurationSettingRepositoryTests.cs b/tests/AzureAppConfigurationEmulator.Tests/Repositories/ConfigurationSettingRepositoryTests.cs index 7b80c72..7adcfdb 100644 --- a/tests/AzureAppConfigurationEmulator.Tests/Repositories/ConfigurationSettingRepositoryTests.cs +++ b/tests/AzureAppConfigurationEmulator.Tests/Repositories/ConfigurationSettingRepositoryTests.cs @@ -13,6 +13,8 @@ public class ConfigurationSettingRepositoryTests { private IDbCommandFactory CommandFactory { get; set; } + private IConfigurationSettingFactory ConfigurationSettingFactory { get; set; } + private IDbConnectionFactory ConnectionFactory { get; set; } private ILogger Logger { get; set; } @@ -40,6 +42,8 @@ public void SetUp() return command; }); + ConfigurationSettingFactory = Substitute.For(); + ConnectionFactory = Substitute.For(); ConnectionFactory .Create() @@ -53,14 +57,14 @@ public void SetUp() ParameterFactory = Substitute.For(); - Repository = new ConfigurationSettingRepository(CommandFactory, ConnectionFactory, Logger, ParameterFactory); + Repository = new ConfigurationSettingRepository(CommandFactory, ConfigurationSettingFactory, ConnectionFactory, Logger, ParameterFactory); } [Test] public async Task AddAsync_CommandText_ConfigurationSetting() { // Arrange - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.AddAsync(setting); @@ -79,7 +83,7 @@ public async Task AddAsync_ContentTypeParameter_ConfigurationSetting() { // Arrange const string contentType = "TestContentType"; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, contentType, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, contentType, null, null); // Act await Repository.AddAsync(setting); @@ -97,7 +101,7 @@ public async Task AddAsync_EtagParameter_ConfigurationSetting() { // Arrange const string etag = "TestEtag"; - var setting = new ConfigurationSetting(etag, "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting(etag, "TestKey", DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.AddAsync(setting); @@ -115,7 +119,7 @@ public async Task AddAsync_KeyParameter_ConfigurationSetting() { // Arrange const string key = "TestKey"; - var setting = new ConfigurationSetting("TestEtag", key, null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", key, DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.AddAsync(setting); @@ -133,7 +137,7 @@ public async Task AddAsync_LabelParameter_ConfigurationSetting() { // Arrange const string label = "TestLabel"; - var setting = new ConfigurationSetting("TestEtag", "TestKey", label, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, label, null, null, null); // Act await Repository.AddAsync(setting); @@ -151,7 +155,7 @@ public async Task AddAsync_LastModifiedParameter_ConfigurationSetting() { // Arrange var lastModified = DateTimeOffset.UtcNow; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, lastModified, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", lastModified, false, null, null, null, null); // Act await Repository.AddAsync(setting); @@ -169,7 +173,7 @@ public async Task AddAsync_LockedParameter_ConfigurationSetting() { // Arrange const bool locked = false; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, locked, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, locked, null, null, null, null); // Act await Repository.AddAsync(setting); @@ -187,7 +191,7 @@ public async Task AddAsync_TagsParameter_ConfigurationSetting() { // Arrange var tags = new Dictionary { { "TestKey", "TestValue" } }; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, tags); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, null, null, tags); // Act await Repository.AddAsync(setting); @@ -205,7 +209,7 @@ public async Task AddAsync_ValueParameter_ConfigurationSetting() { // Arrange const string value = "TestValue"; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, value, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, null, value, null); // Act await Repository.AddAsync(setting); @@ -293,7 +297,7 @@ public async Task Get_CommandText_KeyAndLabelAndMoment(string key, string label, public async Task RemoveAsync_CommandText_ConfigurationSetting(string key, string? label, string expected) { // Arrange - var setting = new ConfigurationSetting("TestEtag", key, label, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", key, DateTimeOffset.UtcNow, false, label, null, null, null); // Act await Repository.RemoveAsync(setting); @@ -312,7 +316,7 @@ public async Task RemoveAsync_KeyParameter_ConfigurationSetting() { // Arrange const string key = "TestKey"; - var setting = new ConfigurationSetting("TestEtag", key, null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", key, DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.RemoveAsync(setting); @@ -330,7 +334,7 @@ public async Task RemoveAsync_KeyParameter_ConfigurationSetting() public async Task RemoveAsync_LabelParameter_ConfigurationSetting(string? label) { // Arrange - var setting = new ConfigurationSetting("TestEtag", "TestKey", label, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, label, null, null, null); // Act await Repository.RemoveAsync(setting); @@ -359,7 +363,7 @@ public async Task RemoveAsync_LabelParameter_ConfigurationSetting(string? label) public async Task UpdateAsync_CommandText_ConfigurationSetting(string key, string? label, string expected) { // Arrange - var setting = new ConfigurationSetting("TestEtag", key, label, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", key, DateTimeOffset.UtcNow, false, label, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -378,7 +382,7 @@ public async Task UpdateAsync_ContentTypeParameter_ConfigurationSetting() { // Arrange const string contentType = "TestContentType"; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, contentType, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, contentType, null, null); // Act await Repository.UpdateAsync(setting); @@ -396,7 +400,7 @@ public async Task UpdateAsync_EtagParameter_ConfigurationSetting() { // Arrange const string etag = "TestEtag"; - var setting = new ConfigurationSetting(etag, "TestKey", null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting(etag, "TestKey", DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -414,7 +418,7 @@ public async Task UpdateAsync_KeyParameter_ConfigurationSetting() { // Arrange const string key = "TestKey"; - var setting = new ConfigurationSetting("TestEtag", key, null, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", key, DateTimeOffset.UtcNow, false, null, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -432,7 +436,7 @@ public async Task UpdateAsync_KeyParameter_ConfigurationSetting() public async Task UpdateAsync_LabelParameter_ConfigurationSetting(string? label) { // Arrange - var setting = new ConfigurationSetting("TestEtag", "TestKey", label, null, null, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, label, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -461,7 +465,7 @@ public async Task UpdateAsync_LastModifiedParameter_ConfigurationSetting() { // Arrange var lastModified = DateTimeOffset.UtcNow; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, lastModified, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", lastModified, false, null, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -479,7 +483,7 @@ public async Task UpdateAsync_LockedParameter_ConfigurationSetting() { // Arrange const bool locked = false; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, locked, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, locked, null, null, null, null); // Act await Repository.UpdateAsync(setting); @@ -497,7 +501,7 @@ public async Task UpdateAsync_TagsParameter_ConfigurationSetting() { // Arrange var tags = new Dictionary { { "TestKey", "TestValue" } }; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, null, DateTimeOffset.UtcNow, false, tags); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, null, null, tags); // Act await Repository.UpdateAsync(setting); @@ -515,7 +519,7 @@ public async Task UpdateAsync_ValueParameter_ConfigurationSetting() { // Arrange const string value = "TestValue"; - var setting = new ConfigurationSetting("TestEtag", "TestKey", null, null, value, DateTimeOffset.UtcNow, false, null); + var setting = new ConfigurationSetting("TestEtag", "TestKey", DateTimeOffset.UtcNow, false, null, null, value, null); // Act await Repository.UpdateAsync(setting);