diff --git a/Source/Starfish.Webapp/Layout/MainLayout.razor b/Source/Starfish.Webapp/Layout/MainLayout.razor
index 090f1f2..e3885e0 100644
--- a/Source/Starfish.Webapp/Layout/MainLayout.razor
+++ b/Source/Starfish.Webapp/Layout/MainLayout.razor
@@ -50,6 +50,10 @@
{
}
+ else if(ex is HttpRequestException e && e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
+ {
+
+ }
else
{
@ex.Message
@@ -61,14 +65,10 @@
-
- Nerosoft © 2024. All rights reserved.
-
+ Nerosoft © @(DateTime.Today.Year). All rights reserved.
diff --git a/Source/Starfish.Webapp/Pages/Apps/Index.razor b/Source/Starfish.Webapp/Pages/Apps/Index.razor
index b1b5baa..6d0395c 100644
--- a/Source/Starfish.Webapp/Pages/Apps/Index.razor
+++ b/Source/Starfish.Webapp/Pages/Apps/Index.razor
@@ -14,7 +14,7 @@
-
+
@(Resources.IDS_COMMON_SEARCH)
@@ -23,15 +23,15 @@
-
-
+
+
@(context.Name)
-
-
-
-
-
+
+
+
+
+
@@ -96,7 +96,7 @@
private async Task HandleDetailClicked(long id)
{
- Navigation.NavigateTo($"/apps/{id}");
+ Navigation.NavigateTo($"/apps/{id}/settings");
await Task.CompletedTask;
}
diff --git a/Source/Starfish.Webapp/Pages/Logging/Detail.razor b/Source/Starfish.Webapp/Pages/Logging/Detail.razor
new file mode 100644
index 0000000..beeb41d
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Logging/Detail.razor
@@ -0,0 +1,5 @@
+Detail
+
+@code {
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Logging/Index.razor b/Source/Starfish.Webapp/Pages/Logging/Index.razor
index 7d7edfc..a5ed32f 100644
--- a/Source/Starfish.Webapp/Pages/Logging/Index.razor
+++ b/Source/Starfish.Webapp/Pages/Logging/Index.razor
@@ -12,7 +12,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
diff --git a/Source/Starfish.Webapp/Pages/Setting/Edit.razor b/Source/Starfish.Webapp/Pages/Setting/Edit.razor
new file mode 100644
index 0000000..ef719d4
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Setting/Edit.razor
@@ -0,0 +1,137 @@
+@using Nerosoft.Starfish.Common
+@implements IDialogContentComponent
+
+@inject ISettingApi SettingApi
+@inject IToastService ToastService
+
+
+
+
+
+ @Dialog.Instance.Parameters.Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @(Resources.IDS_COMMON_SAVE)
+ @(Resources.IDS_COMMON_CANCEL)
+
+
+@code {
+
+ [Parameter]
+ public EditDialogArgs Content { get; set; }
+
+ [CascadingParameter]
+ public FluentDialog Dialog { get; set; } = default!;
+
+ private string Mode => Content?.Properties?.TryGetValue("mode") as string;
+
+ private string Format => Content?.Properties?.TryGetValue("format") as string;
+
+ private StandaloneCodeEditor _editor = null!;
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (Mode == "update")
+ {
+ await LoadValueAsync();
+ }
+ }
+
+ private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
+ {
+ var options = new StandaloneEditorConstructionOptions
+ {
+ AutomaticLayout = true,
+ Theme = "vs-dark"
+ };
+
+ options.Language = Format switch
+ {
+ Constants.Setting.FormatJson => "json",
+ Constants.Setting.FormatText => "text",
+ _ => options.Language
+ };
+
+ return options;
+ }
+
+ private async Task EditorOnDidInit()
+ {
+ await Task.CompletedTask;
+ }
+
+ private bool Loading { get; set; }
+
+ private async Task SaveAsync()
+ {
+ try
+ {
+ Loading = true;
+ var value = await _editor.GetValue();
+ var request = new SettingEditDto()
+ {
+ Type = "diff",
+ Data = Cryptography.Base64.Encrypt(value)
+ };
+ var task = Mode switch
+ {
+ "create" => SettingApi.CreateAsync(Content.AppId, Content.Environment, Format, request)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ task.Result.EnsureSuccess();
+ }),
+ "update" => SettingApi.UpdateAsync(Content.AppId, Content.Environment, Format, request)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ task.Result.EnsureSuccess();
+ }),
+ _ => Task.CompletedTask
+ };
+ await task;
+ await Dialog.CloseAsync(Content);
+ }
+ catch (Exception exception)
+ {
+ var message = exception.GetPromptMessage();
+ ToastService.ShowError(message);
+ }
+ finally
+ {
+ Loading = false;
+ }
+ }
+
+ private async Task CancelAsync()
+ {
+ await Dialog.CancelAsync();
+ }
+
+ private Task LoadValueAsync()
+ {
+ return SettingApi.GetItemsAsync(Content.AppId, Content.Environment, Format)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ var value = Cryptography.Base64.Decrypt(task.Result.EnsureSuccess());
+ _editor.SetValue(value);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Setting/EditDialogArgs.cs b/Source/Starfish.Webapp/Pages/Setting/EditDialogArgs.cs
new file mode 100644
index 0000000..75d163c
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Setting/EditDialogArgs.cs
@@ -0,0 +1,16 @@
+namespace Nerosoft.Starfish.Webapp.Pages.Setting;
+
+public class EditDialogArgs
+{
+ public EditDialogArgs(long appId, string environment)
+ {
+ AppId = appId;
+ Environment = environment;
+ }
+
+ public long AppId { get; set; }
+
+ public string Environment { get; set; }
+
+ public Dictionary Properties { get; set; }
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Setting/EditValue.razor b/Source/Starfish.Webapp/Pages/Setting/EditValue.razor
new file mode 100644
index 0000000..b650245
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Setting/EditValue.razor
@@ -0,0 +1,81 @@
+@implements IDialogContentComponent
+
+@inject ISettingApi SettingApi
+@inject IToastService ToastService
+
+
+
+
+
+ @Dialog.Instance.Parameters.Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @(Resources.IDS_COMMON_SAVE)
+ @(Resources.IDS_COMMON_CANCEL)
+
+
+@code {
+
+ [Parameter]
+ public EditDialogArgs Content { get; set; }
+
+ [CascadingParameter]
+ public FluentDialog Dialog { get; set; } = default!;
+
+ private bool Loading { get; set; }
+
+ private string Key => Content?.Properties?.TryGetValue("key") as string;
+
+ private SettingValueUpdateDto Request { get; } = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ Request.Value = Content?.Properties?.TryGetValue("value") as string;
+ await Task.CompletedTask;
+ }
+
+ private async Task SaveAsync()
+ {
+ try
+ {
+ Loading = true;
+ await SettingApi.UpdateItemValueAsync(Content.AppId, Content.Environment, Key, Request)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ task.Result.EnsureSuccess();
+ });
+ await Dialog.CloseAsync(Content);
+ }
+ catch (Exception exception)
+ {
+ var message = exception.GetPromptMessage();
+ ToastService.ShowError(message);
+ }
+ finally
+ {
+ Loading = false;
+ }
+ }
+
+ private async Task CancelAsync()
+ {
+ await Dialog.CancelAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Setting/Index.razor b/Source/Starfish.Webapp/Pages/Setting/Index.razor
index a62cdbb..4bf85d2 100644
--- a/Source/Starfish.Webapp/Pages/Setting/Index.razor
+++ b/Source/Starfish.Webapp/Pages/Setting/Index.razor
@@ -1,5 +1,262 @@
-Setting
+@page "/apps/{id:long}/settings"
+
+@attribute [Authorize]
+
+@inject IDialogService DialogService
+@inject ISettingApi SettingApi
+@inject IAppsApi AppsApi
+
+
+ @(Resources.IDS_MENU_TEXT_HOME)
+ @(Resources.IDS_MENU_TEXT_APPS)
+ @(Resources.IDS_BREADCRUMB_SETTING)
+
+
+
+
+
+
+
+
+
+ DEV (@Resources.IDS_COMMON_ENVIRONMENT_DEV)
+ SIT (@Resources.IDS_COMMON_ENVIRONMENT_SIT)
+ UAT (@Resources.IDS_COMMON_ENVIRONMENT_UAT)
+ PET (@Resources.IDS_COMMON_ENVIRONMENT_PET)
+ SIM (@Resources.IDS_COMMON_ENVIRONMENT_SIM)
+ PRD (@Resources.IDS_COMMON_ENVIRONMENT_PRD)
+
+
+ @(Resources.IDS_SETTING_INDEX_BUTTON_EDIT_JSON)
+ @(Resources.IDS_SETTING_INDEX_BUTTON_EDIT_TEXT)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ App name: @AppDetail?.Name
+ App code: @AppDetail?.Code
+ App name: @AppDetail?.StatusDescription
+
+ @AppDetail?.Description
+
+
+ @if (SettingDetail != null)
+ {
+
+
+ Version: @SettingDetail?.Version
+
+ @(Resources.IDS_COMMON_OPERATIONS)
+
+ @(Resources.IDS_SETTING_INDEX_BUTTON_SYNC_REDIS)
+ @(Resources.IDS_SETTING_INDEX_BUTTON_DELETE)
+
+ @if (SettingDetail?.Status == "Pending")
+ {
+ @(Resources.IDS_SETTING_INDEX_BUTTON_PUBLISH)
+ }
+
+ }
+
+ Panel three content
+
+
+
+
+
+
+
@code {
-}
+ [Parameter]
+ public long Id { get; set; }
+
+ [CascadingParameter]
+ private Task AuthenticationState { get; set; }
+
+ private UserPrincipal Identity { get; set; }
+
+ private GridItemsProvider _provider;
+
+ private PaginationState Pagination { get; } = new() { ItemsPerPage = Constants.Query.DefaultSize };
+
+ private int Total { get; set; }
+
+ private SettingDetailDto SettingDetail { get; set; }
+
+ private AppInfoDetailDto AppDetail { get; set; }
+
+ private string Environment { get; set; } = "DEV";
+
+ protected override async Task OnInitializedAsync()
+ {
+ var user = await AuthenticationState;
+
+ Identity = new UserPrincipal(user.User);
+
+ await LoadAppDetailAsync();
+ await LoadSettingDetailAsync();
+
+ _provider = async request =>
+ {
+ List items = null;
+ var tasks = new List
+ {
+ SettingApi.GetItemListAsync(Id, Environment, request.StartIndex + 1, Pagination.ItemsPerPage, request.CancellationToken)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException(request.CancellationToken);
+ items = task.Result.EnsureSuccess();
+ }, request.CancellationToken)
+ };
+
+ if (request.StartIndex == 0)
+ {
+ tasks.Add(
+ SettingApi.GetItemCountAsync(Id, Environment, request.CancellationToken)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException(request.CancellationToken);
+ Total = task.Result.EnsureSuccess();
+ }, request.CancellationToken)
+ );
+ }
+
+ await Task.WhenAll(tasks);
+ await Pagination.SetTotalItemCountAsync(Total);
+ return GridItemsProviderResult.From(items, Total);
+ };
+ }
+
+ private async Task OnSelectedEnvironmentChanged(string value)
+ {
+ Environment = value;
+ var tasks = new List
+ {
+ Pagination.SetCurrentPageIndexAsync(0),
+ LoadSettingDetailAsync()
+ };
+ await Task.WhenAll(tasks);
+ }
+
+ private Task LoadSettingDetailAsync(CancellationToken cancellationToken = default)
+ {
+ return SettingApi.GetAsync(Id, Environment, cancellationToken)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException(cancellationToken);
+ SettingDetail = task.Result.EnsureSuccess();
+ }, cancellationToken);
+ }
+
+ private Task LoadAppDetailAsync()
+ {
+ return AppsApi.GetAsync(Id)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ AppDetail = task.Result.EnsureSuccess();
+ });
+ }
+
+ private async Task OnEditJsonClicked()
+ {
+ var args = new EditDialogArgs(Id, Environment)
+ {
+ Properties = new Dictionary
+ {
+ ["mode"] = SettingDetail == null ? "create" : "update",
+ ["format"] = Constants.Setting.FormatJson
+ }
+ };
+ var title = SettingDetail == null ? Resources.IDS_SETTING_EDIT_DIALOG_TITLE_CREATE_FROM_JSON : Resources.IDS_SETTING_EDIT_DIALOG_TITLE_EDIT_AS_JSON;
+
+ var dialog = await DialogService.ShowDialogAsync(args, new DialogParameters { Title = title, Width = "calc(100% - 2px)", Height = "calc(100% - 2px)" });
+ var result = await dialog.Result;
+ if (!result.Cancelled)
+ {
+ await Pagination.SetCurrentPageIndexAsync(0);
+ }
+ }
+
+ private async Task OnEditTextClicked()
+ {
+ var args = new EditDialogArgs(Id, Environment)
+ {
+ Properties = new Dictionary
+ {
+ ["mode"] = SettingDetail == null ? "create" : "update",
+ ["format"] = Constants.Setting.FormatText
+ }
+ };
+
+ var title = SettingDetail == null ? Resources.IDS_SETTING_EDIT_DIALOG_TITLE_CREATE_FROM_TEXT : Resources.IDS_SETTING_EDIT_DIALOG_TITLE_EDIT_AS_TEXT;
+
+ var dialog = await DialogService.ShowDialogAsync(args, new DialogParameters { Title = title, Width = "calc(100% - 2px)", Height = "calc(100% - 2px)" });
+ var result = await dialog.Result;
+ if (!result.Cancelled)
+ {
+ await Pagination.SetCurrentPageIndexAsync(0);
+ }
+ }
+
+ private async Task OnEditValueClicked(string key, string value)
+ {
+ var args = new EditDialogArgs(Id, Environment)
+ {
+ Properties = new Dictionary
+ {
+ ["key"] = key,
+ ["value"] = value
+ }
+ };
+
+ var dialog = await DialogService.ShowDialogAsync(args, new DialogParameters { Title = Resources.IDS_SETTING_EDIT_DIALOG_TITLE_EDIT_VALUE });
+ var result = await dialog.Result;
+ if (!result.Cancelled)
+ {
+ await Pagination.SetCurrentPageIndexAsync(0);
+ }
+ }
+
+ private async Task OnPublishClicked()
+ {
+ var args = new EditDialogArgs(Id, Environment);
+ var dialog = await DialogService.ShowDialogAsync(args, new DialogParameters { Title = Resources.IDS_SETTING_PUBLISH_DIALOG_TITLE });
+ var result = await dialog.Result;
+ if (!result.Cancelled)
+ {
+ await LoadSettingDetailAsync();
+ }
+ }
+
+ private async Task OnSyncRedisClicked()
+ {
+ var args = new EditDialogArgs(Id, Environment);
+ var dialog = await DialogService.ShowDialogAsync(args, new DialogParameters { Title = Resources.IDS_SETTING_SYNC_REDIS_DIALOG_TITLE });
+ }
+
+ private async Task OnDeleteClicked()
+ {
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Setting/Publish.razor b/Source/Starfish.Webapp/Pages/Setting/Publish.razor
new file mode 100644
index 0000000..8aa97fc
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Setting/Publish.razor
@@ -0,0 +1,75 @@
+@implements IDialogContentComponent
+
+@inject ISettingApi SettingApi
+@inject IToastService ToastService
+
+
+
+
+
+ @Dialog.Instance.Parameters.Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @(Resources.IDS_SETTING_PUBLISH_DIALOG_BUTTON_PUBLISH)
+ @(Resources.IDS_COMMON_CANCEL)
+
+
+@code {
+
+ [Parameter]
+ public EditDialogArgs Content { get; set; }
+
+ [CascadingParameter]
+ public FluentDialog Dialog { get; set; } = default!;
+
+ private bool Loading { get; set; }
+
+ private SettingPublishDto Request { get; } = new();
+
+ private async Task PublishAsync()
+ {
+ try
+ {
+ Loading = true;
+
+ await SettingApi.PublishAsync(Content.AppId, Content.Environment, Request)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ task.Result.EnsureSuccess();
+ });
+
+ await Dialog.CloseAsync(Content);
+ }
+ catch (Exception exception)
+ {
+ var message = exception.GetPromptMessage();
+ ToastService.ShowError(message);
+ }
+ finally
+ {
+ Loading = false;
+ }
+ }
+
+ private async Task CancelAsync()
+ {
+ await Dialog.CancelAsync();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Setting/SyncRedis.razor b/Source/Starfish.Webapp/Pages/Setting/SyncRedis.razor
new file mode 100644
index 0000000..c2d3037
--- /dev/null
+++ b/Source/Starfish.Webapp/Pages/Setting/SyncRedis.razor
@@ -0,0 +1,123 @@
+@using System.IO.Compression
+@implements IDialogContentComponent
+
+@inject ISettingApi SettingApi
+@inject IToastService ToastService
+
+
+
+
+
+ @Dialog.Instance.Parameters.Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @(Resources.IDS_SETTING_SYNC_REDIS_DIALOG_BUTTON_START)
+ @(Resources.IDS_COMMON_CANCEL)
+
+
+@code {
+
+ [Parameter]
+ public EditDialogArgs Content { get; set; }
+
+ [CascadingParameter]
+ public FluentDialog Dialog { get; set; } = default!;
+
+ private bool Loading { get; set; }
+
+ private string ConnectionString { get; set; }
+
+ private int Database { get; set; }
+
+ private string Key { get; set; }
+
+ private async Task SaveAsync()
+ {
+ try
+ {
+ Loading = true;
+
+ var raw = await SettingApi.GetArchivedAsync(Content.AppId, Content.Environment)
+ .ContinueWith(task =>
+ {
+ task.WaitAndUnwrapException();
+ return task.Result.EnsureSuccess();
+ });
+
+ var buffer = Convert.FromBase64String(raw);
+ var items = Decompress(buffer, buffer.Length);
+
+ // var connection = ConnectionMultiplexer.Connect(ConnectionString);
+ // using (connection)
+ // {
+ // var entries = items.Select(t => new HashEntry(t.Key, t.Value)).ToArray();
+
+ // await connection.GetDatabase(Database).HashSetAsync(Key, entries);
+ // await connection.GetDatabase(Database).KeyExpireAsync(Key, default(TimeSpan?));
+ // await connection.CloseAsync();
+ // }
+
+ await Dialog.CloseAsync(Content);
+ }
+ catch (Exception exception)
+ {
+ var message = exception.GetPromptMessage();
+ ToastService.ShowError(message);
+ }
+ finally
+ {
+ Loading = false;
+ }
+ }
+
+ private async Task CancelAsync()
+ {
+ await Dialog.CancelAsync();
+ }
+
+ private static Dictionary Decompress(byte[] data, int count)
+ {
+ var stream = new MemoryStream(data, 0, count);
+ var zip = new GZipStream(stream, CompressionMode.Decompress, true);
+ var destStream = new MemoryStream();
+ var buffer = new byte[0x1000];
+ while (true)
+ {
+ var reader = zip.Read(buffer, 0, buffer.Length);
+ if (reader <= 0)
+ {
+ break;
+ }
+
+ destStream.Write(buffer, 0, reader);
+ }
+
+ zip.Close();
+ stream.Close();
+ destStream.Position = 0;
+ buffer = destStream.ToArray();
+ destStream.Close();
+ var content = Encoding.UTF8.GetString(buffer);
+ return System.Text.Json.JsonSerializer.Deserialize>(content);
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Starfish.Webapp/Pages/Team/Detail.razor b/Source/Starfish.Webapp/Pages/Team/Detail.razor
index 6f77428..66a97dd 100644
--- a/Source/Starfish.Webapp/Pages/Team/Detail.razor
+++ b/Source/Starfish.Webapp/Pages/Team/Detail.razor
@@ -19,9 +19,9 @@
Updated at: @Data.UpdateTime.Date
@Data.Description
-
@if (Data.OwnerId != Identity?.GetUserIdOfInt32())
{
+
@(Resources.IDS_COMMON_OPERATIONS)
@(Resources.IDS_TEAM_DETAIL_BUTTON_EDIT)
diff --git a/Source/Starfish.Webapp/Pages/Team/Index.razor b/Source/Starfish.Webapp/Pages/Team/Index.razor
index 67c3c53..a6a550b 100644
--- a/Source/Starfish.Webapp/Pages/Team/Index.razor
+++ b/Source/Starfish.Webapp/Pages/Team/Index.razor
@@ -14,7 +14,7 @@
-
+
@(Resources.IDS_COMMON_SEARCH)
@@ -23,15 +23,15 @@
-
-
+
+
@(context.Name)
-
-
-
-
-
+
+
+
+
+
diff --git a/Source/Starfish.Webapp/Pages/User/Index.razor b/Source/Starfish.Webapp/Pages/User/Index.razor
index 349a8f2..d07bd72 100644
--- a/Source/Starfish.Webapp/Pages/User/Index.razor
+++ b/Source/Starfish.Webapp/Pages/User/Index.razor
@@ -14,7 +14,7 @@
-
+
@(Resources.IDS_COMMON_SEARCH)
@@ -23,7 +23,7 @@
-
+
@(context.UserName)
diff --git a/Source/Starfish.Webapp/wwwroot/css/app.css b/Source/Starfish.Webapp/wwwroot/css/app.css
index af5ab7f..dea628f 100644
--- a/Source/Starfish.Webapp/wwwroot/css/app.css
+++ b/Source/Starfish.Webapp/wwwroot/css/app.css
@@ -217,4 +217,12 @@ nav[class*="paginator-nav"] {
display: flex;
width: 100%;
gap: 10px;
-}
\ No newline at end of file
+}
+
+.aside-right {
+ background-color: var(--neutral-fill-stealth-rest);
+}
+
+ .aside-right div[class*="region"] {
+ background-color: var(--neutral-fill-input-hover) !important;
+ }
\ No newline at end of file