refactored control panel
This commit is contained in:
parent
0c19050436
commit
93226022b2
@ -30,7 +30,6 @@ dependencies {
|
|||||||
implementation project(":client-impl")
|
implementation project(":client-impl")
|
||||||
|
|
||||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|||||||
@ -17,23 +17,25 @@ import com.vaadin.flow.component.textfield.PasswordField;
|
|||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||||
import com.vaadin.flow.theme.lumo.LumoIcon;
|
import com.vaadin.flow.theme.lumo.LumoIcon;
|
||||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
import ru.dragonestia.picker.api.repository.AccountRepository;
|
import ru.dragonestia.picker.api.model.account.Account;
|
||||||
import ru.dragonestia.picker.cp.model.Permission;
|
import ru.dragonestia.picker.api.model.account.AccountId;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
import ru.dragonestia.picker.cp.util.PermissionDescription;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class AccountList extends VerticalLayout implements RefreshableTable {
|
public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||||
|
|
||||||
private final AccountRepository accountRepository;
|
private final RoomPickerClient client;
|
||||||
private final TextField searchField;
|
private final TextField searchField;
|
||||||
private final Grid<ResponseAccount> grid;
|
private final Grid<Account> grid;
|
||||||
|
|
||||||
private List<ResponseAccount> cachedAccounts = new ArrayList<>();
|
private List<Account> cachedAccounts = new ArrayList<>();
|
||||||
|
|
||||||
public AccountList(AccountRepository accountRepository) {
|
public AccountList(RoomPickerClient client) {
|
||||||
this.accountRepository = accountRepository;
|
this.client = client;
|
||||||
|
|
||||||
add(searchField = createSearchField());
|
add(searchField = createSearchField());
|
||||||
add(grid = createGridAccounts());
|
add(grid = createGridAccounts());
|
||||||
@ -42,7 +44,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TextField createSearchField() {
|
private TextField createSearchField() {
|
||||||
var field = new TextField("Search by account username");
|
var field = new TextField("Search by account entityname");
|
||||||
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
||||||
field.setClearButtonVisible(true);
|
field.setClearButtonVisible(true);
|
||||||
field.setHelperText("Press Enter to search");
|
field.setHelperText("Press Enter to search");
|
||||||
@ -52,11 +54,11 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid<ResponseAccount> createGridAccounts() {
|
private Grid<Account> createGridAccounts() {
|
||||||
var grid = new Grid<>(ResponseAccount.class, false);
|
var grid = new Grid<>(Account.class, false);
|
||||||
|
|
||||||
grid.addColumn(ResponseAccount::getUsername).setHeader("Username")
|
grid.addColumn(Account::id).setHeader("Username")
|
||||||
.setComparator(Comparator.comparing(ResponseAccount::getUsername)).setSortable(true);
|
.setComparator(Comparator.comparing(account -> account.id().getValue())).setSortable(true);
|
||||||
|
|
||||||
grid.addComponentColumn(this::createAccountManagementButtons).setFrozenToEnd(true)
|
grid.addComponentColumn(this::createAccountManagementButtons).setFrozenToEnd(true)
|
||||||
.setTextAlign(ColumnTextAlign.END).setHeader(createToolItems());
|
.setTextAlign(ColumnTextAlign.END).setHeader(createToolItems());
|
||||||
@ -66,7 +68,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HorizontalLayout createAccountManagementButtons(ResponseAccount account) {
|
private HorizontalLayout createAccountManagementButtons(Account account) {
|
||||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -74,7 +76,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
getUI().ifPresent(ui -> {
|
getUI().ifPresent(ui -> {
|
||||||
ui.navigate("/admin/accounts/" + account.getUsername());
|
ui.navigate("/admin/accounts/" + account.id());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
layout.add(button);
|
layout.add(button);
|
||||||
@ -101,7 +103,8 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
cachedAccounts = accountRepository.allAccounts();
|
var ids = client.getAccountRepository().allAccountsIds();
|
||||||
|
cachedAccounts = client.getAccountRepository().getAccounts(ids);
|
||||||
applySearch(searchField.getValue());
|
applySearch(searchField.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +112,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
var temp = input.trim();
|
var temp = input.trim();
|
||||||
|
|
||||||
grid.setItems(cachedAccounts.stream()
|
grid.setItems(cachedAccounts.stream()
|
||||||
.filter(account -> account.getUsername().startsWith(temp))
|
.filter(account -> account.id().getValue().startsWith(temp))
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +126,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
|
|
||||||
var layout = new VerticalLayout();
|
var layout = new VerticalLayout();
|
||||||
|
|
||||||
var fieldUsername = new TextField("Account username");
|
var fieldEntityname = new TextField("Username");
|
||||||
fieldUsername.setWidth(70, Unit.PERCENTAGE);
|
fieldEntityname.setWidth(70, Unit.PERCENTAGE);
|
||||||
layout.add(fieldUsername);
|
layout.add(fieldEntityname);
|
||||||
|
|
||||||
var fieldPassword = new PasswordField("Password");
|
var fieldPassword = new PasswordField("Password");
|
||||||
fieldPassword.setWidth(70, Unit.PERCENTAGE);
|
fieldPassword.setWidth(70, Unit.PERCENTAGE);
|
||||||
@ -138,7 +141,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
layout.add(new H3("Permissions"));
|
layout.add(new H3("Permissions"));
|
||||||
|
|
||||||
var permissionsList = new ArrayList<PermissionCheckBox>();
|
var permissionsList = new ArrayList<PermissionCheckBox>();
|
||||||
for (var permission: Permission.Enum.values()) {
|
for (var permission: Permission.values()) {
|
||||||
|
if (permission == Permission.ADMIN) continue;
|
||||||
|
|
||||||
var comp = new PermissionCheckBox(permission);
|
var comp = new PermissionCheckBox(permission);
|
||||||
permissionsList.add(comp);
|
permissionsList.add(comp);
|
||||||
layout.add(comp);
|
layout.add(comp);
|
||||||
@ -149,7 +154,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
button.setWidth(100, Unit.PERCENTAGE);
|
button.setWidth(100, Unit.PERCENTAGE);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
validateAndRegister(dialog, fieldUsername, fieldPassword, fieldConfirmPassword, permissionsList);
|
validateAndRegister(dialog, fieldEntityname, fieldPassword, fieldConfirmPassword, permissionsList);
|
||||||
});
|
});
|
||||||
dialog.getFooter().add(button);
|
dialog.getFooter().add(button);
|
||||||
}
|
}
|
||||||
@ -162,13 +167,13 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateAndRegister(Dialog dialog, TextField usernameField, PasswordField passwordField, PasswordField confirmPasswordField, List<PermissionCheckBox> permissionCheckBoxes) {
|
private void validateAndRegister(Dialog dialog, TextField entitynameField, PasswordField passwordField, PasswordField confirmPasswordField, List<PermissionCheckBox> permissionCheckBoxes) {
|
||||||
var username = usernameField.getValue().trim();
|
var entityname = entitynameField.getValue().trim();
|
||||||
var password = passwordField.getValue();
|
var password = passwordField.getValue();
|
||||||
var confirmPassword = confirmPasswordField.getValue();
|
var confirmPassword = confirmPasswordField.getValue();
|
||||||
|
|
||||||
if (username.length() < 3 || username.length() > 32) {
|
if (entityname.length() < 3 || entityname.length() > 32) {
|
||||||
Notifications.error("Invalid username length. Valid is 3-32");
|
Notifications.error("Invalid entityname length. Valid is 3-32");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,10 +190,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
var permissions = permissionCheckBoxes.stream()
|
var permissions = permissionCheckBoxes.stream()
|
||||||
.filter(AbstractField::getValue)
|
.filter(AbstractField::getValue)
|
||||||
.map(PermissionCheckBox::getOption)
|
.map(PermissionCheckBox::getOption)
|
||||||
.map(Enum::name)
|
.toList();
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
accountRepository.createAccount(username, password, permissions);
|
client.getAccountRepository().createAccount(AccountId.of(entityname), password, permissions);
|
||||||
|
|
||||||
dialog.close();
|
dialog.close();
|
||||||
refresh();
|
refresh();
|
||||||
@ -196,14 +200,14 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
|||||||
|
|
||||||
public static class PermissionCheckBox extends Checkbox {
|
public static class PermissionCheckBox extends Checkbox {
|
||||||
|
|
||||||
private final Permission.Enum option;
|
private final Permission option;
|
||||||
|
|
||||||
public PermissionCheckBox(Permission.Enum option) {
|
public PermissionCheckBox(Permission option) {
|
||||||
super(option.getDescription());
|
super(PermissionDescription.of(option));
|
||||||
this.option = option;
|
this.option = option;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Permission.Enum getOption() {
|
public Permission getOption() {
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
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.details.Details;
|
||||||
|
import com.vaadin.flow.component.html.Div;
|
||||||
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.model.entity.EntityId;
|
||||||
|
import ru.dragonestia.picker.api.model.room.Room;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class AddEntities extends Details {
|
||||||
|
|
||||||
|
private final BiConsumer<Collection<EntityId>, Boolean> onCommit;
|
||||||
|
private final Checkbox ignoreSlots;
|
||||||
|
private final VerticalLayout entitiesLayout;
|
||||||
|
private final AtomicInteger freeEntityIdNumber = new AtomicInteger(1);
|
||||||
|
|
||||||
|
public AddEntities(Room room, BiConsumer<Collection<EntityId>, Boolean> onCommit) {
|
||||||
|
super(new H2("Add entities"));
|
||||||
|
|
||||||
|
this.onCommit = onCommit;
|
||||||
|
entitiesLayout = new VerticalLayout();
|
||||||
|
|
||||||
|
add(addEntityToTransacionButton());
|
||||||
|
add(entitiesLayout);
|
||||||
|
entitiesLayout.add(new EntityEntry(false, freeEntityIdNumber.getAndIncrement()));
|
||||||
|
add(ignoreSlots = new Checkbox("Ignore slot limitation", false));
|
||||||
|
add(createAddEntitiesButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
freeEntityIdNumber.set(1);
|
||||||
|
ignoreSlots.setValue(false);
|
||||||
|
entitiesLayout.removeAll();
|
||||||
|
entitiesLayout.add(new EntityEntry(false, freeEntityIdNumber.getAndIncrement()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<EntityId> readAllEntities() {
|
||||||
|
return entitiesLayout.getChildren()
|
||||||
|
.filter(component -> component instanceof EntityEntry)
|
||||||
|
.map(component -> (EntityEntry) component)
|
||||||
|
.map(entity -> entity.getEntityIdentifierField().getValue())
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(entity -> !entity.isEmpty())
|
||||||
|
.map(EntityId::of)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button addEntityToTransacionButton() {
|
||||||
|
var button = new Button("Add entity to transaction");
|
||||||
|
button.addClickListener(event -> {
|
||||||
|
entitiesLayout.add(new EntityEntry(true, freeEntityIdNumber.getAndIncrement()));
|
||||||
|
});
|
||||||
|
button.setPrefixComponent(new Icon(VaadinIcon.PLUS));
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createAddEntitiesButton() {
|
||||||
|
var button = new Button("Commit", event -> onClick());
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_SUCCESS, ButtonVariant.LUMO_PRIMARY);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClick() {
|
||||||
|
try {
|
||||||
|
onCommit.accept(readAllEntities(), ignoreSlots.getValue());
|
||||||
|
} catch (Error error) {
|
||||||
|
Notifications.error(error.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class EntityEntry extends Div {
|
||||||
|
|
||||||
|
private final TextField entityIdentifierField;
|
||||||
|
|
||||||
|
public EntityEntry(boolean canBeDeleted, int number) {
|
||||||
|
add(entityIdentifierField = createEntityIdentifierField(canBeDeleted, number));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField createEntityIdentifierField(boolean canBeDeleted, int number) {
|
||||||
|
var field = new TextField("Entity id");
|
||||||
|
field.setPlaceholder("example-entity-id-" + number);
|
||||||
|
if (!canBeDeleted) {
|
||||||
|
field.setHelperText("It can be UUID, entityname, numeric ids, etc");
|
||||||
|
}
|
||||||
|
field.setMinWidth(20, Unit.REM);
|
||||||
|
|
||||||
|
if (canBeDeleted) {
|
||||||
|
var removeButton = new Button(new Icon(VaadinIcon.TRASH), event -> {
|
||||||
|
removeFromParent();
|
||||||
|
});
|
||||||
|
removeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
|
|
||||||
|
field.setSuffixComponent(removeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,117 +0,0 @@
|
|||||||
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.details.Details;
|
|
||||||
import com.vaadin.flow.component.html.Div;
|
|
||||||
import com.vaadin.flow.component.html.H2;
|
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
|
||||||
import lombok.Getter;
|
|
||||||
import ru.dragonestia.picker.api.model.room.IRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.model.user.UserDefinition;
|
|
||||||
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
public class AddUsers extends Details {
|
|
||||||
|
|
||||||
private final BiConsumer<Collection<IUser>, Boolean> onCommit;
|
|
||||||
private final Checkbox ignoreSlots;
|
|
||||||
private final VerticalLayout usersLayout;
|
|
||||||
private final AtomicInteger freeUserIdNumber = new AtomicInteger(1);
|
|
||||||
|
|
||||||
public AddUsers(IRoom room, BiConsumer<Collection<IUser>, Boolean> onCommit) {
|
|
||||||
super(new H2("Add users"));
|
|
||||||
|
|
||||||
this.onCommit = onCommit;
|
|
||||||
usersLayout = new VerticalLayout();
|
|
||||||
|
|
||||||
add(addUserToTransacionButton());
|
|
||||||
add(usersLayout);
|
|
||||||
usersLayout.add(new UserEntry(false, freeUserIdNumber.getAndIncrement()));
|
|
||||||
add(ignoreSlots = new Checkbox("Ignore slot limitation", false));
|
|
||||||
add(createAddUsersButton());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
freeUserIdNumber.set(1);
|
|
||||||
ignoreSlots.setValue(false);
|
|
||||||
usersLayout.removeAll();
|
|
||||||
usersLayout.add(new UserEntry(false, freeUserIdNumber.getAndIncrement()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IUser> readAllUsers() {
|
|
||||||
return usersLayout.getChildren()
|
|
||||||
.filter(component -> component instanceof UserEntry)
|
|
||||||
.map(component -> (UserEntry) component)
|
|
||||||
.map(user -> user.getUserIdentifierField().getValue())
|
|
||||||
.map(String::trim)
|
|
||||||
.filter(user -> !user.isEmpty())
|
|
||||||
.map(id -> (IUser) new UserDefinition(EntityIdentifier.of(id)))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Button addUserToTransacionButton() {
|
|
||||||
var button = new Button("Add user to transaction");
|
|
||||||
button.addClickListener(event -> {
|
|
||||||
usersLayout.add(new UserEntry(true, freeUserIdNumber.getAndIncrement()));
|
|
||||||
});
|
|
||||||
button.setPrefixComponent(new Icon(VaadinIcon.PLUS));
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Button createAddUsersButton() {
|
|
||||||
var button = new Button("Commit", event -> onClick());
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_SUCCESS, ButtonVariant.LUMO_PRIMARY);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onClick() {
|
|
||||||
try {
|
|
||||||
onCommit.accept(readAllUsers(), ignoreSlots.getValue());
|
|
||||||
} catch (Error error) {
|
|
||||||
Notifications.error(error.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class UserEntry extends Div {
|
|
||||||
|
|
||||||
private final TextField userIdentifierField;
|
|
||||||
|
|
||||||
public UserEntry(boolean canBeDeleted, int number) {
|
|
||||||
add(userIdentifierField = createUserIdentifierField(canBeDeleted, number));
|
|
||||||
}
|
|
||||||
|
|
||||||
private TextField createUserIdentifierField(boolean canBeDeleted, int number) {
|
|
||||||
var field = new TextField("User id");
|
|
||||||
field.setPlaceholder("example-user-id-" + number);
|
|
||||||
if (!canBeDeleted) {
|
|
||||||
field.setHelperText("It can be UUID, username, numeric ids, etc");
|
|
||||||
}
|
|
||||||
field.setMinWidth(20, Unit.REM);
|
|
||||||
|
|
||||||
if (canBeDeleted) {
|
|
||||||
var removeButton = new Button(new Icon(VaadinIcon.TRASH), event -> {
|
|
||||||
removeFromParent();
|
|
||||||
});
|
|
||||||
removeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
|
||||||
|
|
||||||
field.setSuffixComponent(removeButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
package ru.dragonestia.picker.cp.component;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||||
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
|
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 ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
|
import ru.dragonestia.picker.api.model.entity.EntityId;
|
||||||
|
import ru.dragonestia.picker.api.model.room.Room;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.AllEntities;
|
||||||
|
import ru.dragonestia.picker.cp.util.UsingSlots;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class EntityList extends VerticalLayout implements RefreshableTable {
|
||||||
|
|
||||||
|
private final Room room;
|
||||||
|
private final RoomPickerClient client;
|
||||||
|
private final Button buttonRemove;
|
||||||
|
private final Grid<EntityDTO> entitiesGrid;
|
||||||
|
private final Span totalEntities = new Span();
|
||||||
|
private final Span occupancy = new Span();
|
||||||
|
private List<EntityDTO> cachedEntities = new ArrayList<>();
|
||||||
|
|
||||||
|
public EntityList(Room room, RoomPickerClient client) {
|
||||||
|
this.room = room;
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
buttonRemove = createButtonRemove();
|
||||||
|
add(entitiesGrid = createEntitiesGrid());
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
updateButtonRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createButtonRemove() {
|
||||||
|
var button = new Button("Unlink");
|
||||||
|
button.setPrefixComponent(new Icon(VaadinIcon.UNLINK));
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
|
button.addClickListener(event -> {
|
||||||
|
var entities = entitiesGrid.getSelectedItems();
|
||||||
|
if (entities.isEmpty()) return;
|
||||||
|
client.getEntityRepository().unlinkEntitiesFromRoom(room, entities.stream().map(entity -> EntityId.of(entity.getId())).collect(Collectors.toSet()));
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Grid<EntityDTO> createEntitiesGrid() {
|
||||||
|
var grid = new Grid<EntityDTO>();
|
||||||
|
|
||||||
|
grid.addColumn(EntityDTO::getId).setHeader("Entity Identifier").setSortable(true).setFooter(totalEntities);
|
||||||
|
|
||||||
|
grid.addColumn(EntityDTO::getCountRooms).setTextAlign(ColumnTextAlign.CENTER)
|
||||||
|
.setHeader("Linked with rooms").setComparator((entity1, entity2) -> {
|
||||||
|
var r1 = entity1.getCountRooms();
|
||||||
|
var r2 = entity2.getCountRooms();
|
||||||
|
|
||||||
|
return Integer.compare(r1, r2);
|
||||||
|
}).setSortable(true).setFooter(occupancy);
|
||||||
|
|
||||||
|
grid.addComponentColumn(this::createManageButton).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true)
|
||||||
|
.setTextAlign(ColumnTextAlign.END).setHeader(createManageTableButtons());
|
||||||
|
|
||||||
|
grid.setSelectionMode(Grid.SelectionMode.MULTI);
|
||||||
|
grid.addSelectionListener(event -> updateButtonRemove());
|
||||||
|
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createManageButton(EntityDTO entity) {
|
||||||
|
var button = new Button("Details");
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
button.addClickListener(e -> {
|
||||||
|
getUI().ifPresent(ui -> ui.navigate("/entities/" + entity.getId()));
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HorizontalLayout createManageTableButtons() {
|
||||||
|
var layout = new HorizontalLayout();
|
||||||
|
layout.setJustifyContentMode(JustifyContentMode.END);
|
||||||
|
|
||||||
|
layout.add(buttonRemove);
|
||||||
|
layout.add(createRefreshButton());
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtonRemove() {
|
||||||
|
var entities = entitiesGrid.getSelectedItems();
|
||||||
|
|
||||||
|
if (entities.isEmpty()) {
|
||||||
|
buttonRemove.setEnabled(false);
|
||||||
|
buttonRemove.setText("Unlink");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonRemove.setEnabled(true);
|
||||||
|
buttonRemove.setText("Unlink(" + entities.size() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
cachedEntities = client.getRestTemplate().executeGraphQL(AllEntities.query(room.instanceId().getValue(), room.id().getValue()))
|
||||||
|
.getRoomById().getEntities().stream().map(entity -> (EntityDTO) entity).toList();
|
||||||
|
entitiesGrid.setItems(cachedEntities);
|
||||||
|
totalEntities.setText("Total entities: " + cachedEntities.size());
|
||||||
|
occupancy.setText("Occupancy: %s".formatted(UsingSlots.getUsingPercentage(room.slots(), cachedEntities.size()) + "%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,34 +14,34 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
import ru.dragonestia.picker.api.model.node.NodeDetails;
|
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||||
import ru.dragonestia.picker.api.repository.InstanceRepository;
|
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||||
import ru.dragonestia.picker.api.repository.query.node.GetAllNodes;
|
import ru.dragonestia.picker.cp.repository.graphql.AllInstances;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class NodeList extends VerticalLayout implements RefreshableTable {
|
public class InstanceList extends VerticalLayout implements RefreshableTable {
|
||||||
|
|
||||||
private final InstanceRepository instanceRepository;
|
private final RoomPickerClient client;
|
||||||
private final Grid<INode> nodesGrid;
|
private final Grid<InstanceDTO> instancesGrid;
|
||||||
private final TextField searchField;
|
private final TextField searchField;
|
||||||
private List<INode> cachedNodes;
|
private List<InstanceDTO> cachedInstances;
|
||||||
|
|
||||||
public NodeList(InstanceRepository instanceRepository) {
|
public InstanceList(RoomPickerClient client) {
|
||||||
super();
|
this.client = client;
|
||||||
this.instanceRepository = instanceRepository;
|
|
||||||
|
|
||||||
add(new H2("Nodes"));
|
add(new H2("Instances"));
|
||||||
add(searchField = createSearchField());
|
add(searchField = createSearchField());
|
||||||
add(nodesGrid = createGrid());
|
add(instancesGrid = createGrid());
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextField createSearchField() {
|
private TextField createSearchField() {
|
||||||
var field = new TextField("Search node");
|
var field = new TextField("Search instance");
|
||||||
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
||||||
field.setClearButtonVisible(true);
|
field.setClearButtonVisible(true);
|
||||||
field.setHelperText("Press Enter to search");
|
field.setHelperText("Press Enter to search");
|
||||||
@ -53,25 +53,28 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
|||||||
private void applySearch(String input) {
|
private void applySearch(String input) {
|
||||||
var temp = input.trim();
|
var temp = input.trim();
|
||||||
|
|
||||||
nodesGrid.setItems(cachedNodes.stream()
|
var instances = cachedInstances.stream()
|
||||||
.filter(node -> node.getIdentifier().startsWith(temp))
|
.filter(instance -> instance.getId().startsWith(temp))
|
||||||
.toList());
|
.map(instance -> (InstanceDTO) instance)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
instancesGrid.setItems(instances);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid<INode> createGrid() {
|
private Grid<InstanceDTO> createGrid() {
|
||||||
var grid = new Grid<>(INode.class, false);
|
var grid = new Grid<>(InstanceDTO.class, false);
|
||||||
|
|
||||||
grid.addComponentColumn(node -> {
|
grid.addComponentColumn(instance -> {
|
||||||
if (Boolean.parseBoolean(node.getDetail(NodeDetails.PERSIST))) {
|
if (instance.isPersist()) {
|
||||||
return new Span(node.getIdentifier());
|
return new Span(instance.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new Span(node.getIdentifier());
|
var result = new Span(instance.getId());
|
||||||
result.add(grayBadge("(temp)"));
|
result.add(grayBadge("(temp)"));
|
||||||
return result;
|
return result;
|
||||||
}).setHeader("Identifier").setComparator(Comparator.comparing(INode::getIdentifier)).setSortable(true);
|
}).setHeader("Identifier").setComparator(Comparator.comparing(InstanceDTO::getId)).setSortable(true);
|
||||||
|
|
||||||
grid.addColumn(node -> node.getPickingMethod().name()).setHeader("Mode").setSortable(true);
|
grid.addColumn(instance -> instance.getMethod().name()).setHeader("Mode").setSortable(true);
|
||||||
|
|
||||||
grid.addComponentColumn(this::createManageButtons).setFrozenToEnd(true)
|
grid.addComponentColumn(this::createManageButtons).setFrozenToEnd(true)
|
||||||
.setTextAlign(ColumnTextAlign.END).setHeader(createRefreshButton());
|
.setTextAlign(ColumnTextAlign.END).setHeader(createRefreshButton());
|
||||||
@ -80,33 +83,33 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HorizontalLayout createManageButtons(INode node) {
|
private HorizontalLayout createManageButtons(InstanceDTO instance) {
|
||||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||||
|
|
||||||
{
|
{
|
||||||
var button = new Button("Details");
|
var button = new Button("Details");
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
button.addClickListener(event -> clickDetailsButton(node));
|
button.addClickListener(event -> clickDetailsButton(instance));
|
||||||
layout.add(button);
|
layout.add(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var button = new Button("Remove");
|
var button = new Button("Remove");
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
button.addClickListener(event -> clickRemoveButton(node));
|
button.addClickListener(event -> clickRemoveButton(instance));
|
||||||
layout.add(button);
|
layout.add(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickDetailsButton(INode node) {
|
private void clickDetailsButton(InstanceDTO instance) {
|
||||||
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.getIdentifier()));
|
getUI().ifPresent(ui -> ui.navigate("/instances/" + instance.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickRemoveButton(INode node) {
|
private void clickRemoveButton(InstanceDTO instance) {
|
||||||
var dialog = new Dialog("Confirm node deletion");
|
var dialog = new Dialog("Confirm instance deletion");
|
||||||
dialog.add(new Html("<p>Confirm that you want to delete node. Enter <b><u>" + node.getIdentifier() + "</u></b> to field below and confirm.</p>"));
|
dialog.add(new Html("<p>Confirm that you want to delete instance. Enter <b><u>" + instance.getId() + "</u></b> to field below and confirm.</p>"));
|
||||||
|
|
||||||
var inputField = new TextField();
|
var inputField = new TextField();
|
||||||
inputField.setWidth("100%");
|
inputField.setWidth("100%");
|
||||||
@ -116,13 +119,13 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
|||||||
var button = new Button("Confirm");
|
var button = new Button("Confirm");
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
if (!node.getIdentifier().equals(inputField.getValue())) {
|
if (!instance.getId().equals(inputField.getValue())) {
|
||||||
Notifications.error("Invalid input");
|
Notifications.error("Invalid input");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeNode(node);
|
removeInstance(instance);
|
||||||
Notifications.success("Node <b>" + node.getIdentifier() + "</b> was successfully removed!");
|
Notifications.success("Instance <b>" + instance.getId() + "</b> was successfully removed!");
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,14 +141,16 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeNode(INode node) {
|
private void removeInstance(InstanceDTO instance) {
|
||||||
instanceRepository.removeNode(node);
|
client.getInstanceRepository().deleteInstance(InstanceId.of(instance.getId()));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
cachedNodes = instanceRepository.allNodes(GetAllNodes.WITH_ALL_DETAILS);
|
cachedInstances = client.getRestTemplate().executeGraphQL(AllInstances.query()).getAllInstances().stream()
|
||||||
|
.map(instance -> (InstanceDTO) instance)
|
||||||
|
.toList();
|
||||||
applySearch(searchField.getValue());
|
applySearch(searchField.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,19 +58,19 @@ public class NavPath extends HorizontalLayout{
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NavPath rootNodes() {
|
public static NavPath rootInstances() {
|
||||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"));
|
return new NavPath(new NavPath.Point("Instances", "/instances"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NavPath toNode(String nodeId) {
|
public static NavPath toInstance(String instanceId) {
|
||||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"),
|
return new NavPath(new NavPath.Point("Instances", "/instances"),
|
||||||
new NavPath.Point(nodeId, "/nodes/" + nodeId));
|
new NavPath.Point(instanceId, "/instances/" + instanceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NavPath toRoom(String nodeId, String roomId) {
|
public static NavPath toRoom(String instanceId, String roomId) {
|
||||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"),
|
return new NavPath(new NavPath.Point("Instances", "/instances"),
|
||||||
new NavPath.Point(nodeId, "/nodes/" + nodeId),
|
new NavPath.Point(instanceId, "/instances/" + instanceId),
|
||||||
new NavPath.Point(roomId, "/nodes/" + nodeId + "/rooms/" + roomId));
|
new NavPath.Point(roomId, "/instances/" + instanceId + "/rooms/" + roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Point(String name, String uri) {}
|
private record Point(String name, String uri) {}
|
||||||
|
|||||||
@ -15,25 +15,27 @@ import com.vaadin.flow.component.textfield.Autocomplete;
|
|||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import ru.dragonestia.picker.api.model.node.NodeDefinition;
|
import ru.dragonestia.picker.api.exception.InvalidIdentifierException;
|
||||||
import ru.dragonestia.picker.api.model.node.PickingMethod;
|
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||||
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
|
import ru.dragonestia.picker.api.model.instance.type.PickingMethod;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class RegisterNode extends Details {
|
public class RegisterInstance extends Details {
|
||||||
|
|
||||||
private final Function<NodeDefinition, Response> onSubmit;
|
private final Function<InstanceDTO, Response> onSubmit;
|
||||||
private final TextField identifierField;
|
private final TextField identifierField;
|
||||||
private final RadioButtonGroup<PickingMethod> modeRadio;
|
private final RadioButtonGroup<PickingMethod> modeRadio;
|
||||||
private final Checkbox persistField;
|
private final Checkbox persistField;
|
||||||
|
|
||||||
public RegisterNode(Function<NodeDefinition, Response> onSubmit) {
|
public RegisterInstance(Function<InstanceDTO, Response> onSubmit) {
|
||||||
super(new H2("Register node"));
|
super(new H2("Register instance"));
|
||||||
this.onSubmit = onSubmit;
|
this.onSubmit = onSubmit;
|
||||||
|
|
||||||
var layout = new VerticalLayout();
|
var layout = new VerticalLayout();
|
||||||
layout.add(identifierField = createNodeIdentifierField());
|
layout.add(identifierField = createInstanceIdentifierField());
|
||||||
layout.add(modeRadio = createModeRadio());
|
layout.add(modeRadio = createModeRadio());
|
||||||
layout.add(persistField = createPersistField());
|
layout.add(persistField = createPersistField());
|
||||||
layout.add(createSubmitButton());
|
layout.add(createSubmitButton());
|
||||||
@ -41,10 +43,10 @@ public class RegisterNode extends Details {
|
|||||||
add(layout);
|
add(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextField createNodeIdentifierField() {
|
private TextField createInstanceIdentifierField() {
|
||||||
var field = new TextField("Identifier");
|
var field = new TextField("Identifier");
|
||||||
field.setMinWidth(20, Unit.REM);
|
field.setMinWidth(20, Unit.REM);
|
||||||
field.setPlaceholder("example-node-id");
|
field.setPlaceholder("example-instance-id");
|
||||||
field.setHelperText("The field can contain only lowercase letters, numbers and a dash character");
|
field.setHelperText("The field can contain only lowercase letters, numbers and a dash character");
|
||||||
field.setPattern("^[a-z\\d-]+$");
|
field.setPattern("^[a-z\\d-]+$");
|
||||||
field.setRequired(true);
|
field.setRequired(true);
|
||||||
@ -83,36 +85,61 @@ public class RegisterNode extends Details {
|
|||||||
|
|
||||||
private @Nullable String validateForm(String identifier) {
|
private @Nullable String validateForm(String identifier) {
|
||||||
if (identifier.isEmpty()) {
|
if (identifier.isEmpty()) {
|
||||||
return "Node id cannot be empty";
|
return "Instance id cannot be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
InstanceId.of(identifier);
|
||||||
|
} catch (InvalidIdentifierException ex) {
|
||||||
|
return "Invalid identifier";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClick() {
|
private void onClick() {
|
||||||
String nodeIdentifier = identifierField.getValue();
|
String instanceIdentifier = identifierField.getValue();
|
||||||
|
|
||||||
String error = null;
|
String error = null;
|
||||||
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
|
if (identifierField.isInvalid() || (error = validateForm(instanceIdentifier)) != null) {
|
||||||
if (identifierField.isInvalid()) {
|
if (identifierField.isInvalid()) {
|
||||||
error = "Invalid node id format";
|
error = "Invalid instance id format";
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifications.error(error);
|
Notifications.error(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = new NodeDefinition(NodeIdentifier.of(nodeIdentifier))
|
var response = onSubmit.apply(new InstanceDTO() {
|
||||||
.setPickingMethod(modeRadio.getValue())
|
|
||||||
.setPersist(persistField.getValue());
|
@Override
|
||||||
var response = onSubmit.apply(node);
|
public String getId() {
|
||||||
|
return instanceIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PickingMethod getMethod() {
|
||||||
|
return modeRadio.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPersist() {
|
||||||
|
return persistField.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCountRooms() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
if (response.error()) {
|
if (response.error()) {
|
||||||
Notifications.error(response.reason());
|
Notifications.error(response.reason());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifications.success("Node was successfully registered");
|
Notifications.success("Instance was successfully registered");
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Response(boolean error, @Nullable String reason) {}
|
public record Response(boolean error, @Nullable String reason) {}
|
||||||
@ -11,29 +11,29 @@ import com.vaadin.flow.component.textfield.Autocomplete;
|
|||||||
import com.vaadin.flow.component.textfield.TextArea;
|
import com.vaadin.flow.component.textfield.TextArea;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||||
import ru.dragonestia.picker.api.model.room.IRoom;
|
import ru.dragonestia.picker.api.model.room.Room;
|
||||||
import ru.dragonestia.picker.api.model.room.RoomDefinition;
|
import ru.dragonestia.picker.api.model.room.RoomId;
|
||||||
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class RegisterRoom extends Details {
|
public class RegisterRoom extends Details {
|
||||||
|
|
||||||
private final INode node;
|
private final Instance instance;
|
||||||
private final Function<RoomDefinition, Response> onSubmit;
|
private final Function<Room, Response> onSubmit;
|
||||||
private final TextField identifierField;
|
private final TextField identifierField;
|
||||||
private final TextArea payloadField;
|
private final TextArea payloadField;
|
||||||
private final Checkbox lockedField;
|
private final Checkbox lockedField;
|
||||||
private final Checkbox persistField;
|
private final Checkbox persistField;
|
||||||
|
|
||||||
public RegisterRoom(INode node, Function<RoomDefinition, Response> onSubmit) {
|
public RegisterRoom(Instance instance, Function<Room, Response> onSubmit) {
|
||||||
super(new H2("Register room"));
|
super(new H2("Register room"));
|
||||||
this.node = node;
|
this.instance = instance;
|
||||||
this.onSubmit = onSubmit;
|
this.onSubmit = onSubmit;
|
||||||
|
|
||||||
var layout = new VerticalLayout();
|
var layout = new VerticalLayout();
|
||||||
layout.add(createNodeIdentifierField());
|
layout.add(createInstanceIdentifierField());
|
||||||
layout.add(identifierField = createRoomIdentifierField());
|
layout.add(identifierField = createRoomIdentifierField());
|
||||||
layout.add(payloadField = createPayloadField());
|
layout.add(payloadField = createPayloadField());
|
||||||
layout.add(lockedField = createLockedField());
|
layout.add(lockedField = createLockedField());
|
||||||
@ -43,10 +43,10 @@ public class RegisterRoom extends Details {
|
|||||||
add(layout);
|
add(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextField createNodeIdentifierField() {
|
private TextField createInstanceIdentifierField() {
|
||||||
var field = new TextField("Node identifier");
|
var field = new TextField("Instance identifier");
|
||||||
field.setMinWidth(20, Unit.REM);
|
field.setMinWidth(20, Unit.REM);
|
||||||
field.setValue(node.getIdentifier());
|
field.setValue(instance.id().getValue());
|
||||||
field.setReadOnly(true);
|
field.setReadOnly(true);
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ public class RegisterRoom extends Details {
|
|||||||
|
|
||||||
private @Nullable String validateForm(String identifier) {
|
private @Nullable String validateForm(String identifier) {
|
||||||
if (identifier.isEmpty()) {
|
if (identifier.isEmpty()) {
|
||||||
return "Node identifier cannot be empty";
|
return "Instance identifier cannot be empty";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -116,13 +116,7 @@ public class RegisterRoom extends Details {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var room = new RoomDefinition(node.getIdentifierObject(), RoomIdentifier.of(roomId))
|
var response = onSubmit.apply(new Room(RoomId.of(roomId), instance.id(), -1, lockedField.getValue(), payloadField.getValue(), persistField.getValue()));
|
||||||
.setMaxSlots(IRoom.UNLIMITED_SLOTS)
|
|
||||||
.setPayload(payloadField.getValue())
|
|
||||||
.setPersist(persistField.getValue());
|
|
||||||
|
|
||||||
room.setLocked(lockedField.getValue());
|
|
||||||
var response = onSubmit.apply(room);
|
|
||||||
clear();
|
clear();
|
||||||
if (response.error()) {
|
if (response.error()) {
|
||||||
Notifications.error(response.reason());
|
Notifications.error(response.reason());
|
||||||
|
|||||||
@ -15,27 +15,31 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
import ru.dragonestia.picker.api.model.room.RoomDetails;
|
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||||
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
|
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||||
import ru.dragonestia.picker.api.repository.RoomRepository;
|
import ru.dragonestia.picker.api.model.room.RoomId;
|
||||||
import ru.dragonestia.picker.api.repository.query.room.GetAllRooms;
|
import ru.dragonestia.picker.cp.repository.dto.RoomDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.AllRooms;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
import ru.dragonestia.picker.cp.util.UsingSlots;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
public class RoomList extends VerticalLayout implements RefreshableTable {
|
public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||||
|
|
||||||
private final INode node;
|
private final Instance instance;
|
||||||
private final RoomRepository roomRepository;
|
private final RoomPickerClient client;
|
||||||
private final Grid<ShortResponseRoom> roomsGrid;
|
private final Grid<RoomDTO> roomsGrid;
|
||||||
private final TextField searchField;
|
private final TextField searchField;
|
||||||
private List<ShortResponseRoom> cachedRooms;
|
private List<RoomDTO> cachedRooms;
|
||||||
private final Span totalUsers = new Span();
|
private final Span totalEntities = new Span();
|
||||||
|
|
||||||
public RoomList(INode node, RoomRepository roomRepository) {
|
public RoomList(Instance instance, RoomPickerClient client) {
|
||||||
this.node = node;
|
this.instance = instance;
|
||||||
this.roomRepository = roomRepository;
|
this.client = client;
|
||||||
|
|
||||||
add(new H2("Rooms"));
|
add(new H2("Rooms"));
|
||||||
add(searchField = createSearchField());
|
add(searchField = createSearchField());
|
||||||
@ -58,39 +62,39 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
|||||||
var temp = input.trim();
|
var temp = input.trim();
|
||||||
|
|
||||||
roomsGrid.setItems(cachedRooms.stream()
|
roomsGrid.setItems(cachedRooms.stream()
|
||||||
.filter(room -> room.getIdentifier().startsWith(temp))
|
.filter(room -> room.getId().startsWith(temp))
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid<ShortResponseRoom> createGrid() {
|
private Grid<RoomDTO> createGrid() {
|
||||||
var grid = new Grid<>(ShortResponseRoom.class, false);
|
var grid = new Grid<>(RoomDTO.class, false);
|
||||||
|
|
||||||
grid.addColumn(ShortResponseRoom::getIdentifier).setHeader("Identifier").setSortable(true);
|
grid.addColumn(RoomDTO::getId).setHeader("Identifier").setSortable(true);
|
||||||
|
|
||||||
grid.addComponentColumn(room -> {
|
grid.addComponentColumn(room -> {
|
||||||
var result = new Span();
|
var result = new Span();
|
||||||
if (room.getMaxSlots() == -1) {
|
if (room.getSlots() == -1) {
|
||||||
result.setText("Unlimited");
|
result.setText("Unlimited");
|
||||||
result.getElement().getThemeList().add("badge contrast");
|
result.getElement().getThemeList().add("badge contrast");
|
||||||
} else {
|
} else {
|
||||||
result.setText(Integer.toString(room.getMaxSlots()));
|
result.setText(Integer.toString(room.getSlots()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}).setHeader("Slots").setComparator((room1, room2) -> {
|
}).setHeader("Slots").setComparator((room1, room2) -> {
|
||||||
var r1 = room1.hasUnlimitedSlots()? Integer.MAX_VALUE : room1.getMaxSlots();
|
var r1 = room1.getSlots() == 1? Integer.MAX_VALUE : room1.getSlots();
|
||||||
var r2 = room2.hasUnlimitedSlots()? Integer.MAX_VALUE : room2.getMaxSlots();
|
var r2 = room2.getSlots() == 1? Integer.MAX_VALUE : room2.getSlots();
|
||||||
|
|
||||||
return Integer.compare(r1, r2);
|
return Integer.compare(r1, r2);
|
||||||
}).setSortable(true).setTextAlign(ColumnTextAlign.CENTER);
|
}).setSortable(true).setTextAlign(ColumnTextAlign.CENTER);
|
||||||
|
|
||||||
grid.addColumn(this::getUsers).setHeader("Users")
|
grid.addColumn(RoomDTO::getCountEntities).setHeader("Entities")
|
||||||
.setComparator((room1, room2) -> Integer.compare(getUsers(room1), getUsers(room2))).setSortable(true)
|
.setComparator(Comparator.comparingInt(RoomDTO::getCountEntities)).setSortable(true)
|
||||||
.setTextAlign(ColumnTextAlign.CENTER).setFooter(totalUsers);
|
.setTextAlign(ColumnTextAlign.CENTER).setFooter(totalEntities);
|
||||||
|
|
||||||
grid.addColumn(room -> Math.max(UserList.getUsingPercentage(room.getMaxSlots(), getUsers(room)), 0) + "%")
|
grid.addColumn(room -> Math.max(UsingSlots.getUsingPercentage(room.getSlots(), room.getCountEntities()), 0) + "%")
|
||||||
.setComparator((room1, room2) -> {
|
.setComparator((room1, room2) -> {
|
||||||
var p1 = UserList.getUsingPercentage(room1.getMaxSlots(), getUsers(room1));
|
var p1 = UsingSlots.getUsingPercentage(room1.getSlots(), room1.getCountEntities());
|
||||||
var p2 = UserList.getUsingPercentage(room2.getMaxSlots(), getUsers(room2));
|
var p2 = UsingSlots.getUsingPercentage(room2.getSlots(), room2.getCountEntities());
|
||||||
|
|
||||||
return Integer.compare(p1, p2);
|
return Integer.compare(p1, p2);
|
||||||
}).setHeader("Occupancy").setTextAlign(ColumnTextAlign.CENTER);
|
}).setHeader("Occupancy").setTextAlign(ColumnTextAlign.CENTER);
|
||||||
@ -114,7 +118,7 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HorizontalLayout createManageButtons(ShortResponseRoom room) {
|
private HorizontalLayout createManageButtons(RoomDTO room) {
|
||||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -134,15 +138,15 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
|||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickDetailsButton(ShortResponseRoom room) {
|
private void clickDetailsButton(RoomDTO room) {
|
||||||
getUI().ifPresent(ui -> {
|
getUI().ifPresent(ui -> {
|
||||||
ui.navigate("/nodes/%s/rooms/%s".formatted(node.getIdentifier(), room.getIdentifier()));
|
ui.navigate("/instances/%s/rooms/%s".formatted(instance.id(), room.getId()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickRemoveButton(ShortResponseRoom room) {
|
private void clickRemoveButton(RoomDTO room) {
|
||||||
var dialog = new Dialog("Confirm room deletion");
|
var dialog = new Dialog("Confirm room deletion");
|
||||||
dialog.add(new Html("<p>Confirm that you want to delete room. Enter <b><u>" + room.getIdentifier() + "</u></b> to field below and confirm.</p>"));
|
dialog.add(new Html("<p>Confirm that you want to delete room. Enter <b><u>" + room.getId() + "</u></b> to field below and confirm.</p>"));
|
||||||
|
|
||||||
var inputField = new TextField();
|
var inputField = new TextField();
|
||||||
inputField.setWidth("100%");
|
inputField.setWidth("100%");
|
||||||
@ -152,13 +156,13 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
|||||||
var button = new Button("Confirm");
|
var button = new Button("Confirm");
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
if (!room.getIdentifier().equals(inputField.getValue())) {
|
if (!room.getId().equals(inputField.getValue())) {
|
||||||
Notifications.error("Invalid input");
|
Notifications.error("Invalid input");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRoom(room);
|
removeRoom(room);
|
||||||
Notifications.success("Room <b>" + room.getIdentifier() + "</b> was successfully removed!");
|
Notifications.success("Room <b>" + room.getId() + "</b> was successfully removed!");
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -174,31 +178,23 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRoom(ShortResponseRoom room) {
|
public void removeRoom(RoomDTO room) {
|
||||||
roomRepository.removeRoom(room);
|
client.getRoomRepository().deleteRoom(InstanceId.of(room.getInstanceId()), RoomId.of(room.getId()));
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getUsers(ShortResponseRoom room) {
|
|
||||||
var users = room.getDetail(RoomDetails.COUNT_USERS);
|
|
||||||
if (users == null) return 0;
|
|
||||||
try {
|
|
||||||
|
|
||||||
return Integer.parseInt(users);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
cachedRooms = roomRepository.allRooms(GetAllRooms.withAllDetails(node.getIdentifierObject()));
|
cachedRooms = client.getRestTemplate().executeGraphQL(AllRooms.query(instance.id().getValue())).getAllRooms()
|
||||||
|
.stream()
|
||||||
|
.map(room -> (RoomDTO) room)
|
||||||
|
.toList();
|
||||||
applySearch(searchField.getValue());
|
applySearch(searchField.getValue());
|
||||||
|
|
||||||
int users = 0;
|
int entities = 0;
|
||||||
for (var room: cachedRooms) {
|
for (var room: cachedRooms) {
|
||||||
users += getUsers(room);
|
entities += room.getCountEntities();
|
||||||
}
|
}
|
||||||
totalUsers.setText("Total users: " + users);
|
totalEntities.setText("Total entities: " + entities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,130 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.component;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.button.Button;
|
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
|
||||||
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
import com.vaadin.flow.component.html.Span;
|
|
||||||
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 ru.dragonestia.picker.api.model.room.IRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.model.user.UserDetails;
|
|
||||||
import ru.dragonestia.picker.api.repository.EntityRepository;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.GetAllUsersFromRoom;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.UnlinkUsersFromRoom;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class UserList extends VerticalLayout implements RefreshableTable {
|
|
||||||
|
|
||||||
private final IRoom room;
|
|
||||||
private final EntityRepository entityRepository;
|
|
||||||
private final Button buttonRemove;
|
|
||||||
private final Grid<IUser> usersGrid;
|
|
||||||
private final Span totalUsers = new Span();
|
|
||||||
private final Span occupancy = new Span();
|
|
||||||
private List<IUser> cachedUsers = new ArrayList<>();
|
|
||||||
|
|
||||||
public UserList(IRoom room, EntityRepository entityRepository) {
|
|
||||||
this.room = room;
|
|
||||||
this.entityRepository = entityRepository;
|
|
||||||
|
|
||||||
buttonRemove = createButtonRemove();
|
|
||||||
add(usersGrid = createUsersGrid());
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
updateButtonRemove();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Button createButtonRemove() {
|
|
||||||
var button = new Button("Unlink");
|
|
||||||
button.setPrefixComponent(new Icon(VaadinIcon.UNLINK));
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
|
||||||
button.addClickListener(event -> {
|
|
||||||
var users = usersGrid.getSelectedItems();
|
|
||||||
if (users.isEmpty()) return;
|
|
||||||
entityRepository.unlinkUsersFromRoom(UnlinkUsersFromRoom.builder()
|
|
||||||
.setNodeId(room.getNodeIdentifierObject())
|
|
||||||
.setRoomId(room.getIdentifierObject())
|
|
||||||
.setUsers(users.stream().map(IUser::getIdentifierObject).collect(Collectors.toSet()))
|
|
||||||
.build());
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Grid<IUser> createUsersGrid() {
|
|
||||||
var grid = new Grid<IUser>();
|
|
||||||
|
|
||||||
grid.addColumn(IUser::getIdentifier).setHeader("User Identifier").setSortable(true).setFooter(totalUsers);
|
|
||||||
|
|
||||||
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setTextAlign(ColumnTextAlign.CENTER)
|
|
||||||
.setHeader("Linked with rooms").setComparator((user1, user2) -> {
|
|
||||||
var r1 = Integer.parseInt(Objects.requireNonNull(user1.getDetail(UserDetails.COUNT_ROOMS)));
|
|
||||||
var r2 = Integer.parseInt(Objects.requireNonNull(user2.getDetail(UserDetails.COUNT_ROOMS)));
|
|
||||||
|
|
||||||
return Integer.compare(r1, r2);
|
|
||||||
}).setSortable(true).setFooter(occupancy);
|
|
||||||
|
|
||||||
grid.addComponentColumn(this::createManageButton).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true)
|
|
||||||
.setTextAlign(ColumnTextAlign.END).setHeader(createManageTableButtons());
|
|
||||||
|
|
||||||
grid.setSelectionMode(Grid.SelectionMode.MULTI);
|
|
||||||
grid.addSelectionListener(event -> updateButtonRemove());
|
|
||||||
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Button createManageButton(IUser user) {
|
|
||||||
var button = new Button("Details");
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
button.addClickListener(e -> {
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getIdentifier()));
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HorizontalLayout createManageTableButtons() {
|
|
||||||
var layout = new HorizontalLayout();
|
|
||||||
layout.setJustifyContentMode(JustifyContentMode.END);
|
|
||||||
|
|
||||||
layout.add(buttonRemove);
|
|
||||||
layout.add(createRefreshButton());
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateButtonRemove() {
|
|
||||||
var users = usersGrid.getSelectedItems();
|
|
||||||
|
|
||||||
if (users.isEmpty()) {
|
|
||||||
buttonRemove.setEnabled(false);
|
|
||||||
buttonRemove.setText("Unlink");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonRemove.setEnabled(true);
|
|
||||||
buttonRemove.setText("Unlink(" + users.size() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getUsingPercentage(int slots, int usedSlots) {
|
|
||||||
if (slots == IRoom.UNLIMITED_SLOTS) return -1;
|
|
||||||
double percent = usedSlots / (double) slots * 100;
|
|
||||||
return (int) percent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refresh() {
|
|
||||||
cachedUsers = entityRepository.getAllUsersFormRoom(GetAllUsersFromRoom.withAllDetails(room.getNodeIdentifierObject(), room.getIdentifierObject()))
|
|
||||||
.stream().map(user -> (IUser) user).toList();
|
|
||||||
usersGrid.setItems(cachedUsers);
|
|
||||||
totalUsers.setText("Total users: " + cachedUsers.size());
|
|
||||||
occupancy.setText("Occupancy: %s".formatted(getUsingPercentage(room.getMaxSlots(), cachedUsers.size()) + "%"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.config;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class RestApiConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
RestTemplateBuilder restTemplateBuilder() {
|
|
||||||
return new RestTemplateBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Supplier<RestTemplate> restTemplateSupplier(@Autowired RestTemplateBuilder builder) {
|
|
||||||
return builder::build;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,11 +3,7 @@ package ru.dragonestia.picker.cp.config;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.cp.annotation.ServerURL;
|
import ru.dragonestia.picker.cp.annotation.ServerURL;
|
||||||
import ru.dragonestia.picker.cp.model.Account;
|
|
||||||
import ru.dragonestia.picker.cp.model.provider.AccountProvider;
|
|
||||||
import ru.dragonestia.picker.cp.util.AdminRoomPickerClient;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class RoomPickerConfig {
|
public class RoomPickerConfig {
|
||||||
@ -15,25 +11,9 @@ public class RoomPickerConfig {
|
|||||||
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080}")
|
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080}")
|
||||||
private String serverUrl;
|
private String serverUrl;
|
||||||
|
|
||||||
@Value("${ROOMPICKER_ADMIN_USERNAME:admin}")
|
|
||||||
private String adminUsername;
|
|
||||||
|
|
||||||
@Value("${ROOMPICKER_ADMIN_PASSWORD:qwerty123}")
|
|
||||||
private String adminPassword;
|
|
||||||
|
|
||||||
@ServerURL
|
@ServerURL
|
||||||
@Bean
|
@Bean
|
||||||
String severUrl() {
|
String severUrl() {
|
||||||
return serverUrl;
|
return serverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
RoomPickerClient adminClient() {
|
|
||||||
return new AdminRoomPickerClient(serverUrl, adminUsername, adminPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
AccountProvider accountProvider() {
|
|
||||||
return response -> new Account(response, new RoomPickerClient(serverUrl, response.getUsername(), response.getPassword()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.config;
|
|
||||||
|
|
||||||
import com.vaadin.flow.spring.security.VaadinWebSecurity;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
import ru.dragonestia.picker.cp.page.LoginPage;
|
|
||||||
import ru.dragonestia.picker.cp.service.AccountService;
|
|
||||||
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Configuration
|
|
||||||
public class SecurityConfig extends VaadinWebSecurity {
|
|
||||||
|
|
||||||
private AccountService accountService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void setAccountService(AccountService accountService) {
|
|
||||||
this.accountService = accountService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
PasswordEncoder passwordEncoder() {
|
|
||||||
return new PasswordEncoder() {
|
|
||||||
@Override
|
|
||||||
public String encode(CharSequence rawPassword) {
|
|
||||||
return rawPassword.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
|
||||||
return rawPassword.toString().equals(encodedPassword);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
http.authorizeHttpRequests(auth -> {
|
|
||||||
auth.requestMatchers(AntPathRequestMatcher.antMatcher("/static/**")).permitAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
http.userDetailsService(accountService);
|
|
||||||
|
|
||||||
super.configure(http);
|
|
||||||
|
|
||||||
http.formLogin(login -> {
|
|
||||||
login.successForwardUrl("/instances");
|
|
||||||
login.defaultSuccessUrl("/instances");
|
|
||||||
});
|
|
||||||
setLoginView(http, LoginPage.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ import com.vaadin.flow.server.ErrorHandler;
|
|||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import ru.dragonestia.picker.api.exception.ApiException;
|
import ru.dragonestia.picker.api.exception.ApiException;
|
||||||
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
||||||
import ru.dragonestia.picker.cp.component.Notifications;
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
import java.security.InvalidParameterException;
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
@ -21,30 +21,22 @@ public class ApplicationErrorHandler implements ErrorHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorEvent.getThrowable() instanceof ApiException ex) {
|
if (errorEvent.getThrowable().getClass().getAnnotation(ApiException.class) != null) {
|
||||||
execute(() -> {
|
execute(() -> Notifications.error(errorEvent.getThrowable().getMessage()));
|
||||||
Notifications.error(ex.getMessage());
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorEvent.getThrowable() instanceof InvalidParameterException ex) {
|
if (errorEvent.getThrowable() instanceof InvalidParameterException ex) {
|
||||||
execute(() -> {
|
execute(() -> Notifications.error(ex.getMessage()));
|
||||||
Notifications.error(ex.getMessage());
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorEvent.getThrowable() instanceof NotEnoughPermissions) {
|
if (errorEvent.getThrowable() instanceof NotEnoughPermissions) {
|
||||||
execute(() -> {
|
execute(() -> Notifications.error("Not enough permissions to this action"));
|
||||||
Notifications.error("Not enough permissions to this action");
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(() -> {
|
execute(() -> Notifications.error("Internal server error"));
|
||||||
Notifications.error("Internal server error");
|
|
||||||
});
|
|
||||||
log.throwing(errorEvent.getThrowable());
|
log.throwing(errorEvent.getThrowable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
package ru.dragonestia.picker.cp.exception;
|
||||||
|
|
||||||
|
public class Unauthorized extends RuntimeException {}
|
||||||
@ -3,10 +3,8 @@ package ru.dragonestia.picker.cp.listener;
|
|||||||
import com.vaadin.flow.server.ServiceInitEvent;
|
import com.vaadin.flow.server.ServiceInitEvent;
|
||||||
import com.vaadin.flow.server.VaadinServiceInitListener;
|
import com.vaadin.flow.server.VaadinServiceInitListener;
|
||||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import ru.dragonestia.picker.cp.error.ApplicationErrorHandler;
|
import ru.dragonestia.picker.cp.error.ApplicationErrorHandler;
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
@SpringComponent
|
@SpringComponent
|
||||||
public class VaadinEventListener implements VaadinServiceInitListener {
|
public class VaadinEventListener implements VaadinServiceInitListener {
|
||||||
|
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.model;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.account.IAccount;
|
|
||||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class Account implements IAccount, UserDetails {
|
|
||||||
|
|
||||||
private final ResponseAccount original;
|
|
||||||
private final RoomPickerClient client;
|
|
||||||
private final Set<Permission> permissions;
|
|
||||||
|
|
||||||
public Account(ResponseAccount original, RoomPickerClient client) {
|
|
||||||
this.original = original;
|
|
||||||
this.client = client;
|
|
||||||
permissions = original.getPermissions().stream().map(permission -> new Permission("ROLE_" + permission)).collect(Collectors.toSet());
|
|
||||||
permissions.add(new Permission("ROLE_USER"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull RoomPickerClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Permission> getAuthorities() {
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccountNonLocked() {
|
|
||||||
return !original.isLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCredentialsNonExpired() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String getUsername() {
|
|
||||||
return original.getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String getPassword() {
|
|
||||||
return original.getPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Set<String> getPermissions() {
|
|
||||||
return original.getPermissions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocked() {
|
|
||||||
return original.isLocked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package ru.dragonestia.picker.cp.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Account;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public class AccountSession {
|
||||||
|
|
||||||
|
private final Account data;
|
||||||
|
private String password;
|
||||||
|
private final RoomPickerClient client;
|
||||||
|
|
||||||
|
public AccountSession(Account data, String password, RoomPickerClient client) {
|
||||||
|
this.data = data;
|
||||||
|
this.password = password;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,34 +0,0 @@
|
|||||||
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 {
|
|
||||||
|
|
||||||
private final String authority;
|
|
||||||
|
|
||||||
public Permission(@NotNull String authority) {
|
|
||||||
this.authority = authority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.model.provider;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
|
||||||
import ru.dragonestia.picker.cp.model.Account;
|
|
||||||
|
|
||||||
public interface AccountProvider {
|
|
||||||
|
|
||||||
@NotNull Account provide(@NotNull ResponseAccount responseAccount);
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Html;
|
|
||||||
import com.vaadin.flow.component.UI;
|
|
||||||
import com.vaadin.flow.component.login.LoginForm;
|
|
||||||
import com.vaadin.flow.component.login.LoginI18n;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.router.*;
|
|
||||||
import jakarta.annotation.security.PermitAll;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
@PermitAll
|
|
||||||
@Route("/login")
|
|
||||||
public class LoginPage extends VerticalLayout implements BeforeEnterObserver, AfterNavigationObserver {
|
|
||||||
|
|
||||||
private final LoginForm formLogin;
|
|
||||||
private final boolean authenticated;
|
|
||||||
|
|
||||||
public LoginPage(SecurityService securityService) {
|
|
||||||
if (securityService.getAuthenticatedAccount() != null) {
|
|
||||||
formLogin = null;
|
|
||||||
authenticated = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticated = false;
|
|
||||||
|
|
||||||
setAlignItems(Alignment.CENTER);
|
|
||||||
|
|
||||||
add(new Html("<h1><u>RoomPicker!</u></h1>"));
|
|
||||||
add(formLogin = createFormLogin());
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoginForm createFormLogin() {
|
|
||||||
var form = new LoginForm();
|
|
||||||
form.setAction("login");
|
|
||||||
form.setForgotPasswordButtonVisible(false);
|
|
||||||
|
|
||||||
var i18n = LoginI18n.createDefault();
|
|
||||||
i18n.getForm().setTitle(null);
|
|
||||||
i18n.getForm().setUsername("Account username");
|
|
||||||
i18n.getForm().setSubmit("Login");
|
|
||||||
form.setI18n(i18n);
|
|
||||||
return form;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
|
||||||
if(event.getLocation()
|
|
||||||
.getQueryParameters()
|
|
||||||
.getParameters()
|
|
||||||
.containsKey("error")) {
|
|
||||||
|
|
||||||
formLogin.setError(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterNavigation(AfterNavigationEvent afterNavigationEvent) {
|
|
||||||
if (!authenticated) return;
|
|
||||||
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("/nodes"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Html;
|
|
||||||
import com.vaadin.flow.component.html.H2;
|
|
||||||
import com.vaadin.flow.component.html.Hr;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
|
||||||
import ru.dragonestia.picker.cp.component.RoomList;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
import ru.dragonestia.picker.cp.component.RegisterRoom;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RolesAllowed("USER")
|
|
||||||
@PageTitle("Rooms")
|
|
||||||
@Route(value = "/instances/:nodeId", layout = MainLayout.class)
|
|
||||||
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
|
||||||
|
|
||||||
private final SecurityService securityService;
|
|
||||||
private final RouteParamsExtractor paramsExtractor;
|
|
||||||
|
|
||||||
private RoomPickerClient client;
|
|
||||||
private INode node;
|
|
||||||
private RegisterRoom registerRoom;
|
|
||||||
private RoomList roomList;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
void postConstruct() {
|
|
||||||
client = securityService.getAuthenticatedAccount().getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
|
||||||
node = paramsExtractor.extractNode(event);
|
|
||||||
|
|
||||||
initComponents(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents(INode node) {
|
|
||||||
add(NavPath.toNode(node.getIdentifier()));
|
|
||||||
printNodeDetails(node);
|
|
||||||
add(new Hr());
|
|
||||||
add(registerRoom = new RegisterRoom(node, roomDefinition -> {
|
|
||||||
try {
|
|
||||||
client.getRoomRepository().saveRoom(roomDefinition);
|
|
||||||
return new RegisterRoom.Response(false, null);
|
|
||||||
} catch (Error error) {
|
|
||||||
return new RegisterRoom.Response(true, error.getMessage());
|
|
||||||
} finally {
|
|
||||||
roomList.refresh();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
add(new Hr());
|
|
||||||
add(roomList = new RoomList(node, client.getRoomRepository()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printNodeDetails(INode node) {
|
|
||||||
add(new H2("Node details"));
|
|
||||||
|
|
||||||
var layout = new VerticalLayout();
|
|
||||||
layout.add(new Html("<span>Identifier: <b>" + node.getIdentifier() + "</b></span>"));
|
|
||||||
layout.add(new Html("<span>Mode: <b>" + node.getPickingMethod().name() + "</b></span>"));
|
|
||||||
|
|
||||||
add(layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.html.Hr;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import com.vaadin.flow.router.RouteAlias;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import ru.dragonestia.picker.api.exception.ApiException;
|
|
||||||
import ru.dragonestia.picker.api.repository.InstanceRepository;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
import ru.dragonestia.picker.cp.component.NodeList;
|
|
||||||
import ru.dragonestia.picker.cp.component.RegisterNode;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
|
|
||||||
@RolesAllowed("USER")
|
|
||||||
@PageTitle("Nodes")
|
|
||||||
@RouteAlias(value = "/", layout = MainLayout.class)
|
|
||||||
@Route(value = "/instances", layout = MainLayout.class)
|
|
||||||
public class NodesPage extends VerticalLayout {
|
|
||||||
|
|
||||||
private final InstanceRepository instanceRepository;
|
|
||||||
private final NodeList nodeList;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public NodesPage(SecurityService securityService) {
|
|
||||||
this.instanceRepository = securityService.getAuthenticatedAccount().getClient().getNodeRepository();
|
|
||||||
|
|
||||||
add(NavPath.rootNodes());
|
|
||||||
|
|
||||||
if (securityService.hasRole("NODE_MANAGEMENT")) {
|
|
||||||
add(createRegisterNodeElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
add(new Hr());
|
|
||||||
add(nodeList = createNodeListElement());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected RegisterNode createRegisterNodeElement() {
|
|
||||||
return new RegisterNode(nodeDefinition -> {
|
|
||||||
try {
|
|
||||||
instanceRepository.saveNode(nodeDefinition);
|
|
||||||
return new RegisterNode.Response(false, "");
|
|
||||||
} catch (ApiException ex) {
|
|
||||||
return new RegisterNode.Response(true, ex.getMessage());
|
|
||||||
} finally {
|
|
||||||
nodeList.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected NodeList createNodeListElement() {
|
|
||||||
return new NodeList(instanceRepository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Html;
|
|
||||||
import com.vaadin.flow.component.Unit;
|
|
||||||
import com.vaadin.flow.component.button.Button;
|
|
||||||
import com.vaadin.flow.component.html.H2;
|
|
||||||
import com.vaadin.flow.component.html.Hr;
|
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.component.textfield.TextArea;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
|
||||||
import ru.dragonestia.picker.api.model.room.IRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.room.ResponseRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.LinkUsersWithRoom;
|
|
||||||
import ru.dragonestia.picker.cp.component.AddUsers;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
import ru.dragonestia.picker.cp.component.Notifications;
|
|
||||||
import ru.dragonestia.picker.cp.component.UserList;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RolesAllowed("USER")
|
|
||||||
@PageTitle("Room details")
|
|
||||||
@Route(value = "/instances/:nodeId/rooms/:roomId", layout = MainLayout.class)
|
|
||||||
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
|
||||||
|
|
||||||
private final SecurityService securityService;
|
|
||||||
private final RouteParamsExtractor paramsExtractor;
|
|
||||||
|
|
||||||
private RoomPickerClient client;
|
|
||||||
private INode node;
|
|
||||||
private ResponseRoom room;
|
|
||||||
private UserList userList;
|
|
||||||
private Button lockRoomButton;
|
|
||||||
private VerticalLayout roomInfo;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
void postConstruct() {
|
|
||||||
client = securityService.getAuthenticatedAccount().getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
|
||||||
node = paramsExtractor.extractNode(event);
|
|
||||||
room = (ResponseRoom) paramsExtractor.extractRoom(event, node);
|
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
add(NavPath.toRoom(node.getIdentifier(), room.getIdentifier()));
|
|
||||||
add(new H2("Room details"));
|
|
||||||
printRoomDetails();
|
|
||||||
add(new Hr());
|
|
||||||
add(new AddUsers(room, (users, ignoreLimitation) -> appendUsers(room, users, ignoreLimitation)));
|
|
||||||
add(new Hr());
|
|
||||||
add(new H2("Users"));
|
|
||||||
add(userList = new UserList(room, client.getUserRepository()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRoomInfo() {
|
|
||||||
roomInfo.removeAll();
|
|
||||||
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getInstanceIdentifier() + "</b></span>"));
|
|
||||||
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getIdentifier() + "</b></span>"));
|
|
||||||
roomInfo.add(new Html("<span>Slots: <b>" + (room.hasUnlimitedSlots()? "Unlimited" : room.getMaxSlots()) + "</b></span>"));
|
|
||||||
roomInfo.add(new Html("<span>Locked: <b>" + (room.isLocked()? "Yes" : "No") + "</b></span>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printRoomDetails() {
|
|
||||||
add(roomInfo = new VerticalLayout());
|
|
||||||
roomInfo.setPadding(false);
|
|
||||||
|
|
||||||
updateRoomInfo();
|
|
||||||
add(lockRoomButton = new Button("", event -> changeBucketLockedState()));
|
|
||||||
setLockRoomButtonState();
|
|
||||||
|
|
||||||
var payload = new TextArea("Payload(" + room.getPayload().length() + ")");
|
|
||||||
payload.setValue(room.getPayload());
|
|
||||||
payload.setReadOnly(true);
|
|
||||||
payload.setMinWidth(50, Unit.REM);
|
|
||||||
add(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLockRoomButtonState() {
|
|
||||||
if (room.isLocked()) {
|
|
||||||
lockRoomButton.setText("Unlock");
|
|
||||||
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.UNLOCK));
|
|
||||||
} else {
|
|
||||||
lockRoomButton.setText("Lock");
|
|
||||||
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.LOCK));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeBucketLockedState() {
|
|
||||||
var newValue = !room.isLocked();
|
|
||||||
client.getRoomRepository().lockRoom(room.getPath(), newValue);
|
|
||||||
|
|
||||||
room.setLocked(newValue);
|
|
||||||
setLockRoomButtonState();
|
|
||||||
updateRoomInfo();
|
|
||||||
|
|
||||||
Notifications.success("Success");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendUsers(IRoom room, Collection<IUser> users, boolean ignoreLimitation) {
|
|
||||||
AtomicBoolean validationFail = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
var newUsers = users.stream()
|
|
||||||
.filter(user -> {
|
|
||||||
if (user.getIdentifier().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
validationFail.set(true);
|
|
||||||
return false;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
client.getUserRepository().linkUsersWithRoom(LinkUsersWithRoom.builder()
|
|
||||||
.setNodeId(room.getNodeIdentifierObject())
|
|
||||||
.setRoomId(room.getIdentifierObject())
|
|
||||||
.setUsers(users.stream().map(IUser::getIdentifierObject).collect(Collectors.toSet()))
|
|
||||||
.setIgnoreSlotLimitation(ignoreLimitation)
|
|
||||||
.build());
|
|
||||||
userList.refresh();
|
|
||||||
|
|
||||||
if (validationFail.get()) {
|
|
||||||
if (newUsers.isEmpty()) {
|
|
||||||
Notifications.error("All users entered were added because they do not comply with the rule for writing the user identifier");
|
|
||||||
} else {
|
|
||||||
Notifications.warn("Not all users entered were added because they do not comply with the rule for writing the user identifier");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Notifications.success("Success");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.button.Button;
|
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
|
||||||
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
import com.vaadin.flow.component.html.H2;
|
|
||||||
import com.vaadin.flow.component.html.H3;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.room.RoomDetails;
|
|
||||||
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.FindRoomsLinkedWithUser;
|
|
||||||
import ru.dragonestia.picker.cp.component.RefreshableTable;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RolesAllowed("USER")
|
|
||||||
@PageTitle("User details")
|
|
||||||
@Route(value = "/users/:userId", layout = MainLayout.class)
|
|
||||||
public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserver, RefreshableTable {
|
|
||||||
|
|
||||||
private final SecurityService securityService;
|
|
||||||
private final RouteParamsExtractor paramsExtractor;
|
|
||||||
|
|
||||||
private RoomPickerClient client;
|
|
||||||
private IUser user;
|
|
||||||
private Grid<ShortResponseRoom> gridRooms;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
void postConstruct() {
|
|
||||||
client = securityService.getAuthenticatedAccount().getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
|
||||||
user = paramsExtractor.extractUser(event);
|
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
add(new H2("User '%s'".formatted(user.getIdentifier())));
|
|
||||||
add(new H3("Linked with rooms"));
|
|
||||||
add(gridRooms = createGrid());
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Grid<ShortResponseRoom> createGrid() {
|
|
||||||
var grid = new Grid<ShortResponseRoom>();
|
|
||||||
|
|
||||||
grid.addColumn(ShortResponseRoom::getIdentifier).setHeader("Room identifier").setSortable(true);
|
|
||||||
|
|
||||||
grid.addColumn(ShortResponseRoom::getInstanceIdentifier).setHeader("Node identifier").setSortable(true);
|
|
||||||
|
|
||||||
grid.addColumn(room -> room.getDetail(RoomDetails.COUNT_USERS)).setHeader("Users")
|
|
||||||
.setComparator((room1, room2) -> {
|
|
||||||
var r1 = Integer.parseInt(Objects.requireNonNull(room1.getDetail(RoomDetails.COUNT_USERS)));
|
|
||||||
var r2 = Integer.parseInt(Objects.requireNonNull(room2.getDetail(RoomDetails.COUNT_USERS)));
|
|
||||||
|
|
||||||
return Integer.compare(r1, r2);
|
|
||||||
}).setSortable(true);
|
|
||||||
|
|
||||||
grid.addComponentColumn(room -> {
|
|
||||||
var button = new Button("Details");
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
button.addClickListener(event -> {
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("/nodes/%s/rooms/%s".formatted(room.getInstanceIdentifier(), room.getIdentifier())));
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
|
|
||||||
|
|
||||||
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refresh() {
|
|
||||||
List<ShortResponseRoom> cachedRooms = client.getUserRepository()
|
|
||||||
.findRoomsLinkedWithUser(FindRoomsLinkedWithUser.withAllDetails(user.getIdentifierObject()));
|
|
||||||
gridRooms.setItems(cachedRooms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.Key;
|
|
||||||
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.grid.ColumnTextAlign;
|
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
|
||||||
import com.vaadin.flow.component.html.Span;
|
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
|
||||||
import com.vaadin.flow.router.PageTitle;
|
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.model.user.UserDetails;
|
|
||||||
import ru.dragonestia.picker.api.repository.EntityRepository;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.SearchUsers;
|
|
||||||
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
|
|
||||||
import ru.dragonestia.picker.cp.component.RefreshableTable;
|
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@RolesAllowed("USER")
|
|
||||||
@PageTitle("Search users")
|
|
||||||
@Route(value = "/users", layout = MainLayout.class)
|
|
||||||
public class UserSearchPage extends VerticalLayout implements RefreshableTable {
|
|
||||||
|
|
||||||
private final EntityRepository entityRepository;
|
|
||||||
private final TextField fieldUsername;
|
|
||||||
private final Grid<IUser> userGrid;
|
|
||||||
private final Span foundUsers;
|
|
||||||
private List<IUser> cachedUsers = new LinkedList<>();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public UserSearchPage(SecurityService securityService) {
|
|
||||||
this.entityRepository = securityService.getAuthenticatedAccount().getClient().getUserRepository();
|
|
||||||
|
|
||||||
foundUsers = new Span();
|
|
||||||
add(fieldUsername = createUsernameInputField());
|
|
||||||
add(userGrid = createUserGrid());
|
|
||||||
justRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TextField createUsernameInputField() {
|
|
||||||
var field = new TextField();
|
|
||||||
field.setLabel("Username");
|
|
||||||
field.setPlaceholder("some-user-identifier");
|
|
||||||
field.setRequired(true);
|
|
||||||
field.setMinWidth(30, Unit.PERCENTAGE);
|
|
||||||
field.setAutofocus(true);
|
|
||||||
|
|
||||||
var button = new Button(new Icon(VaadinIcon.SEARCH));
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
button.getStyle().set("color", "#FFFFFF");
|
|
||||||
button.addClickListener(event -> refresh());
|
|
||||||
button.addClickShortcut(Key.ENTER);
|
|
||||||
|
|
||||||
field.setSuffixComponent(button);
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Grid<IUser> createUserGrid() {
|
|
||||||
var grid = new Grid<IUser>();
|
|
||||||
|
|
||||||
grid.addColumn(IUser::getIdentifier).setHeader("Identifier").setSortable(true)
|
|
||||||
.setFooter(foundUsers);
|
|
||||||
|
|
||||||
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setComparator((user1, user2) -> {
|
|
||||||
var r1 = Integer.parseInt(Objects.requireNonNull(user1.getDetail(UserDetails.COUNT_ROOMS)));
|
|
||||||
var r2 = Integer.parseInt(Objects.requireNonNull(user2.getDetail(UserDetails.COUNT_ROOMS)));
|
|
||||||
|
|
||||||
return Integer.compare(r1, r2);
|
|
||||||
}).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms");
|
|
||||||
|
|
||||||
grid.addComponentColumn(user -> {
|
|
||||||
var button = new Button("Details");
|
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
button.addClickListener(event -> {
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getIdentifier()));
|
|
||||||
});
|
|
||||||
return button;
|
|
||||||
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
|
|
||||||
|
|
||||||
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void search(String input) {
|
|
||||||
System.out.println("Input: " + input);
|
|
||||||
if (input.isEmpty()) {
|
|
||||||
userGrid.setItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
userGrid.setItems(cachedUsers = entityRepository.searchUsers(SearchUsers.withAllDetails(EntityIdentifier.of(input)))
|
|
||||||
.stream().map(user -> (IUser) user).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refresh() {
|
|
||||||
search(fieldUsername.getValue().trim());
|
|
||||||
foundUsers.setText("Found %s users".formatted(cachedUsers.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void justRefresh() {
|
|
||||||
userGrid.setItems();
|
|
||||||
foundUsers.setText("Found %s users".formatted(cachedUsers.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
|
||||||
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.ErrorParameter;
|
|
||||||
import com.vaadin.flow.router.HasErrorParameter;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import ru.dragonestia.picker.api.exception.InvalidInstanceIdentifierException;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
|
|
||||||
public class InvalidNodeIdentifierPlug extends ErrorPlug implements HasErrorParameter<InvalidInstanceIdentifierException> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<InvalidInstanceIdentifierException> errorParameter) {
|
|
||||||
var ex = errorParameter.getException();
|
|
||||||
var nodeId = ex.getNodeId();
|
|
||||||
|
|
||||||
init(NavPath.toNode(nodeId), "Error 400", ex.getMessage());
|
|
||||||
return HttpServletResponse.SC_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
|
||||||
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.ErrorParameter;
|
|
||||||
import com.vaadin.flow.router.HasErrorParameter;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
|
|
||||||
public class InvalidRoomIdentifierPlug extends ErrorPlug implements HasErrorParameter<InvalidRoomIdentifierException> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setErrorParameter(BeforeEnterEvent event, ErrorParameter<InvalidRoomIdentifierException> errorParameter) {
|
|
||||||
var ex = errorParameter.getException();
|
|
||||||
var nodeId = ex.getNodeId();
|
|
||||||
var roomId = ex.getRoomId();
|
|
||||||
|
|
||||||
init(NavPath.toRoom(nodeId, roomId), "Error 400", ex.getMessage());
|
|
||||||
return HttpServletResponse.SC_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
|
||||||
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.ErrorParameter;
|
|
||||||
import com.vaadin.flow.router.HasErrorParameter;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import ru.dragonestia.picker.api.exception.InstanceNotFoundException;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
|
|
||||||
public class NodeNotFoundPlug extends ErrorPlug implements HasErrorParameter<InstanceNotFoundException> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<InstanceNotFoundException> errorParameter) {
|
|
||||||
var ex = errorParameter.getException();
|
|
||||||
var nodeId = ex.getNodeId();
|
|
||||||
|
|
||||||
init(NavPath.toNode(nodeId), "Error 404", ex.getMessage());
|
|
||||||
return HttpServletResponse.SC_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
|
||||||
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import com.vaadin.flow.router.ErrorParameter;
|
|
||||||
import com.vaadin.flow.router.HasErrorParameter;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
|
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
|
||||||
|
|
||||||
public class RoomNotFoundPlug extends ErrorPlug implements HasErrorParameter<RoomNotFoundException> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<RoomNotFoundException> errorParameter) {
|
|
||||||
var ex = errorParameter.getException();
|
|
||||||
var nodeId = ex.getNodeId();
|
|
||||||
var roomId = ex.getRoomId();
|
|
||||||
|
|
||||||
init(NavPath.toRoom(nodeId, roomId), "Error 404", ex.getMessage());
|
|
||||||
return HttpServletResponse.SC_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface InstanceRepository {
|
||||||
|
|
||||||
|
List<? extends InstanceDTO> all();
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.dto;
|
||||||
|
|
||||||
|
public interface EntityDTO {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
int getCountRooms();
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.dto;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.model.instance.type.PickingMethod;
|
||||||
|
|
||||||
|
public interface InstanceDTO {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
PickingMethod getMethod();
|
||||||
|
|
||||||
|
boolean isPersist();
|
||||||
|
|
||||||
|
int getCountRooms();
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.dto;
|
||||||
|
|
||||||
|
public interface RoomDTO {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
String getInstanceId();
|
||||||
|
|
||||||
|
int getSlots();
|
||||||
|
|
||||||
|
boolean isLocked();
|
||||||
|
|
||||||
|
int getCountEntities();
|
||||||
|
|
||||||
|
boolean isPersist();
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class AllEntities {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
query ($instanceId: String!, $roomId: String!) {
|
||||||
|
roomById(nodeId: $instanceId, roomId: $roomId) {
|
||||||
|
entities {
|
||||||
|
id
|
||||||
|
countRooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private Room roomById;
|
||||||
|
|
||||||
|
public static GraphqlQuery<AllEntities> query(String instanceId, String roomId) {
|
||||||
|
return new GraphqlQuery<>(QUERY, AllEntities.class, params -> {
|
||||||
|
params.put("instanceId", instanceId);
|
||||||
|
params.put("roomId", roomId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Room {
|
||||||
|
private List<Entity> entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Entity implements EntityDTO {
|
||||||
|
private String id;
|
||||||
|
private int countRooms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.api.model.instance.type.PickingMethod;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class AllInstances {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
{
|
||||||
|
allInstances {
|
||||||
|
id
|
||||||
|
method
|
||||||
|
persist
|
||||||
|
countRooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private List<Instance> allInstances;
|
||||||
|
|
||||||
|
public static GraphqlQuery<AllInstances> query() {
|
||||||
|
return new GraphqlQuery<>(QUERY, AllInstances.class, params -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Instance implements InstanceDTO {
|
||||||
|
private String id;
|
||||||
|
private PickingMethod method;
|
||||||
|
private boolean persist;
|
||||||
|
private int countRooms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.RoomDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class AllRooms {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
query ($nodeId: String!) {
|
||||||
|
allRooms(nodeId: $nodeId) {
|
||||||
|
id
|
||||||
|
instanceId
|
||||||
|
slots
|
||||||
|
locked
|
||||||
|
countEntities
|
||||||
|
persist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private List<Room> allRooms;
|
||||||
|
|
||||||
|
public static GraphqlQuery<AllRooms> query(String nodeId) {
|
||||||
|
return new GraphqlQuery<>(QUERY, AllRooms.class, params -> {
|
||||||
|
params.put("nodeId", nodeId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Room implements RoomDTO {
|
||||||
|
private String id;
|
||||||
|
private String instanceId;
|
||||||
|
private int slots;
|
||||||
|
private boolean locked;
|
||||||
|
private int countEntities;
|
||||||
|
private boolean persist;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class EntityData {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
query ($entityId: String!) {
|
||||||
|
entityById(id: $entityId) {
|
||||||
|
id
|
||||||
|
countRooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private Entity entityById;
|
||||||
|
|
||||||
|
public static GraphqlQuery<EntityData> query(String entityId) {
|
||||||
|
return new GraphqlQuery<>(QUERY, EntityData.class, params -> {
|
||||||
|
params.put("entityId", entityId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Entity implements EntityDTO {
|
||||||
|
private String id;
|
||||||
|
private int countRooms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.RoomDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class EntityRooms {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
query ($entityId: String!) {
|
||||||
|
entityById(id: $entityId) {
|
||||||
|
rooms {
|
||||||
|
id
|
||||||
|
instanceId
|
||||||
|
countEntities
|
||||||
|
locked
|
||||||
|
persist
|
||||||
|
slots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private Entity entityById;
|
||||||
|
|
||||||
|
public static GraphqlQuery<EntityRooms> query(String entityId) {
|
||||||
|
return new GraphqlQuery<>(QUERY, EntityRooms.class, params -> {
|
||||||
|
params.put("entityId", entityId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Entity {
|
||||||
|
private List<Room> rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Room implements RoomDTO {
|
||||||
|
private String id;
|
||||||
|
private String instanceId;
|
||||||
|
private int slots;
|
||||||
|
private boolean locked;
|
||||||
|
private int countEntities;
|
||||||
|
private boolean persist;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.graphql;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import ru.dragonestia.picker.api.impl.util.GraphqlQuery;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class SearchEntity {
|
||||||
|
|
||||||
|
private final static String QUERY = """
|
||||||
|
query ($input: String!) {
|
||||||
|
searchEntity(input: $input) {
|
||||||
|
id
|
||||||
|
countRooms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private List<Entity> searchEntity;
|
||||||
|
|
||||||
|
public static GraphqlQuery<SearchEntity> query(String input) {
|
||||||
|
return new GraphqlQuery<>(QUERY, SearchEntity.class, params -> {
|
||||||
|
params.put("input", input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class Entity implements EntityDTO {
|
||||||
|
private String id;
|
||||||
|
private int countRooms;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package ru.dragonestia.picker.cp.repository.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.dragonestia.picker.cp.repository.InstanceRepository;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.AllInstances;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class InstanceRepositoryImpl implements InstanceRepository {
|
||||||
|
|
||||||
|
private final SessionService sessionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends InstanceDTO> all() {
|
||||||
|
return sessionService.getSession().getClient().getRestTemplate().executeGraphQL(AllInstances.query()).getAllInstances();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,25 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.service;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.cp.model.Account;
|
|
||||||
import ru.dragonestia.picker.cp.model.provider.AccountProvider;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AccountService implements UserDetailsService {
|
|
||||||
|
|
||||||
private final RoomPickerClient adminClient;
|
|
||||||
private final AccountProvider accountProvider;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Account loadUserByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
var response = adminClient.getAccountRepository().findAccountByUsername(username)
|
|
||||||
.orElseThrow(() -> new UsernameNotFoundException(username));
|
|
||||||
|
|
||||||
return accountProvider.provide(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.service;
|
|
||||||
|
|
||||||
import com.vaadin.flow.component.UI;
|
|
||||||
import com.vaadin.flow.server.VaadinServletRequest;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import ru.dragonestia.picker.cp.model.Account;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SecurityService {
|
|
||||||
|
|
||||||
public Account getAuthenticatedAccount() {
|
|
||||||
var context = SecurityContextHolder.getContext();
|
|
||||||
if (context != null && context.getAuthentication().getPrincipal() instanceof Account account) {
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logout() {
|
|
||||||
UI.getCurrent().getPage().setLocation("/login");
|
|
||||||
var logoutHandler = new SecurityContextLogoutHandler();
|
|
||||||
logoutHandler.logout(VaadinServletRequest.getCurrent().getHttpServletRequest(), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasRole(String role) {
|
|
||||||
var r = "ROLE_" + role;
|
|
||||||
for (var permission: getAuthenticatedAccount().getAuthorities()) {
|
|
||||||
if (r.equals(permission.getAuthority())) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package ru.dragonestia.picker.cp.service;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.UI;
|
||||||
|
import com.vaadin.flow.server.VaadinSession;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
import ru.dragonestia.picker.cp.exception.Unauthorized;
|
||||||
|
import ru.dragonestia.picker.cp.model.AccountSession;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SessionService {
|
||||||
|
|
||||||
|
public void setSession(AccountSession session) {
|
||||||
|
VaadinSession.getCurrent().setAttribute(AccountSession.class, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSession getSession() {
|
||||||
|
return VaadinSession.getCurrent().getAttribute(AccountSession.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAuthorisation() throws Unauthorized {
|
||||||
|
if (VaadinSession.getCurrent().getAttribute(AccountSession.class) == null) {
|
||||||
|
throw new Unauthorized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void login(AccountSession session, UI ui) {
|
||||||
|
setSession(session);
|
||||||
|
ui.getPage().setLocation("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logout(UI ui) {
|
||||||
|
VaadinSession.getCurrent().setAttribute(AccountSession.class, null);
|
||||||
|
ui.getPage().setLocation("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPermission(Permission permission) {
|
||||||
|
return getSession().getData().permissions().contains(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.util;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.repository.InstanceRepository;
|
|
||||||
import ru.dragonestia.picker.api.repository.RoomRepository;
|
|
||||||
import ru.dragonestia.picker.api.repository.EntityRepository;
|
|
||||||
|
|
||||||
public class AdminRoomPickerClient extends RoomPickerClient {
|
|
||||||
|
|
||||||
public AdminRoomPickerClient(@NotNull String url, @NotNull String username, @NotNull String password) {
|
|
||||||
super(url, username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull InstanceRepository getNodeRepository() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RoomRepository getRoomRepository() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull EntityRepository getUserRepository() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.picker.cp.component;
|
package ru.dragonestia.picker.cp.util;
|
||||||
|
|
||||||
import com.vaadin.flow.component.Html;
|
import com.vaadin.flow.component.Html;
|
||||||
import com.vaadin.flow.component.button.Button;
|
import com.vaadin.flow.component.button.Button;
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package ru.dragonestia.picker.cp.util;
|
||||||
|
|
||||||
|
import com.github.javaparser.quality.NotNull;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class PermissionDescription {
|
||||||
|
|
||||||
|
private final static Map<Permission, String> map;
|
||||||
|
|
||||||
|
static {
|
||||||
|
map = new HashMap<>();
|
||||||
|
map.put(Permission.NODE_MANAGEMENT, "Create and remove instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionDescription() {}
|
||||||
|
|
||||||
|
public static String of(@NotNull Permission permission) {
|
||||||
|
return map.getOrDefault(permission, permission.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
package ru.dragonestia.picker.cp.util;
|
||||||
|
|
||||||
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
|
import com.vaadin.flow.router.RouteParameters;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.dragonestia.picker.api.exception.DoesNotExistsException;
|
||||||
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Account;
|
||||||
|
import ru.dragonestia.picker.api.model.account.AccountId;
|
||||||
|
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||||
|
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||||
|
import ru.dragonestia.picker.api.model.room.Room;
|
||||||
|
import ru.dragonestia.picker.api.model.room.RoomId;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.EntityData;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RouteParamExtractor {
|
||||||
|
|
||||||
|
private final SessionService sessionService;
|
||||||
|
|
||||||
|
private RoomPickerClient client() {
|
||||||
|
return sessionService.getSession().getClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instance instance(RouteParameters params) throws DoesNotExistsException {
|
||||||
|
var id = params.get("instanceId").map(InstanceId::of).orElseThrow();
|
||||||
|
return client().getInstanceRepository().getInstance(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Room room(RouteParameters params) throws DoesNotExistsException {
|
||||||
|
var instanceId = params.get("instanceId").map(InstanceId::of).orElseThrow();
|
||||||
|
var roomId = params.get("roomId").map(RoomId::of).orElseThrow();
|
||||||
|
return client().getRoomRepository().getRoom(instanceId, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityDTO entity(RouteParameters params) throws DoesNotExistsException {
|
||||||
|
var id = params.get("entityId").orElseThrow();
|
||||||
|
return client().getRestTemplate().executeGraphQL(EntityData.query(id)).getEntityById();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account account(RouteParameters params) throws DoesNotExistsException {
|
||||||
|
var id = params.get("accountId").map(AccountId::of).orElseThrow();
|
||||||
|
return client().getAccountRepository().getAccount(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,46 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.util;
|
|
||||||
|
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import ru.dragonestia.picker.api.exception.InstanceNotFoundException;
|
|
||||||
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.account.IAccount;
|
|
||||||
import ru.dragonestia.picker.api.model.node.INode;
|
|
||||||
import ru.dragonestia.picker.api.model.room.IRoom;
|
|
||||||
import ru.dragonestia.picker.api.model.user.IUser;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.node.FindNodeById;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.room.FindRoomById;
|
|
||||||
import ru.dragonestia.picker.api.repository.query.user.FindUserById;
|
|
||||||
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
|
|
||||||
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
|
|
||||||
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class RouteParamsExtractor {
|
|
||||||
|
|
||||||
private final RoomPickerClient client;
|
|
||||||
|
|
||||||
public INode extractNode(BeforeEnterEvent e) throws InstanceNotFoundException {
|
|
||||||
var nodeId = NodeIdentifier.of(e.getRouteParameters().get("nodeId").orElseThrow(() -> new InstanceNotFoundException("null")));
|
|
||||||
return client.getNodeRepository().findNodeById(FindNodeById.justFind(nodeId)).orElseThrow(() -> new InstanceNotFoundException(nodeId.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRoom extractRoom(BeforeEnterEvent e, INode node) throws RoomNotFoundException {
|
|
||||||
var nodeId = node.getIdentifierObject();
|
|
||||||
var roomId = RoomIdentifier.of(e.getRouteParameters().get("roomId").orElseThrow(() -> new InstanceNotFoundException("null")));
|
|
||||||
return client.getRoomRepository().find(FindRoomById.just(nodeId, roomId)).orElseThrow(() -> new InstanceNotFoundException(roomId.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IUser extractUser(BeforeEnterEvent e) {
|
|
||||||
var userId = EntityIdentifier.of(e.getRouteParameters().get("userId").orElseThrow(RuntimeException::new));
|
|
||||||
return client.getUserRepository().findUserById(FindUserById.withAllDetails(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAccount extractAccount(BeforeEnterEvent e) {
|
|
||||||
var accountId = e.getRouteParameters().get("accountId").orElseThrow(RuntimeException::new);
|
|
||||||
return client.getAccountRepository().findAccountByUsername(accountId).orElseThrow(RuntimeException::new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package ru.dragonestia.picker.cp.util;
|
||||||
|
|
||||||
|
public class UsingSlots {
|
||||||
|
|
||||||
|
private UsingSlots() {}
|
||||||
|
|
||||||
|
public static int getUsingPercentage(int slots, int usedSlots) {
|
||||||
|
if (slots == -1) return -1;
|
||||||
|
double percent = usedSlots / (double) slots * 100;
|
||||||
|
return (int) percent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
import com.vaadin.flow.component.AbstractField;
|
import com.vaadin.flow.component.AbstractField;
|
||||||
import com.vaadin.flow.component.Html;
|
import com.vaadin.flow.component.Html;
|
||||||
@ -8,52 +8,37 @@ import com.vaadin.flow.component.button.ButtonVariant;
|
|||||||
import com.vaadin.flow.component.html.H2;
|
import com.vaadin.flow.component.html.H2;
|
||||||
import com.vaadin.flow.component.html.H3;
|
import com.vaadin.flow.component.html.H3;
|
||||||
import com.vaadin.flow.component.html.Hr;
|
import com.vaadin.flow.component.html.Hr;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|
||||||
import com.vaadin.flow.component.textfield.PasswordField;
|
import com.vaadin.flow.component.textfield.PasswordField;
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
import com.vaadin.flow.router.*;
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
import ru.dragonestia.picker.api.model.account.Account;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
import com.vaadin.flow.router.Route;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
|
||||||
import ru.dragonestia.picker.api.model.account.IAccount;
|
|
||||||
import ru.dragonestia.picker.cp.component.AccountList;
|
import ru.dragonestia.picker.cp.component.AccountList;
|
||||||
import ru.dragonestia.picker.cp.component.Notifications;
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
import ru.dragonestia.picker.cp.model.Permission;
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@PageTitle("UserDetails details")
|
||||||
@RolesAllowed("ADMIN")
|
|
||||||
@PageTitle("Account details")
|
|
||||||
@Route(value = "/admin/accounts/:accountId", layout = MainLayout.class)
|
@Route(value = "/admin/accounts/:accountId", layout = MainLayout.class)
|
||||||
public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
public class AccountView extends SecuredView{
|
||||||
|
|
||||||
private final SecurityService securityService;
|
private Account account;
|
||||||
private final RouteParamsExtractor paramsExtractor;
|
|
||||||
|
|
||||||
private RoomPickerClient client;
|
public AccountView(SessionService sessionService, RouteParamExtractor paramsExtractor) {
|
||||||
private IAccount account;
|
super(sessionService, paramsExtractor, Permission.ADMIN);
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
void postConstruct() {
|
|
||||||
client = securityService.getAuthenticatedAccount().getClient();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
protected void preRender(RouteParameters routeParams) {
|
||||||
account = paramsExtractor.extractAccount(event);
|
account = getParamsExtractor().account(routeParams);
|
||||||
|
|
||||||
init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
@Override
|
||||||
add(new H2("Account management"));
|
protected void render() {
|
||||||
add(new Html("<span>Username: <b>%s</b></span>".formatted(account.getUsername())));
|
add(new H2("UserDetails management"));
|
||||||
|
add(new Html("<span>Entityname: <b>%s</b></span>".formatted(account.id())));
|
||||||
|
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
add(new H3("Change password"));
|
add(new H3("Change password"));
|
||||||
@ -91,7 +76,7 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.getAccountRepository().setPassword(account, pass);
|
getClient().getAccountRepository().changePassword(account, pass);
|
||||||
Notifications.success("Password successfully changed!");
|
Notifications.success("Password successfully changed!");
|
||||||
newPassword.setValue("");
|
newPassword.setValue("");
|
||||||
confirmPassword.setValue("");
|
confirmPassword.setValue("");
|
||||||
@ -102,9 +87,11 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
|||||||
|
|
||||||
private void createEditPermissions() {
|
private void createEditPermissions() {
|
||||||
var permissionsList = new ArrayList<AccountList.PermissionCheckBox>();
|
var permissionsList = new ArrayList<AccountList.PermissionCheckBox>();
|
||||||
for (var permission: Permission.Enum.values()) {
|
for (var permission: Permission.values()) {
|
||||||
|
if (permission == Permission.ADMIN) continue;
|
||||||
|
|
||||||
var comp = new AccountList.PermissionCheckBox(permission);
|
var comp = new AccountList.PermissionCheckBox(permission);
|
||||||
comp.setValue(account.getPermissions().contains(permission.name()));
|
comp.setValue(account.permissions().contains(permission));
|
||||||
permissionsList.add(comp);
|
permissionsList.add(comp);
|
||||||
add(comp);
|
add(comp);
|
||||||
}
|
}
|
||||||
@ -113,10 +100,9 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
|||||||
var permissions = permissionsList.stream()
|
var permissions = permissionsList.stream()
|
||||||
.filter(AbstractField::getValue)
|
.filter(AbstractField::getValue)
|
||||||
.map(AccountList.PermissionCheckBox::getOption)
|
.map(AccountList.PermissionCheckBox::getOption)
|
||||||
.map(Enum::name)
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
client.getAccountRepository().setPermissions(account, permissions);
|
getClient().getAccountRepository().setPermissions(account, permissions);
|
||||||
Notifications.success("Permissions successfully changed!");
|
Notifications.success("Permissions successfully changed!");
|
||||||
});
|
});
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
@ -125,8 +111,8 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
|||||||
|
|
||||||
private Button createDeleteAccountButton() {
|
private Button createDeleteAccountButton() {
|
||||||
var button = new Button("Delete this account", event -> {
|
var button = new Button("Delete this account", event -> {
|
||||||
client.getAccountRepository().removeAccount(account);
|
getClient().getAccountRepository().deleteAccount(account.id());
|
||||||
Notifications.warn("Account '%s' was deleted.".formatted(account.getUsername()));
|
Notifications.warn("UserDetails '%s' was deleted.".formatted(account.id()));
|
||||||
|
|
||||||
getUI().ifPresent(ui -> {
|
getUI().ifPresent(ui -> {
|
||||||
ui.navigate("/admin/accounts");
|
ui.navigate("/admin/accounts");
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.router.PageTitle;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
import ru.dragonestia.picker.cp.component.AccountList;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
@PageTitle("Accounts")
|
||||||
|
@Route(value = "/admin/accounts", layout = MainLayout.class)
|
||||||
|
public class AllAccountsView extends SecuredView {
|
||||||
|
|
||||||
|
public AllAccountsView(SessionService sessionService, RouteParamExtractor paramExtractor) {
|
||||||
|
super(sessionService, paramExtractor, Permission.ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
add(new H2("Account details management"));
|
||||||
|
add(new AccountList(getClient()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.html.Hr;
|
||||||
|
import com.vaadin.flow.router.PageTitle;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.router.RouteAlias;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||||
|
import ru.dragonestia.picker.cp.component.NavPath;
|
||||||
|
import ru.dragonestia.picker.cp.component.InstanceList;
|
||||||
|
import ru.dragonestia.picker.cp.component.RegisterInstance;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
@PageTitle("Instances")
|
||||||
|
@RouteAlias(value = "/", layout = MainLayout.class)
|
||||||
|
@Route(value = "/instances", layout = MainLayout.class)
|
||||||
|
public class AllInstancesView extends SecuredView {
|
||||||
|
|
||||||
|
private InstanceList instanceList;
|
||||||
|
|
||||||
|
public AllInstancesView(SessionService sessionService, RouteParamExtractor paramExtractor) {
|
||||||
|
super(sessionService, paramExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RegisterInstance createRegisterInstanceElement() {
|
||||||
|
return new RegisterInstance(instance -> {
|
||||||
|
try {
|
||||||
|
getClient().getInstanceRepository().createInstance(InstanceId.of(instance.getId()), instance.getMethod(), instance.isPersist());
|
||||||
|
return new RegisterInstance.Response(false, "");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return new RegisterInstance.Response(true, ex.getMessage());
|
||||||
|
} finally {
|
||||||
|
instanceList.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InstanceList createInstanceListElement() {
|
||||||
|
return new InstanceList(getClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
add(NavPath.rootInstances());
|
||||||
|
|
||||||
|
if (getSessionService().hasPermission(Permission.NODE_MANAGEMENT)) {
|
||||||
|
add(createRegisterInstanceElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
add(new Hr());
|
||||||
|
add(instanceList = createInstanceListElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||||
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.component.html.H3;
|
||||||
|
import com.vaadin.flow.router.PageTitle;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import com.vaadin.flow.router.RouteParameters;
|
||||||
|
import ru.dragonestia.picker.cp.component.RefreshableTable;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.RoomDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.EntityRooms;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@PageTitle("Entity details")
|
||||||
|
@Route(value = "/entities/:entityId", layout = MainLayout.class)
|
||||||
|
public class EntityView extends SecuredView implements RefreshableTable {
|
||||||
|
|
||||||
|
private EntityDTO entity;
|
||||||
|
private Grid<RoomDTO> gridRooms;
|
||||||
|
|
||||||
|
public EntityView(SessionService service, RouteParamExtractor paramExtractor) {
|
||||||
|
super(service, paramExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void preRender(RouteParameters routeParams) {
|
||||||
|
entity = getParamsExtractor().entity(routeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
add(new H2("Entity '%s'".formatted(entity.getId())));
|
||||||
|
add(new H3("Linked with rooms"));
|
||||||
|
add(gridRooms = createGrid());
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Grid<RoomDTO> createGrid() {
|
||||||
|
var grid = new Grid<RoomDTO>();
|
||||||
|
|
||||||
|
grid.addColumn(RoomDTO::getId).setHeader("Room identifier").setSortable(true);
|
||||||
|
|
||||||
|
grid.addColumn(RoomDTO::getInstanceId).setHeader("Instance identifier").setSortable(true);
|
||||||
|
|
||||||
|
grid.addColumn(RoomDTO::getCountEntities).setHeader("Entities")
|
||||||
|
.setComparator((room1, room2) -> {
|
||||||
|
var r1 = room1.getCountEntities();
|
||||||
|
var r2 = room2.getCountEntities();
|
||||||
|
|
||||||
|
return Integer.compare(r1, r2);
|
||||||
|
}).setSortable(true);
|
||||||
|
|
||||||
|
grid.addComponentColumn(room -> {
|
||||||
|
var button = new Button("Details");
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
button.addClickListener(event -> {
|
||||||
|
getUI().ifPresent(ui -> ui.navigate("/instances/%s/rooms/%s".formatted(room.getInstanceId(), room.getId())));
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
|
||||||
|
|
||||||
|
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
List<RoomDTO> cachedRooms = getClient().getRestTemplate().executeGraphQL(EntityRooms.query(entity.getId())).getEntityById().getRooms().stream().map(entity -> (RoomDTO) entity).toList();
|
||||||
|
gridRooms.setItems(cachedRooms);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.Html;
|
||||||
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.component.html.Hr;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
import com.vaadin.flow.router.*;
|
||||||
|
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||||
|
import ru.dragonestia.picker.cp.component.RoomList;
|
||||||
|
import ru.dragonestia.picker.cp.component.NavPath;
|
||||||
|
import ru.dragonestia.picker.cp.component.RegisterRoom;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
@PageTitle("Rooms")
|
||||||
|
@Route(value = "/instances/:instanceId", layout = MainLayout.class)
|
||||||
|
public class InstanceView extends SecuredView {
|
||||||
|
|
||||||
|
private Instance instance;
|
||||||
|
private RoomList roomList;
|
||||||
|
|
||||||
|
public InstanceView(SessionService sessionService, RouteParamExtractor paramsExtractor) {
|
||||||
|
super(sessionService, paramsExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void preRender(RouteParameters routeParams) {
|
||||||
|
instance = getParamsExtractor().instance(routeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
add(NavPath.toInstance(instance.id().getValue()));
|
||||||
|
printInstanceDetails(instance);
|
||||||
|
add(new Hr());
|
||||||
|
add(new RegisterRoom(instance, room -> {
|
||||||
|
try {
|
||||||
|
getClient().getRoomRepository().createRoom(room.instanceId(), room.id(), room.slots(), room.payload(), room.locked(), room.persist());
|
||||||
|
return new RegisterRoom.Response(false, null);
|
||||||
|
} catch (Error error) {
|
||||||
|
return new RegisterRoom.Response(true, error.getMessage());
|
||||||
|
} finally {
|
||||||
|
roomList.refresh();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
add(new Hr());
|
||||||
|
add(roomList = new RoomList(instance, getClient()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printInstanceDetails(Instance instance) {
|
||||||
|
add(new H2("Instance details"));
|
||||||
|
|
||||||
|
var layout = new VerticalLayout();
|
||||||
|
layout.add(new Html("<span>Identifier: <b>" + instance.id() + "</b></span>"));
|
||||||
|
layout.add(new Html("<span>Mode: <b>" + instance.method().name() + "</b></span>"));
|
||||||
|
|
||||||
|
add(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.Html;
|
||||||
|
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.orderedlayout.VerticalLayout;
|
||||||
|
import com.vaadin.flow.component.textfield.PasswordField;
|
||||||
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
|
import com.vaadin.flow.router.PageTitle;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
|
import ru.dragonestia.picker.api.impl.exception.AuthException;
|
||||||
|
import ru.dragonestia.picker.cp.annotation.ServerURL;
|
||||||
|
import ru.dragonestia.picker.cp.model.AccountSession;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
|
||||||
|
@Route("/login")
|
||||||
|
@PageTitle("Log in")
|
||||||
|
public class LoginView extends VerticalLayout {
|
||||||
|
|
||||||
|
private final SessionService sessionService;
|
||||||
|
private final String serverUrl;
|
||||||
|
private final TextField fieldLogin;
|
||||||
|
private final PasswordField fieldPassword;
|
||||||
|
|
||||||
|
public LoginView(SessionService sessionService, @ServerURL String serverURL) {
|
||||||
|
this.sessionService = sessionService;
|
||||||
|
this.serverUrl = serverURL;
|
||||||
|
|
||||||
|
if (sessionService.getSession() != null) {
|
||||||
|
fieldLogin = null;
|
||||||
|
fieldPassword = null;
|
||||||
|
|
||||||
|
add(new Button("Logout", event -> getUI().ifPresent(sessionService::logout)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAlignItems(Alignment.CENTER);
|
||||||
|
|
||||||
|
add(new Html("<h1><u>RoomPicker!</u></h1>"));
|
||||||
|
add(fieldLogin = createLoginField());
|
||||||
|
add(fieldPassword = createPasswordField());
|
||||||
|
add(createLoginButton());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField createLoginField() {
|
||||||
|
var field = new TextField("Username");
|
||||||
|
field.setRequired(true);
|
||||||
|
field.setMinWidth(20, Unit.PERCENTAGE);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PasswordField createPasswordField() {
|
||||||
|
var field = new PasswordField("Password");
|
||||||
|
field.setRequired(true);
|
||||||
|
field.setMinWidth(20, Unit.PERCENTAGE);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button createLoginButton() {
|
||||||
|
var button = new Button("Login", event -> tryLogin());
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
button.setMinWidth(20, Unit.PERCENTAGE);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryLogin() {
|
||||||
|
var client = new RoomPickerClient(serverUrl, fieldLogin.getValue(), fieldPassword.getValue());
|
||||||
|
try {
|
||||||
|
var account = client.getAccount();
|
||||||
|
var session = new AccountSession(account, fieldPassword.getValue(), client);
|
||||||
|
getUI().ifPresent(ui -> {
|
||||||
|
sessionService.login(session, ui);
|
||||||
|
});
|
||||||
|
} catch (AssertionError | AuthException ex) {
|
||||||
|
Notifications.error("Invalid username or password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.Html;
|
||||||
|
import com.vaadin.flow.component.Unit;
|
||||||
|
import com.vaadin.flow.component.button.Button;
|
||||||
|
import com.vaadin.flow.component.html.H2;
|
||||||
|
import com.vaadin.flow.component.html.Hr;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
import com.vaadin.flow.component.textfield.TextArea;
|
||||||
|
import com.vaadin.flow.router.*;
|
||||||
|
import ru.dragonestia.picker.api.model.entity.EntityId;
|
||||||
|
import ru.dragonestia.picker.api.model.room.Room;
|
||||||
|
import ru.dragonestia.picker.cp.component.AddEntities;
|
||||||
|
import ru.dragonestia.picker.cp.component.NavPath;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.Notifications;
|
||||||
|
import ru.dragonestia.picker.cp.component.EntityList;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@PageTitle("Room details")
|
||||||
|
@Route(value = "/instances/:instanceId/rooms/:roomId", layout = MainLayout.class)
|
||||||
|
public class RoomView extends SecuredView {
|
||||||
|
|
||||||
|
private Room room;
|
||||||
|
private EntityList entityList;
|
||||||
|
private Button lockRoomButton;
|
||||||
|
private VerticalLayout roomInfo;
|
||||||
|
|
||||||
|
public RoomView(SessionService sessionService, RouteParamExtractor paramExtractor) {
|
||||||
|
super(sessionService, paramExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void preRender(RouteParameters routeParams) {
|
||||||
|
room = getParamsExtractor().room(routeParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
add(NavPath.toRoom(room.instanceId().getValue(), room.id().getValue()));
|
||||||
|
add(new H2("Room details"));
|
||||||
|
printRoomDetails();
|
||||||
|
add(new Hr());
|
||||||
|
add(new AddEntities(room, (entities, ignoreLimitation) -> appendEntities(room, entities, ignoreLimitation)));
|
||||||
|
add(new Hr());
|
||||||
|
add(new H2("Entities"));
|
||||||
|
add(entityList = new EntityList(room, getClient()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRoomInfo() {
|
||||||
|
roomInfo.removeAll();
|
||||||
|
roomInfo.add(new Html("<span>Instance identifier: <b>" + room.instanceId() + "</b></span>"));
|
||||||
|
roomInfo.add(new Html("<span>Room identifier: <b>" + room.id() + "</b></span>"));
|
||||||
|
roomInfo.add(new Html("<span>Slots: <b>" + (room.slots() == -1? "Unlimited" : room.slots()) + "</b></span>"));
|
||||||
|
roomInfo.add(new Html("<span>Locked: <b>" + (room.locked()? "Yes" : "No") + "</b></span>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printRoomDetails() {
|
||||||
|
add(roomInfo = new VerticalLayout());
|
||||||
|
roomInfo.setPadding(false);
|
||||||
|
|
||||||
|
updateRoomInfo();
|
||||||
|
add(lockRoomButton = new Button("", event -> changeBucketLockedState()));
|
||||||
|
setLockRoomButtonState();
|
||||||
|
|
||||||
|
var payload = new TextArea("Payload(" + room.payload().length() + ")");
|
||||||
|
payload.setValue(room.payload());
|
||||||
|
payload.setReadOnly(true);
|
||||||
|
payload.setMinWidth(50, Unit.REM);
|
||||||
|
add(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLockRoomButtonState() {
|
||||||
|
if (room.locked()) {
|
||||||
|
lockRoomButton.setText("Unlock");
|
||||||
|
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.UNLOCK));
|
||||||
|
} else {
|
||||||
|
lockRoomButton.setText("Lock");
|
||||||
|
lockRoomButton.setPrefixComponent(new Icon(VaadinIcon.LOCK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeBucketLockedState() {
|
||||||
|
var newValue = !room.locked();
|
||||||
|
getClient().getRoomRepository().lockRoom(room, newValue);
|
||||||
|
|
||||||
|
room = getClient().getRoomRepository().getRoom(room.instanceId(), room.id());
|
||||||
|
|
||||||
|
setLockRoomButtonState();
|
||||||
|
updateRoomInfo();
|
||||||
|
|
||||||
|
Notifications.success("Success");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendEntities(Room room, Collection<EntityId> entities, boolean ignoreLimitation) {
|
||||||
|
AtomicBoolean validationFail = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
var newEntities = entities.stream()
|
||||||
|
.filter(entity -> {
|
||||||
|
if (entity.getValue().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationFail.set(true);
|
||||||
|
return false;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
getClient().getEntityRepository().linkEntitiesWithRoom(room, entities, ignoreLimitation);
|
||||||
|
entityList.refresh();
|
||||||
|
|
||||||
|
if (validationFail.get()) {
|
||||||
|
if (newEntities.isEmpty()) {
|
||||||
|
Notifications.error("All entities entered were added because they do not comply with the rule for writing the entity identifier");
|
||||||
|
} else {
|
||||||
|
Notifications.warn("Not all entities entered were added because they do not comply with the rule for writing the entity identifier");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Notifications.success("Success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.Key;
|
||||||
|
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.grid.ColumnTextAlign;
|
||||||
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
|
import com.vaadin.flow.router.PageTitle;
|
||||||
|
import com.vaadin.flow.router.Route;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import ru.dragonestia.picker.cp.component.RefreshableTable;
|
||||||
|
import ru.dragonestia.picker.cp.repository.dto.EntityDTO;
|
||||||
|
import ru.dragonestia.picker.cp.repository.graphql.SearchEntity;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@PageTitle("Search entities")
|
||||||
|
@Route(value = "/entities", layout = MainLayout.class)
|
||||||
|
public class SearchEntityView extends SecuredView implements RefreshableTable {
|
||||||
|
|
||||||
|
private TextField fieldEntityname;
|
||||||
|
private Grid<EntityDTO> entityGrid;
|
||||||
|
private Span foundEntities;
|
||||||
|
private List<EntityDTO> cachedEntities = new LinkedList<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SearchEntityView(SessionService sessionService, RouteParamExtractor paramExtractor) {
|
||||||
|
super(sessionService, paramExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextField createEntitynameInputField() {
|
||||||
|
var field = new TextField();
|
||||||
|
field.setLabel("Entityname");
|
||||||
|
field.setPlaceholder("some-entity-identifier");
|
||||||
|
field.setRequired(true);
|
||||||
|
field.setMinWidth(30, Unit.PERCENTAGE);
|
||||||
|
field.setAutofocus(true);
|
||||||
|
|
||||||
|
var button = new Button(new Icon(VaadinIcon.SEARCH));
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
button.getStyle().set("color", "#FFFFFF");
|
||||||
|
button.addClickListener(event -> refresh());
|
||||||
|
button.addClickShortcut(Key.ENTER);
|
||||||
|
|
||||||
|
field.setSuffixComponent(button);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Grid<EntityDTO> createEntityGrid() {
|
||||||
|
var grid = new Grid<EntityDTO>();
|
||||||
|
|
||||||
|
grid.addColumn(EntityDTO::getId).setHeader("Identifier").setSortable(true)
|
||||||
|
.setFooter(foundEntities);
|
||||||
|
|
||||||
|
grid.addColumn(EntityDTO::getCountRooms).setComparator((entity1, entity2) -> {
|
||||||
|
var r1 = entity1.getCountRooms();
|
||||||
|
var r2 = entity2.getCountRooms();
|
||||||
|
|
||||||
|
return Integer.compare(r1, r2);
|
||||||
|
}).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms");
|
||||||
|
|
||||||
|
grid.addComponentColumn(entity -> {
|
||||||
|
var button = new Button("Details");
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
|
button.addClickListener(event -> {
|
||||||
|
getUI().ifPresent(ui -> ui.navigate("/entities/" + entity.getId()));
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
|
||||||
|
|
||||||
|
grid.setMultiSort(true, Grid.MultiSortPriority.APPEND);
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void search(String input) {
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
entityGrid.setItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
entityGrid.setItems(cachedEntities = getClient().getRestTemplate().executeGraphQL(SearchEntity.query(input)).getSearchEntity().stream().map(entity -> (EntityDTO) entity).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
search(fieldEntityname.getValue().trim());
|
||||||
|
foundEntities.setText("Found %s entities".formatted(cachedEntities.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void justRefresh() {
|
||||||
|
entityGrid.setItems();
|
||||||
|
foundEntities.setText("Found %s entities".formatted(cachedEntities.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void render() {
|
||||||
|
foundEntities = new Span();
|
||||||
|
add(fieldEntityname = createEntitynameInputField());
|
||||||
|
add(entityGrid = createEntityGrid());
|
||||||
|
justRefresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.UI;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
|
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||||
|
import com.vaadin.flow.router.RouteParameters;
|
||||||
|
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||||
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
|
import ru.dragonestia.picker.cp.exception.Unauthorized;
|
||||||
|
import ru.dragonestia.picker.cp.model.AccountSession;
|
||||||
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||||
|
|
||||||
|
public abstract class SecuredView extends VerticalLayout implements BeforeEnterObserver {
|
||||||
|
|
||||||
|
private final SessionService sessionService;
|
||||||
|
private final RouteParamExtractor paramsExtractor;
|
||||||
|
private final AccountSession accountSession;
|
||||||
|
private final RoomPickerClient client;
|
||||||
|
private final boolean authenticated;
|
||||||
|
|
||||||
|
public SecuredView(SessionService sessionService, RouteParamExtractor paramsExtractor, Permission... requiredPermissions) {
|
||||||
|
this.sessionService = sessionService;
|
||||||
|
this.paramsExtractor = paramsExtractor;
|
||||||
|
this.accountSession = sessionService.getSession();
|
||||||
|
|
||||||
|
boolean auth = true;
|
||||||
|
try {
|
||||||
|
checkAuth();
|
||||||
|
checkPermissions(requiredPermissions);
|
||||||
|
} catch (Unauthorized ex) {
|
||||||
|
auth = false;
|
||||||
|
}
|
||||||
|
authenticated = auth;
|
||||||
|
|
||||||
|
client = auth? accountSession.getClient() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final SessionService getSessionService() {
|
||||||
|
return sessionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final AccountSession getAccountSession() {
|
||||||
|
return accountSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final RoomPickerClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RouteParamExtractor getParamsExtractor() {
|
||||||
|
return paramsExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAuth() throws Unauthorized {
|
||||||
|
if (accountSession == null || accountSession.getData() == null) {
|
||||||
|
throw new Unauthorized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPermissions(Permission... permissions) throws Unauthorized {
|
||||||
|
for (var permission: permissions) {
|
||||||
|
if (!accountSession.getData().permissions().contains(permission)) {
|
||||||
|
throw new Unauthorized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void beforeEnter(BeforeEnterEvent event) {
|
||||||
|
if (!authenticated) {
|
||||||
|
UI.getCurrent().getPage().setLocation("/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preRender(event.getRouteParameters());
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void preRender(RouteParameters routeParams) {}
|
||||||
|
|
||||||
|
protected abstract void render();
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.picker.cp.page;
|
package ru.dragonestia.picker.cp.view.layout;
|
||||||
|
|
||||||
import com.vaadin.flow.component.Component;
|
import com.vaadin.flow.component.Component;
|
||||||
import com.vaadin.flow.component.Html;
|
import com.vaadin.flow.component.Html;
|
||||||
@ -16,36 +16,45 @@ import com.vaadin.flow.component.orderedlayout.Scroller;
|
|||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.sidenav.SideNav;
|
import com.vaadin.flow.component.sidenav.SideNav;
|
||||||
import com.vaadin.flow.component.sidenav.SideNavItem;
|
import com.vaadin.flow.component.sidenav.SideNavItem;
|
||||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
import ru.dragonestia.picker.api.model.account.Permission;
|
||||||
import ru.dragonestia.picker.api.repository.response.RoomPickerInfoResponse;
|
import ru.dragonestia.picker.api.repository.response.RoomPickerInfoResponse;
|
||||||
import ru.dragonestia.picker.cp.annotation.ServerURL;
|
import ru.dragonestia.picker.cp.annotation.ServerURL;
|
||||||
import ru.dragonestia.picker.cp.model.Account;
|
import ru.dragonestia.picker.cp.model.AccountSession;
|
||||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
import ru.dragonestia.picker.cp.service.SessionService;
|
||||||
|
import ru.dragonestia.picker.cp.view.AllAccountsView;
|
||||||
|
import ru.dragonestia.picker.cp.view.AllInstancesView;
|
||||||
|
import ru.dragonestia.picker.cp.view.SearchEntityView;
|
||||||
|
|
||||||
public class MainLayout extends AppLayout {
|
public class MainLayout extends AppLayout {
|
||||||
|
|
||||||
private final SecurityService securityService;
|
private final SessionService sessionService;
|
||||||
private final RoomPickerInfoResponse serverInfo;
|
|
||||||
private final String serverUrl;
|
private final String serverUrl;
|
||||||
private final Account account;
|
private final AccountSession session;
|
||||||
|
private final RoomPickerInfoResponse info;
|
||||||
private final boolean isAdmin;
|
private final boolean isAdmin;
|
||||||
|
|
||||||
public MainLayout(SecurityService securityService, RoomPickerClient adminClient, @ServerURL String serverUrl) {
|
public MainLayout(SessionService sessionService, @ServerURL String serverURL) {
|
||||||
this.securityService = securityService;
|
this.sessionService = sessionService;
|
||||||
this.serverInfo = adminClient.getServerInfo();
|
this.serverUrl = serverURL;
|
||||||
this.serverUrl = serverUrl;
|
this.session = sessionService.getSession();
|
||||||
account = securityService.getAuthenticatedAccount();
|
|
||||||
isAdmin = securityService.hasRole("ADMIN");
|
|
||||||
|
|
||||||
var toggle = new DrawerToggle();
|
if (session == null) {
|
||||||
var scroller = new Scroller(createSideNav());
|
info = null;
|
||||||
scroller.setWidth(100, Unit.PERCENTAGE);
|
isAdmin = false;
|
||||||
|
} else {
|
||||||
|
info = session.getClient().getServerInfo();
|
||||||
|
isAdmin = session.getData().permissions().contains(Permission.ADMIN);
|
||||||
|
|
||||||
var navLayout = new VerticalLayout(createAccountButtons(), new Hr(), scroller);
|
var toggle = new DrawerToggle();
|
||||||
navLayout.setPadding(false);
|
var scroller = new Scroller(createSideNav());
|
||||||
|
scroller.setWidth(100, Unit.PERCENTAGE);
|
||||||
|
|
||||||
addToDrawer(navLayout);
|
var navLayout = new VerticalLayout(createAccountButtons(), new Hr(), scroller);
|
||||||
addToNavbar(toggle, createLogo());
|
navLayout.setPadding(false);
|
||||||
|
|
||||||
|
addToDrawer(navLayout);
|
||||||
|
addToNavbar(toggle, createLogo());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component createLogo() {
|
private Component createLogo() {
|
||||||
@ -53,17 +62,17 @@ public class MainLayout extends AppLayout {
|
|||||||
layout.setAlignItems(FlexComponent.Alignment.END);
|
layout.setAlignItems(FlexComponent.Alignment.END);
|
||||||
layout.setPadding(true);
|
layout.setPadding(true);
|
||||||
layout.add(new Html("<h2><u>RoomPicker!</u></h2>"));
|
layout.add(new Html("<h2><u>RoomPicker!</u></h2>"));
|
||||||
layout.add(new Html("<sub>" + serverInfo.version() + "</sub>"));
|
layout.add(new Html("<sub>" + info.version() + "</sub>"));
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component createAccountButtons() {
|
private Component createAccountButtons() {
|
||||||
var layout = new VerticalLayout();
|
var layout = new VerticalLayout();
|
||||||
var username = new Span(new Icon(isAdmin? VaadinIcon.USER_STAR : VaadinIcon.USER));
|
var username = new Span(new Icon(isAdmin? VaadinIcon.USER_STAR : VaadinIcon.USER));
|
||||||
username.add(account.getUsername());
|
username.add(session.getData().id().getValue());
|
||||||
layout.add(username);
|
layout.add(username);
|
||||||
|
|
||||||
var logoutButton = new Button("Logout", event -> securityService.logout());
|
var logoutButton = new Button("Logout", event -> getUI().ifPresent(sessionService::logout));
|
||||||
logoutButton.setWidth(100, Unit.PERCENTAGE);
|
logoutButton.setWidth(100, Unit.PERCENTAGE);
|
||||||
layout.add(logoutButton);
|
layout.add(logoutButton);
|
||||||
|
|
||||||
@ -72,10 +81,10 @@ public class MainLayout extends AppLayout {
|
|||||||
|
|
||||||
private SideNav createSideNav() {
|
private SideNav createSideNav() {
|
||||||
var nav = new SideNav();
|
var nav = new SideNav();
|
||||||
nav.addItem(new SideNavItem("Nodes list", NodesPage.class, VaadinIcon.FOLDER_O.create()));
|
nav.addItem(new SideNavItem("Instances list", AllInstancesView.class, VaadinIcon.FOLDER_O.create()));
|
||||||
nav.addItem(new SideNavItem("Search users", UserSearchPage.class, VaadinIcon.SEARCH.create()));
|
nav.addItem(new SideNavItem("Search entities", SearchEntityView.class, VaadinIcon.SEARCH.create()));
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
nav.addItem(new SideNavItem("Accounts", AccountsPage.class, VaadinIcon.USERS.create()));
|
nav.addItem(new SideNavItem("Accounts", AllAccountsView.class, VaadinIcon.USERS.create()));
|
||||||
}
|
}
|
||||||
nav.addItem(new SideNavItem("Documentation", "https://github.com/ScarletRedMan/RoomPicker", VaadinIcon.BOOK.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()));
|
nav.addItem(new SideNavItem("Swagger UI", serverUrl + "/api-docs-ui", VaadinIcon.CURLY_BRACKETS.create()));
|
||||||
@ -1,17 +1,16 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
package ru.dragonestia.picker.cp.view.plug;
|
||||||
|
|
||||||
import com.vaadin.flow.component.Html;
|
import com.vaadin.flow.component.Html;
|
||||||
import com.vaadin.flow.component.html.H1;
|
import com.vaadin.flow.component.html.H1;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.ParentLayout;
|
import com.vaadin.flow.router.ParentLayout;
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
import ru.dragonestia.picker.cp.component.NavPath;
|
||||||
import ru.dragonestia.picker.cp.page.MainLayout;
|
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||||
|
|
||||||
@ParentLayout(MainLayout.class)
|
@ParentLayout(MainLayout.class)
|
||||||
public abstract class ErrorPlug extends VerticalLayout {
|
public abstract class ErrorPlug extends VerticalLayout {
|
||||||
|
|
||||||
public void init(NavPath path, String title, String description) {
|
public void init(String title, String description) {
|
||||||
add(path);
|
|
||||||
add(new H1(title));
|
add(new H1(title));
|
||||||
add(new Html("<p>" + description + "</p>"));
|
add(new Html("<p>" + description + "</p>"));
|
||||||
}
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view.plug;
|
||||||
|
|
||||||
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
|
import com.vaadin.flow.router.ErrorParameter;
|
||||||
|
import com.vaadin.flow.router.HasErrorParameter;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import ru.dragonestia.picker.api.exception.InvalidIdentifierException;
|
||||||
|
|
||||||
|
public class InvalidIdentifierPlug extends ErrorPlug implements HasErrorParameter<InvalidIdentifierException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent event, ErrorParameter<InvalidIdentifierException> parameter) {
|
||||||
|
var ex = parameter.getException();
|
||||||
|
init("Error 400", ex.getMessage());
|
||||||
|
return HttpServletResponse.SC_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.picker.cp.page.plug;
|
package ru.dragonestia.picker.cp.view.plug;
|
||||||
|
|
||||||
import com.vaadin.flow.component.html.H1;
|
import com.vaadin.flow.component.html.H1;
|
||||||
import com.vaadin.flow.component.html.Paragraph;
|
import com.vaadin.flow.component.html.Paragraph;
|
||||||
@ -6,7 +6,6 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
|||||||
import com.vaadin.flow.router.ErrorParameter;
|
import com.vaadin.flow.router.ErrorParameter;
|
||||||
import com.vaadin.flow.router.HasErrorParameter;
|
import com.vaadin.flow.router.HasErrorParameter;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.security.core.userdetails.User;
|
|
||||||
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
||||||
|
|
||||||
public class NotEnoughPermissionsPlug extends ErrorPlug implements HasErrorParameter<NotEnoughPermissions> {
|
public class NotEnoughPermissionsPlug extends ErrorPlug implements HasErrorParameter<NotEnoughPermissions> {
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package ru.dragonestia.picker.cp.view.plug;
|
||||||
|
|
||||||
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
|
import com.vaadin.flow.router.ErrorParameter;
|
||||||
|
import com.vaadin.flow.router.HasErrorParameter;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import ru.dragonestia.picker.api.exception.DoesNotExistsException;
|
||||||
|
|
||||||
|
public class NotFoundPlug extends ErrorPlug implements HasErrorParameter<DoesNotExistsException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent event, ErrorParameter<DoesNotExistsException> parameter) {
|
||||||
|
var ex = parameter.getException();
|
||||||
|
init("Error 404", ex.getMessage());
|
||||||
|
return HttpServletResponse.SC_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -59,20 +59,20 @@ public class UserMetricsAspect {
|
|||||||
void onCreateNode(Instance instance) {
|
void onCreateNode(Instance instance) {
|
||||||
var nodeId = instance.getId();
|
var nodeId = instance.getId();
|
||||||
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId.getValue()).users())
|
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId.getValue()).users())
|
||||||
.tag("nodeId", nodeId.getValue())
|
.tag("instanceId", nodeId.getValue())
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
var counter = Counter.builder("roompicker_picks")
|
var counter = Counter.builder("roompicker_picks")
|
||||||
.tag("nodeId", nodeId.getValue())
|
.tag("instanceId", nodeId.getValue())
|
||||||
.baseUnit("1s")
|
.baseUnit("1s")
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId.getValue()).locked())
|
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId.getValue()).locked())
|
||||||
.tag("nodeId", nodeId.getValue())
|
.tag("instanceId", nodeId.getValue())
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance.getId()).size())
|
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance.getId()).size())
|
||||||
.tag("nodeId", nodeId.getValue())
|
.tag("instanceId", nodeId.getValue())
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|
||||||
data.put(nodeId.getValue(), new NodeData(gauge, new AtomicInteger(0), counter, new AtomicInteger(0), lockedGauge, roomsGauge));
|
data.put(nodeId.getValue(), new NodeData(gauge, new AtomicInteger(0), counter, new AtomicInteger(0), lockedGauge, roomsGauge));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user