public async Task <IActionResult> CreateCatalog() { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageSearchSettings)) { return(Unauthorized()); } // Create catalog var result = await _fullTextCatalogManager.CreateCatalogAsync(); if (result.Succeeded) { _alerter.Success(T["Catalog Created Successfully!"]); } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> CreatePost(CreateThemeViewModel viewModel) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.CreateThemes)) { return(Unauthorized()); } var user = await _contextFacade.GetAuthenticatedUserAsync(); if (user == null) { return(Unauthorized()); } if (!ModelState.IsValid) { // Add model state errors foreach (var modelState in ViewData.ModelState.Values) { foreach (var error in modelState.Errors) { _alerter.Danger(T[error.ErrorMessage]); } } // Return return(RedirectToAction(nameof(Index))); } // Create theme var result = _themeCreator.CreateTheme(viewModel.ThemeId, viewModel.Name); if (result.Succeeded) { // Execute view providers await _viewProvider.ProvideUpdateAsync(new ThemeAdmin() { ThemeId = viewModel.ThemeId, Path = viewModel.Name }, this); // Add confirmation _alerter.Success(T["Theme Added Successfully!"]); // Return return(RedirectToAction(nameof(Index))); } else { // Report any errors foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } return(View(viewModel)); }
// ------------------ // Enable Features // ------------------ public async Task <IActionResult> Enable( string id, string category, string returnUrl) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.EnableFeatures)) { return(Unauthorized()); } if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } if (string.IsNullOrEmpty(category)) { throw new ArgumentNullException(nameof(category)); } if (string.IsNullOrEmpty(returnUrl)) { throw new ArgumentNullException(nameof(returnUrl)); } // Enable feature var result = !string.IsNullOrEmpty(category) ? await InstallByCategoryAsync(category) : await InstallByIdAsync(id); if (result.Succeeded) { _alerter.Success(T[$"Features enabled successfully!"]); } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } if (!string.IsNullOrEmpty(returnUrl)) { // Redirect to returnUrl return(RedirectToLocal(returnUrl)); } else { return(RedirectToAction(nameof(Index), new RouteValueDictionary() { ["opts.category"] = category })); } }
// -------------- // Move Up / Down // -------------- public async Task <IActionResult> MoveUp(int id) { var category = await _categoryStore.GetByIdAsync(id); if (category == null) { return(NotFound()); } var result = await _categoryManager.Move(category, MoveDirection.Up); if (result.Succeeded) { _alerter.Success(T["Category Updated Successfully"]); } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> ResetPassword() { // Get user var user = await _contextFacade.GetAuthenticatedUserAsync(); // Ensure user exists if (user == null) { return(NotFound()); } var result = await _platoUserManager.GetForgotPasswordUserAsync(user.UserName); if (result.Succeeded) { // Ensure account has been confirmed if (await _userManager.IsEmailConfirmedAsync(user)) { user.ResetToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(result.Response.ResetToken)); var emailResult = await _userEmails.SendPasswordResetTokenAsync(result.Response); if (emailResult.Succeeded) { _alerter.Success(T["Check your email. We've sent you a password reset link!"]); } else { foreach (var error in emailResult.Errors) { _alerter.Danger(T[error.Description]); } } } else { _alerter.Danger(T["You must confirm your email before you can reset your password!"]); } } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); //ViewData.ModelState.AddModelError(string.Empty, error.Description); } } return(RedirectToAction(nameof(EditAccount))); }
public async Task <IActionResult> CreateApiKey() { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.ManageGeneralSettings)) { return(Unauthorized()); } var settings = await _siteSettingsStore.GetAsync(); if (settings == null) { return(RedirectToAction(nameof(Index))); } var result = await _siteSettingsStore.SaveAsync(settings); if (result != null) { _alerter.Success(T["Key Updated Successfully!"]); } else { _alerter.Danger(T["A problem occurred updating the settings. Please try again!"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> IndexPost(HttpsSettingsViewModel viewModel) { //if (!await _authorizationService.AuthorizeAsync(User, PermissionsProvider.ManageRoles)) //{ // return Unauthorized(); //} if (!ModelState.IsValid) { return(View(await GetModel())); } var settings = new HttpsSettings() { EnforceSsl = viewModel.EnforceSsl, UsePermanentRedirect = viewModel.UsePermanentRedirect, SslPort = viewModel.SslPort }; var result = await _httpsSettingsStore.SaveAsync(settings); if (result != null) { _alerter.Success(T["Settings Updated Successfully!"]); } else { _alerter.Danger(T["A problem occurred updating the settings. Please try again!"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Delete(string id) { var ok = int.TryParse(id, out int categoryId); if (!ok) { return(NotFound()); } var currentTag = await _tagStore.GetByIdAsync(categoryId); if (currentTag == null) { return(NotFound()); } var result = await _tagManager.DeleteAsync(currentTag); if (result.Succeeded) { _alerter.Success(T["Tag Deleted Successfully"]); } else { _alerter.Danger(T["Could not delete the tag"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Enable( string id, string category, string returnUrl) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.EnableFeatures)) { return(Unauthorized()); } var contexts = await _shellFeatureManager.EnableFeatureAsync(id); foreach (var context in contexts) { if (context.Errors.Any()) { foreach (var error in context.Errors) { _alerter.Danger(T[$"{context.Feature.ModuleId} could not be enabled. {error.Key} - {error.Value}"]); } } else { _alerter.Success(T[$"{context.Feature.ModuleId} enabled successfully!"]); } } if (!string.IsNullOrEmpty(returnUrl)) { // Redirect to returnUrl return(RedirectToLocal(returnUrl)); } else { return(RedirectToAction(nameof(Index), new RouteValueDictionary() { ["opts.category"] = category })); } }
public async Task <IActionResult> CreatePost() { var newRole = new Role(); var roleClaims = new List <RoleClaim>(); foreach (string key in Request.Form.Keys) { if (key.StartsWith("Checkbox.") && Request.Form[key] == "true") { var permissionName = key.Substring("Checkbox.".Length); roleClaims.Add(new RoleClaim { ClaimType = Permission.ClaimTypeName, ClaimValue = permissionName }); } } newRole.RoleClaims.RemoveAll(c => c.ClaimType == Permission.ClaimTypeName); newRole.RoleClaims.AddRange(roleClaims); var result = await _roleViewProvider.ProvideUpdateAsync(newRole, this); if (!ModelState.IsValid) { _alerter.Success(T["Role Created Successfully!"]); } else { foreach (var modelState in ViewData.ModelState.Values) { foreach (var error in modelState.Errors) { _alerter.Danger(T[error.ErrorMessage]); } } } return(RedirectToAction(nameof(Index))); }
// ------------ // Reset App Api Key // ------------ public async Task <IActionResult> ResetApiKey() { var settings = await _siteSettingsStore.GetAsync(); if (settings == null) { return(RedirectToAction(nameof(Index))); } settings.ApiKey = _keyGenerator.GenerateKey(); var result = await _siteSettingsStore.SaveAsync(settings); if (result != null) { _alerter.Success(T["Key Updated Successfully!"]); } else { _alerter.Danger(T["A problem occurred updating the settings. Please try again!"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Update(string id) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.UpdateFeatures)) { return(Unauthorized()); } var result = await _shellFeatureUpdater.UpdateAsync(id); if (result.Errors.Any()) { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } else { _alerter.Success(T[$"{id} Updated Successfully!"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Delete(string id) { var user = await _contextFacade.GetAuthenticatedUserAsync(); if (user == null) { return(Unauthorized()); } var ok = int.TryParse(id, out int categoryId); if (!ok) { return(NotFound()); } var file = await _fileStore.GetByIdAsync(categoryId); if (file == null) { return(NotFound()); } var deletePermission = file.CreatedUserId == user.Id ? Permissions.DeleteOwnFiles : Permissions.DeleteAnyFile; // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, deletePermission)) { return(Unauthorized()); } var result = await _fileManager.DeleteAsync(file); if (result.Succeeded) { _alerter.Success(T["File Deleted Successfully"]); } else { _alerter.Danger(T["Could not delete the file"]); } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Delete(string id) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.DeleteTenants)) { return(Unauthorized()); } // Ensure host if (!_shellSettings.IsDefaultShell()) { return(Unauthorized()); } // Get shell var shell = GetShell(WebUtility.UrlDecode(id)); // Ensure the shell exists if (shell == null) { return(NotFound()); } // Attempt to delete the shell var result = await _setUpService.UninstallAsync(shell); // Redirect to success if (result.Succeeded) { _alerter.Success(T["Tenant Deleted Successfully"]); return(RedirectToAction(nameof(Index))); } // Display errors foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } return(RedirectToAction(nameof(Edit), new RouteValueDictionary() { ["id"] = id })); }
public async Task <IActionResult> IndexPost(ReportOptions opts, PagerOptions pager) { // Default options if (opts == null) { opts = new ReportOptions(); } // Default pager if (pager == null) { pager = new PagerOptions(); } // Build view model var viewModel = await GetIndexViewModelAsync(opts, pager); // Add view model to context HttpContext.Items[typeof(ReportIndexViewModel <Metric>)] = viewModel; // Execute view providers await _pageViewsViewProvider.ProvideUpdateAsync(new PageViewIndex(), this); // May have been invalidated by a view provider if (!ModelState.IsValid) { // if we reach this point some view model validation // failed within a view provider, display model state errors foreach (var modelState in ViewData.ModelState.Values) { foreach (var error in modelState.Errors) { _alerter.Danger(T[error.ErrorMessage]); } } } return(await Index(opts, new PagerOptions() { Page = 1 })); }
public async Task <IActionResult> IndexPost() { // Execute view providers await _viewProvider.ProvideUpdateAsync(new AdminIndex(), this); if (!ModelState.IsValid) { // if we reach this point some view model validation // failed within a view provider, display model state errors foreach (var modelState in ViewData.ModelState.Values) { foreach (var error in modelState.Errors) { _alerter.Danger(T[error.ErrorMessage]); } } } return(await Index()); }
public async Task <IActionResult> Index(EditNotificationsViewModel model) { var user = await _userManager.FindByIdAsync(model.Id.ToString()); if (user == null) { return(NotFound()); } var editProfileViewModel = new EditNotificationsViewModel() { Id = user.Id }; // Build view await _editProfileViewProvider.ProvideUpdateAsync(editProfileViewModel, this); // Ensure model state is still valid after view providers have executed if (ModelState.IsValid) { _alerter.Success(T["Notifications Updated Successfully!"]); return(RedirectToAction(nameof(Index))); } // if we reach this point some view model validation // failed within a view provider, display model state errors foreach (var modelState in ViewData.ModelState.Values) { foreach (var error in modelState.Errors) { _alerter.Danger(T[error.ErrorMessage]); } } return(await Index()); }
public async Task <IActionResult> Delete(string id) { var user = await _contextFacade.GetAuthenticatedUserAsync(); if (user == null) { return(Unauthorized()); } var ok = int.TryParse(id, out var categoryId); if (!ok) { return(NotFound()); } var currentLabel = await _labelStore.GetByIdAsync(categoryId); if (currentLabel == null) { return(NotFound()); } var result = await _labelManager.DeleteAsync(currentLabel); if (result.Succeeded) { _alerter.Success(T["Label Deleted Successfully"]); } else { _alerter.Danger(T["Could not delete the label"]); } return(RedirectToAction(nameof(Index))); }
// ----------------- // AddSpammer // ----------------- public async Task <IActionResult> AddSpammer(EntityOptions opts) { // Disable functionality within demo mode if (_platoOpts.DemoMode) { return(Unauthorized()); } // Empty options if (opts == null) { opts = new EntityOptions(); } // Validate if (opts.Id <= 0) { throw new ArgumentOutOfRangeException(nameof(opts.Id)); } // Get entity var entity = await _entityStore.GetByIdAsync(opts.Id); // Ensure the topic exists if (entity == null) { return(NotFound()); } // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, entity.CategoryId, Permissions.AddToStopForumSpam)) { return(Unauthorized()); } // Get reply IEntityReply reply = null; if (opts.ReplyId > 0) { reply = await _entityReplyStore.GetByIdAsync(opts.ReplyId); } // Get user for reply or entity var user = reply != null ? await GetUserToValidateAsync(reply) : await GetUserToValidateAsync(entity); // Ensure we found the user if (user == null) { return(NotFound()); } // Add spammer for reply or entity var result = await AddSpammerAsync(user); // Confirmation if (result.Success) { _alerter.Success(T["Spammer Added Successfully"]); } else { _alerter.Danger(!string.IsNullOrEmpty(result.Error) ? T[result.Error] : T["An unknown error occurred adding the user to the StopForumSpam database."]); } // Redirect back to reply if (reply != null) { return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Docs", ["controller"] = "Home", ["action"] = "Reply", ["opts.id"] = entity.Id, ["opts.alias"] = entity.Alias, ["opts.replyId"] = reply.Id }))); } // Redirect back to entity return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Docs", ["controller"] = "Home", ["action"] = "Display", ["opts.id"] = entity.Id, ["opts.alias"] = entity.Alias }))); }
public async Task <IActionResult> ToAnswer(string id) { // Get authenticated user var user = await _contextFacade.GetAuthenticatedUserAsync(); // We need to be authenticated if (user == null) { return(Unauthorized()); } // Ensure we have a valid id var ok = int.TryParse(id, out var replyId); if (!ok) { return(NotFound()); } var reply = await _entityReplyStore.GetByIdAsync(replyId); if (reply == null) { return(NotFound()); } var entity = await _entityStore.GetByIdAsync(reply.EntityId); // Ensure the entity exists if (entity == null) { return(NotFound()); } // Get permission var permission = entity.CreatedUserId == user.Id ? Permissions.MarkOwnRepliesAnswer : Permissions.MarkAnyReplyAnswer; // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, entity.CategoryId, permission)) { return(Unauthorized()); } // Update reply reply.ModifiedUserId = user?.Id ?? 0; reply.ModifiedDate = DateTimeOffset.UtcNow; reply.IsAnswer = true; // Save changes and return results var result = await _replyManager.UpdateAsync(reply); if (result.Succeeded) { await UpdateEntityAsync(entity); _alerter.Success(T["Marked As Answer Successfully"]); } else { _alerter.Danger(T["Could not mark the reply as an answer"]); } // Redirect back to reply return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Questions", ["controller"] = "Home", ["action"] = "Reply", ["opts.id"] = entity.Id, ["opts.alias"] = entity.Alias, ["opts.replyId"] = reply.Id }))); }
public async Task <IActionResult> InstallEntities() { var result = await _sampleEntitiesService.InstallAsync(); if (result.Succeeded) { // Add alert _alerter.Success(T["Entities Added Successfully!"]); // Redirect to success return(RedirectToAction(nameof(Index))); } // If we reach this point something went wrong foreach (var error in result.Errors) { // Add errors _alerter.Danger(T[error.Description]); } // And redirect to display return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Delete(string id) { // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, Permissions.DeleteUsers)) { return(Unauthorized()); } // Ensure the user exists var user = await _userManager.FindByIdAsync(id); if (user == null) { return(NotFound()); } // Delete var result = await _platoUserManager.DeleteAsync(user); if (result.Succeeded) { _alerter.Success(T["User Deleted Successfully"]); } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } return(RedirectToAction(nameof(Index))); }
public async Task <IActionResult> Rollback(int id) { // Validate if (id <= 0) { throw new ArgumentOutOfRangeException(nameof(id)); } // Get history point var history = await _entityHistoryStore.GetByIdAsync(id); // Ensure we found the history point if (history == null) { return(NotFound()); } // Get entity for history point var entity = await _entityStore.GetByIdAsync(history.EntityId); // Ensure we found the entity if (entity == null) { return(NotFound()); } // Get reply IdeaComment reply = null; if (history.EntityReplyId > 0) { reply = await _entityReplyStore.GetByIdAsync(history.EntityReplyId); // Ensure we found a reply if supplied if (reply == null) { return(NotFound()); } } // Get current user var user = await _contextFacade.GetAuthenticatedUserAsync(); // We always need to be logged in to edit entities if (user == null) { return(Unauthorized()); } // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(HttpContext.User, entity.CategoryId, reply != null ? Permissions.RevertReplyHistory : Permissions.RevertEntityHistory)) { return(Unauthorized()); } ICommandResultBase result; if (reply != null) { // Only update edited information if the message changes if (history.Message != reply.Message) { reply.Message = history.Message; reply.EditedUserId = user?.Id ?? 0; reply.EditedDate = DateTimeOffset.UtcNow; } // Update reply to history point result = await _entityReplyManager.UpdateAsync(reply); } else { // Only update edited information if the message changes if (history.Message != entity.Message) { entity.Message = history.Message; entity.EditedUserId = user?.Id ?? 0; entity.EditedDate = DateTimeOffset.UtcNow; } // Update entity to history point result = await _entityManager.UpdateAsync(entity); } // Add result if (result.Succeeded) { _alerter.Success(T["Version Rolled Back Successfully!"]); } else { foreach (var error in result.Errors) { _alerter.Danger(T[error.Description]); } } // Redirect return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Ideas", ["controller"] = "Home", ["action"] = "Reply", ["opts.id"] = entity.Id, ["opts.alias"] = entity.Alias, ["opts.replyId"] = reply?.Id ?? 0 }))); }
public async Task <IActionResult> ShareFileAttachment(ShareFileViewModel model) { var email = model.AttachmentEmail?.Trim() ?? string.Empty; // Ensure we have an email to share with if (string.IsNullOrEmpty(email)) { // Add alert _alerter.Danger(T["An email address is required!"]); // Redirect back to file return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Files", ["controller"] = "Admin", ["action"] = "Edit", ["id"] = model.FileId }))); } // Get current user var user = await _contextFacade.GetAuthenticatedUserAsync(); // We need to be authenticated to add the invite if (user == null) { return(Unauthorized()); } // Create the invite var invite = await _fileInviteStore.CreateAsync(new FileInvite() { FileId = model.FileId, Email = email, CreatedUserId = user.Id, CreatedDate = DateTimeOffset.Now }); // Share the invite if (invite != null) { var result = await _shareInviteService.SendAttachmentInviteAsync(invite); if (result.Succeeded) { _alerter.Success(T["File Shared Successfully!"]); } else { foreach (var error in result.Errors) { if (!string.IsNullOrEmpty(error.Description)) { _alerter.Danger(T[error.Description]); } } } } // Redirect back to file return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Files", ["controller"] = "Admin", ["action"] = "Edit", ["id"] = model.FileId }))); }
public async Task <IActionResult> CreatePost(EditModeratorViewModel viewModel) { // Build users to effect var users = new List <User>(); if (!String.IsNullOrEmpty(viewModel.Users)) { var items = JsonConvert.DeserializeObject <IEnumerable <UserApiResult> >(viewModel.Users); foreach (var item in items) { if (item.Id > 0) { var user = await _userStore.GetByIdAsync(item.Id); if (user != null) { users.Add(user); } } } } var userId = 0; foreach (var user in users) { userId = user.Id; } var moderator = new Moderator() { UserId = userId }; // Validate model state within all involved view providers if (await _viewProvider.IsModelStateValidAsync(moderator, this)) { // Get composed type from all involved view providers var model = await _viewProvider.ComposeModelAsync(moderator, this); // Create moderator var result = await _moderatorStore.CreateAsync(model); if (result != null) { // Update moderator within various view providers await _viewProvider.ProvideUpdateAsync(result, this); // Everything was OK _alerter.Success(T["Moderator Created Successfully!"]); // Redirect to topic return(RedirectToAction(nameof(Index), new { Id = 0 })); } } else { _alerter.Danger(T["You must specify at least 1 user!"]); } return(await Create()); //if (users.Count > 0) //{ // // Compose moderator from all involved view providers // // This ensures the claims are always populated // var composedModerator = await _viewProvider.GetComposedType(this); // var isValid = false; // //// Update each user // //foreach (var user in users) // //{ // // composedModerator.UserId = user.Id; // // // Validate model state within all view providers // // if (await _viewProvider.IsModelStateValid(composedModerator, this)) // // { // // // Create moderator // // var result = await _moderatorStore.CreateAsync(composedModerator); // // if (result != null) // // { // // // Update moderator within various view providers // // await _viewProvider.ProvideUpdateAsync(result, this); // // isValid = true; // // } // // } // //} // //if (isValid) // //{ // // // Everything was OK // // _alerter.Success(T["Moderator Created Successfully!"]); // // // Redirect to topic // // return RedirectToAction(nameof(Index), new {Id = 0}); // //} // // if we reach this point some view model validation // // failed within a view provider, display model state errors // foreach (var modelState in ViewData.ModelState.Values) // { // foreach (var error in modelState.Errors) // { // _alerter.Danger(T[error.ErrorMessage]); // } // } // return await Create(); //} //else //{ // _alerter.Danger(T["You must specify at least 1 user!"]); //} //return await Create(); }
public async Task <IActionResult> Pin(string id) { // Ensure we have a valid id var ok = int.TryParse(id, out var entityId); if (!ok) { return(NotFound()); } var topic = await _entityStore.GetByIdAsync(entityId); // Ensure the topic exists if (topic == null) { return(NotFound()); } // Ensure we have permission if (!await _authorizationService.AuthorizeAsync(User, topic.CategoryId, ModeratorPermissions.PinTopics)) { return(Unauthorized()); } var user = await _contextFacade.GetAuthenticatedUserAsync(); // Update topic topic.ModifiedUserId = user?.Id ?? 0; topic.ModifiedDate = DateTimeOffset.UtcNow; topic.IsPinned = true; // Save changes and return results var result = await _topicManager.UpdateAsync(topic); if (result.Succeeded) { _alerter.Success(T["Topic Pinned Successfully"]); } else { _alerter.Danger(T["Could not remove topic from SPAM"]); } // Redirect back to topic return(Redirect(_contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = "Plato.Discuss", ["controller"] = "Home", ["action"] = "Display", ["opts.id"] = topic.Id, ["opts.alias"] = topic.Alias }))); }