From bbd55ed8b331e9471c6797ddfede67b5f6b415a5 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Sat, 30 Mar 2024 11:42:29 +0700 Subject: [PATCH] Added Accounts page --- .../picker/cp/component/AccountList.java | 190 ++++++++++++++++++ .../picker/cp/model/Permission.java | 16 ++ .../picker/cp/page/AccountsPage.java | 29 +++ .../picker/cp/page/MainLayout.java | 3 + 4 files changed, 238 insertions(+) create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/component/AccountList.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/AccountsPage.java diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AccountList.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AccountList.java new file mode 100644 index 0000000..241f059 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AccountList.java @@ -0,0 +1,190 @@ +package ru.dragonestia.picker.cp.component; + +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.grid.ColumnTextAlign; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H3; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.PasswordField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.value.ValueChangeMode; +import com.vaadin.flow.theme.lumo.LumoIcon; +import ru.dragonestia.picker.api.model.account.ResponseAccount; +import ru.dragonestia.picker.api.repository.AccountRepository; +import ru.dragonestia.picker.cp.model.Permission; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public class AccountList extends VerticalLayout implements RefreshableTable { + + private final AccountRepository accountRepository; + private final TextField searchField; + private final Grid grid; + + private List cachedAccounts = new ArrayList<>(); + + public AccountList(AccountRepository accountRepository) { + this.accountRepository = accountRepository; + + add(searchField = createSearchField()); + add(grid = createGridAccounts()); + + refresh(); + } + + private TextField createSearchField() { + var field = new TextField("Search by account username"); + field.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + field.setClearButtonVisible(true); + field.setHelperText("Press Enter to search"); + field.addValueChangeListener(event -> applySearch(event.getValue())); + field.setValueChangeMode(ValueChangeMode.EAGER); + field.setMinWidth(30, Unit.PERCENTAGE); + return field; + } + + private Grid createGridAccounts() { + var grid = new Grid<>(ResponseAccount.class, false); + + grid.addColumn(ResponseAccount::getUsername).setHeader("Username") + .setComparator(Comparator.comparing(ResponseAccount::getUsername)).setSortable(true); + + grid.addComponentColumn(this::createAccountManagementButtons).setFrozenToEnd(true) + .setTextAlign(ColumnTextAlign.END).setHeader(createToolItems()); + + grid.setMultiSort(true, Grid.MultiSortPriority.APPEND); + + return grid; + } + + private HorizontalLayout createAccountManagementButtons(ResponseAccount account) { + var layout = new HorizontalLayout(JustifyContentMode.END); + + { + var button = new Button("Manage", VaadinIcon.COG_O.create()); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + button.addClickListener(event -> { + + }); + layout.add(button); + } + + return layout; + } + + private HorizontalLayout createToolItems() { + var layout = new HorizontalLayout(JustifyContentMode.END); + + layout.add(createRegisterAccountButton()); + layout.add(createRefreshButton()); + + return layout; + } + + private Button createRegisterAccountButton() { + var button = new Button("Register account", VaadinIcon.PLUS.create()); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS); + button.addClickListener(event -> openAccountRegistrationDialog()); + return button; + } + + @Override + public void refresh() { + var list = new ArrayList(); + for (int i = 0; i < 5; i++) { + var acc = new ResponseAccount("test" + i, "", Set.of(), false); + list.add(acc); + } + + cachedAccounts = list; // TODO: accountRepository.getAllAccounts(); + applySearch(searchField.getValue()); + } + + public void applySearch(String input) { + var temp = input.trim(); + + grid.setItems(cachedAccounts.stream() + .filter(account -> account.getUsername().startsWith(temp)) + .toList()); + } + + private void openAccountRegistrationDialog() { + var dialog = new Dialog(); + dialog.setHeaderTitle("Register new account"); + + var closeButton = new Button(LumoIcon.CROSS.create(), e -> dialog.close()); + closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + dialog.getHeader().add(closeButton); + + var layout = new VerticalLayout(); + + var fieldUsername = new TextField("Account username"); + fieldUsername.setWidth(70, Unit.PERCENTAGE); + layout.add(fieldUsername); + + var fieldPassword = new PasswordField("Password"); + fieldPassword.setWidth(70, Unit.PERCENTAGE); + layout.add(fieldPassword); + + var fieldConfirmPassword = new PasswordField("Confirm password"); + fieldConfirmPassword.setWidth(70, Unit.PERCENTAGE); + layout.add(fieldConfirmPassword); + + layout.add(new H3("Permissions")); + + var permissionsList = new ArrayList(); + for (var permission: Permission.Enum.values()) { + var comp = new PermissionCheckBox(permission); + permissionsList.add(comp); + layout.add(comp); + } + + { + var button = new Button("Register"); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + button.setWidth(100, Unit.PERCENTAGE); + button.addClickListener(event -> { + validateAndRegister(dialog, fieldUsername, fieldPassword, fieldConfirmPassword, permissionsList); + }); + dialog.getFooter().add(button); + } + + dialog.add(layout); + dialog.setMinWidth(50, Unit.PERCENTAGE); + dialog.setCloseOnEsc(true); + dialog.setCloseOnOutsideClick(true); + + dialog.open(); + } + + private void validateAndRegister(Dialog dialog, TextField username, PasswordField passwordField, PasswordField confirm, List permissionCheckBoxes) { + // TODO: validate and send request + + dialog.close(); + refresh(); + } + + private static class PermissionCheckBox extends Checkbox { + + private final Permission.Enum option; + + public PermissionCheckBox(Permission.Enum option) { + super(option.getDescription()); + this.option = option; + } + + public Permission.Enum getOption() { + return option; + } + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Permission.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Permission.java index 97499ff..e636e92 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Permission.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Permission.java @@ -1,6 +1,7 @@ package ru.dragonestia.picker.cp.model; import com.github.javaparser.quality.NotNull; +import lombok.Getter; import org.springframework.security.core.GrantedAuthority; public class Permission implements GrantedAuthority { @@ -15,4 +16,19 @@ public class Permission implements GrantedAuthority { public String getAuthority() { return authority; } + + @Getter + public enum Enum { + // All from ru.dragonestia.picker.model.Permission (server) + // Except for USER and ADMIN + + NODE_MANAGEMENT("Create and remove nodes"), + ; + + private final String description; + + Enum(String description) { + this.description = description; + } + } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/AccountsPage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/AccountsPage.java new file mode 100644 index 0000000..9e7ef2a --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/AccountsPage.java @@ -0,0 +1,29 @@ +package ru.dragonestia.picker.cp.page; + +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import jakarta.annotation.security.RolesAllowed; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import ru.dragonestia.picker.api.repository.AccountRepository; +import ru.dragonestia.picker.cp.component.AccountList; +import ru.dragonestia.picker.cp.service.SecurityService; + +@RolesAllowed("ADMIN") +@PageTitle("Accounts") +@Route(value = "/admin/accounts", layout = MainLayout.class) +@RequiredArgsConstructor +public class AccountsPage extends VerticalLayout { + + private final AccountRepository accountRepository; + + @Autowired + public AccountsPage(SecurityService securityService) { + accountRepository = securityService.getAuthenticatedAccount().getClient().getAccountRepository(); + + add(new H2("Account management")); + add(new AccountList(accountRepository)); + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java index 6abde1b..9435d1b 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java @@ -74,6 +74,9 @@ public class MainLayout extends AppLayout { var nav = new SideNav(); nav.addItem(new SideNavItem("Nodes list", NodesPage.class, VaadinIcon.FOLDER_O.create())); nav.addItem(new SideNavItem("Search users", UserSearchPage.class, VaadinIcon.SEARCH.create())); + if (isAdmin) { + nav.addItem(new SideNavItem("Accounts", AccountsPage.class, VaadinIcon.USERS.create())); + } nav.addItem(new SideNavItem("Documentation", "https://github.com/ScarletRedMan/RoomPicker", VaadinIcon.BOOK.create())); nav.addItem(new SideNavItem("Swagger UI", serverUrl + "/api-docs-ui", VaadinIcon.CURLY_BRACKETS.create())); return nav;