Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add export to json file #105

Merged
merged 1 commit into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Loading