Skip to content

Commit

Permalink
feat: add export to json file (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnc1997 authored Dec 1, 2024
1 parent a59356b commit 62ede9b
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</AzureInputRadioLabel>

<AzureInputRadioLabel>
<AzureInputRadio checked="@(Value is Operation.Export)" disabled Value="@Operation.Export"/>
<AzureInputRadio checked="@(Value is Operation.Export)" Value="@Operation.Export"/>
<div>Export</div>
</AzureInputRadioLabel>
</AzureInputRadioGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@using System.Linq.Expressions

<AzureInputSelect AdditionalAttributes="@AdditionalAttributes" TValue="@string" Value="@Value" ValueChanged="@HandleValueChanged" ValueExpression="@ValueExpression">
<option checked="@(Value is null)" hidden value="">Please select a target service</option>
<option checked="@(Value is TargetType.AzureAppConfiguration)" disabled value="@TargetType.AzureAppConfiguration">App Configuration</option>
<option checked="@(Value is TargetType.AzureAppService)" disabled value="@TargetType.AzureAppService">App Service</option>
<option checked="@(Value is TargetType.ConfigurationFile)" value="@TargetType.ConfigurationFile">Configuration file</option>
</AzureInputSelect>

@code {
[Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object>? AdditionalAttributes { get; set; }

[Parameter] public string? Value { get; set; }

[Parameter] public EventCallback<string?> ValueChanged { get; set; }

[Parameter] public Expression<Func<string?>>? ValueExpression { get; set; }

private async Task HandleValueChanged(string? value)
{
await ValueChanged.InvokeAsync(!string.IsNullOrEmpty(value) ? value : null);
}

public static class TargetType
{
public const string AzureAppConfiguration = nameof(AzureAppConfiguration);

public const string AzureAppService = nameof(AzureAppService);

public const string ConfigurationFile = nameof(ConfigurationFile);
}

}
106 changes: 103 additions & 3 deletions src/AzureAppConfigurationEmulator/Components/Pages/ImportExport.razor
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
@implements IAsyncDisposable
@inject IConfigurationSettingFactory ConfigurationSettingFactory
@inject IConfigurationSettingRepository ConfigurationSettingRepository
@inject IJSRuntime JS
@inject IKeyValuePairJsonDecoder KeyValuePairJsonDecoder
@inject IKeyValuePairJsonEncoder KeyValuePairJsonEncoder
@page "/kvdata"
@using System.Net
@using System.Security.Cryptography
@using System.Text
@using System.Text.Json
Expand All @@ -21,6 +25,61 @@
<EditForm class="flex flex-col gap-10" Model="@Model" OnSubmit="@HandleSubmit">
@switch (Model?.Operation)
{
case ImportExportOperationInputRadioGroup.Operation.Export:
<div class="flex flex-col gap-3">
<ImportExportOperationInputRadioGroup @bind-Value="@Model.Operation" name="@nameof(Model.Operation)"/>

<label class="flex flex-row items-center">
<div class="w-[200px]">Target type</div>
<div class="flex-1 max-w-[600px]">
<ImportExportTargetTypeInputSelect @bind-Value="@Model.TargetType" name="@nameof(Model.TargetType)"/>
</div>
</label>
</div>

switch (Model.TargetType)
{
case ImportExportTargetTypeInputSelect.TargetType.ConfigurationFile:
<div class="flex flex-col gap-3">
<div class="font-bold text-lg">Export options</div>

<label class="flex flex-row items-center">
<div class="w-[200px]">File format</div>
<div class="flex-1 max-w-[600px]">
<ImportExportFileFormatInputSelect @bind-Value="@Model.FileFormat" name="@nameof(Model.FileFormat)"/>
</div>
</label>
</div>

if (Model.FileFormat is not null)
{
<div class="flex flex-col gap-3">
<div class="font-bold text-lg">Apply changes to key-values</div>

<label class="flex flex-row items-center">
<div class="w-[200px]">Separator</div>
<div class="flex-1 max-w-[600px]">
<ImportExportSeparatorInputSelect @bind-Value="@Model.Separator" name="@nameof(Model.Separator)"/>
</div>
</label>

<label class="flex flex-row items-center">
<div class="w-[200px]">Remove prefix</div>
<div class="flex-1 max-w-[600px]">
<AzureInputText @bind-Value="@Model.Prefix" name="@nameof(Model.Prefix)"/>
</div>
</label>
</div>

<div>
<AzureButton Appearance="AzureButton.AzureAppearance.Primary" type="submit">Export</AzureButton>
</div>
}

break;
}

break;
case ImportExportOperationInputRadioGroup.Operation.Import:
<div class="flex flex-col gap-3">
<ImportExportOperationInputRadioGroup @bind-Value="@Model.Operation" name="@nameof(Model.Operation)"/>
Expand Down Expand Up @@ -86,7 +145,7 @@
</div>

<div>
<AzureButton Appearance="AzureButton.AzureAppearance.Primary" type="submit">@(Model.Operation switch { ImportExportOperationInputRadioGroup.Operation.Export => "Export", ImportExportOperationInputRadioGroup.Operation.Import => "Import", _ => throw new ArgumentOutOfRangeException() })</AzureButton>
<AzureButton Appearance="AzureButton.AzureAppearance.Primary" type="submit">Import</AzureButton>
</div>
}

Expand Down Expand Up @@ -148,7 +207,7 @@
</div>

<div>
<AzureButton Appearance="AzureButton.AzureAppearance.Primary" type="submit">@(Model.Operation switch { ImportExportOperationInputRadioGroup.Operation.Export => "Export", ImportExportOperationInputRadioGroup.Operation.Import => "Import", _ => throw new ArgumentOutOfRangeException() })</AzureButton>
<AzureButton Appearance="AzureButton.AzureAppearance.Primary" type="submit">Import</AzureButton>
</div>
}

Expand All @@ -166,6 +225,24 @@

private ICollection<string?> Labels { get; } = [];

private IJSObjectReference? Module { get; set; }

public async ValueTask DisposeAsync()
{
if (Module is not null)
{
await Module.DisposeAsync();
}
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Module = await JS.InvokeAsync<IJSObjectReference>("import", "./Components/Pages/ImportExport.razor.js");
}
}

protected override void OnInitialized()
{
Model ??= new InputModel();
Expand Down Expand Up @@ -218,6 +295,27 @@

switch (Model?.Operation)
{
case ImportExportOperationInputRadioGroup.Operation.Export:
{
switch (Model?.TargetType)
{
case ImportExportTargetTypeInputSelect.TargetType.ConfigurationFile:
{
using var document = KeyValuePairJsonEncoder.Encode(await ConfigurationSettingRepository.Get().Where(setting => setting is not FeatureFlagConfigurationSetting).ToDictionaryAsync(setting => setting.Key, setting => setting.Value), Model.Prefix, Model.Separator);

if (Module is not null)
{
await Module.InvokeVoidAsync("download", $"{Dns.GetHostName()}-{DateTimeOffset.UtcNow:yyyy-MM-dd}.json", Convert.ToBase64String(JsonSerializer.SerializeToUtf8Bytes(document)));
}

Model = new InputModel();

break;
}
}

break;
}
case ImportExportOperationInputRadioGroup.Operation.Import:
{
switch (Model?.SourceType)
Expand Down Expand Up @@ -284,7 +382,7 @@
destinationSetting.Etag = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(date.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"))));
destinationSetting.LastModified = date;
destinationSetting.ContentType = Model.ContentType;
destinationSetting.Value = sourceValue?.ToString();
destinationSetting.Value = sourceValue;

await ConfigurationSettingRepository.Update(destinationSetting);
}
Expand Down Expand Up @@ -334,6 +432,8 @@
public IBrowserFile? SourceFile { get; set; }

public string? SourceType { get; set; }

public string? TargetType { get; set; }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function download(name, bytes) {
const element = document.createElement("a");

element.download = name;
element.href = "data:application/octet-stream;base64," + bytes;

window.document.body.appendChild(element);

element.click();

window.document.body.removeChild(element);
}

0 comments on commit 62ede9b

Please sign in to comment.