refactored control panel
This commit is contained in:
parent
0c19050436
commit
93226022b2
@ -30,7 +30,6 @@ dependencies {
|
||||
implementation project(":client-impl")
|
||||
|
||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
|
||||
compileOnly '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.data.value.ValueChangeMode;
|
||||
import com.vaadin.flow.theme.lumo.LumoIcon;
|
||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
||||
import ru.dragonestia.picker.api.repository.AccountRepository;
|
||||
import ru.dragonestia.picker.cp.model.Permission;
|
||||
import 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.account.Permission;
|
||||
import ru.dragonestia.picker.cp.util.PermissionDescription;
|
||||
import ru.dragonestia.picker.cp.util.Notifications;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
private final AccountRepository accountRepository;
|
||||
private final RoomPickerClient client;
|
||||
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) {
|
||||
this.accountRepository = accountRepository;
|
||||
public AccountList(RoomPickerClient client) {
|
||||
this.client = client;
|
||||
|
||||
add(searchField = createSearchField());
|
||||
add(grid = createGridAccounts());
|
||||
@ -42,7 +44,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
}
|
||||
|
||||
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.setClearButtonVisible(true);
|
||||
field.setHelperText("Press Enter to search");
|
||||
@ -52,11 +54,11 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
return field;
|
||||
}
|
||||
|
||||
private Grid<ResponseAccount> createGridAccounts() {
|
||||
var grid = new Grid<>(ResponseAccount.class, false);
|
||||
private Grid<Account> createGridAccounts() {
|
||||
var grid = new Grid<>(Account.class, false);
|
||||
|
||||
grid.addColumn(ResponseAccount::getUsername).setHeader("Username")
|
||||
.setComparator(Comparator.comparing(ResponseAccount::getUsername)).setSortable(true);
|
||||
grid.addColumn(Account::id).setHeader("Username")
|
||||
.setComparator(Comparator.comparing(account -> account.id().getValue())).setSortable(true);
|
||||
|
||||
grid.addComponentColumn(this::createAccountManagementButtons).setFrozenToEnd(true)
|
||||
.setTextAlign(ColumnTextAlign.END).setHeader(createToolItems());
|
||||
@ -66,7 +68,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
return grid;
|
||||
}
|
||||
|
||||
private HorizontalLayout createAccountManagementButtons(ResponseAccount account) {
|
||||
private HorizontalLayout createAccountManagementButtons(Account account) {
|
||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||
|
||||
{
|
||||
@ -74,7 +76,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
button.addClickListener(event -> {
|
||||
getUI().ifPresent(ui -> {
|
||||
ui.navigate("/admin/accounts/" + account.getUsername());
|
||||
ui.navigate("/admin/accounts/" + account.id());
|
||||
});
|
||||
});
|
||||
layout.add(button);
|
||||
@ -101,7 +103,8 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
cachedAccounts = accountRepository.allAccounts();
|
||||
var ids = client.getAccountRepository().allAccountsIds();
|
||||
cachedAccounts = client.getAccountRepository().getAccounts(ids);
|
||||
applySearch(searchField.getValue());
|
||||
}
|
||||
|
||||
@ -109,7 +112,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
var temp = input.trim();
|
||||
|
||||
grid.setItems(cachedAccounts.stream()
|
||||
.filter(account -> account.getUsername().startsWith(temp))
|
||||
.filter(account -> account.id().getValue().startsWith(temp))
|
||||
.toList());
|
||||
}
|
||||
|
||||
@ -123,9 +126,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
var layout = new VerticalLayout();
|
||||
|
||||
var fieldUsername = new TextField("Account username");
|
||||
fieldUsername.setWidth(70, Unit.PERCENTAGE);
|
||||
layout.add(fieldUsername);
|
||||
var fieldEntityname = new TextField("Username");
|
||||
fieldEntityname.setWidth(70, Unit.PERCENTAGE);
|
||||
layout.add(fieldEntityname);
|
||||
|
||||
var fieldPassword = new PasswordField("Password");
|
||||
fieldPassword.setWidth(70, Unit.PERCENTAGE);
|
||||
@ -138,7 +141,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
layout.add(new H3("Permissions"));
|
||||
|
||||
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);
|
||||
permissionsList.add(comp);
|
||||
layout.add(comp);
|
||||
@ -149,7 +154,7 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
button.setWidth(100, Unit.PERCENTAGE);
|
||||
button.addClickListener(event -> {
|
||||
validateAndRegister(dialog, fieldUsername, fieldPassword, fieldConfirmPassword, permissionsList);
|
||||
validateAndRegister(dialog, fieldEntityname, fieldPassword, fieldConfirmPassword, permissionsList);
|
||||
});
|
||||
dialog.getFooter().add(button);
|
||||
}
|
||||
@ -162,13 +167,13 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
private void validateAndRegister(Dialog dialog, TextField usernameField, PasswordField passwordField, PasswordField confirmPasswordField, List<PermissionCheckBox> permissionCheckBoxes) {
|
||||
var username = usernameField.getValue().trim();
|
||||
private void validateAndRegister(Dialog dialog, TextField entitynameField, PasswordField passwordField, PasswordField confirmPasswordField, List<PermissionCheckBox> permissionCheckBoxes) {
|
||||
var entityname = entitynameField.getValue().trim();
|
||||
var password = passwordField.getValue();
|
||||
var confirmPassword = confirmPasswordField.getValue();
|
||||
|
||||
if (username.length() < 3 || username.length() > 32) {
|
||||
Notifications.error("Invalid username length. Valid is 3-32");
|
||||
if (entityname.length() < 3 || entityname.length() > 32) {
|
||||
Notifications.error("Invalid entityname length. Valid is 3-32");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -185,10 +190,9 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
var permissions = permissionCheckBoxes.stream()
|
||||
.filter(AbstractField::getValue)
|
||||
.map(PermissionCheckBox::getOption)
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.toSet());
|
||||
.toList();
|
||||
|
||||
accountRepository.createAccount(username, password, permissions);
|
||||
client.getAccountRepository().createAccount(AccountId.of(entityname), password, permissions);
|
||||
|
||||
dialog.close();
|
||||
refresh();
|
||||
@ -196,14 +200,14 @@ public class AccountList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
public static class PermissionCheckBox extends Checkbox {
|
||||
|
||||
private final Permission.Enum option;
|
||||
private final Permission option;
|
||||
|
||||
public PermissionCheckBox(Permission.Enum option) {
|
||||
super(option.getDescription());
|
||||
public PermissionCheckBox(Permission option) {
|
||||
super(PermissionDescription.of(option));
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
public Permission.Enum getOption() {
|
||||
public Permission getOption() {
|
||||
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.textfield.TextField;
|
||||
import com.vaadin.flow.data.value.ValueChangeMode;
|
||||
import ru.dragonestia.picker.api.model.node.INode;
|
||||
import ru.dragonestia.picker.api.model.node.NodeDetails;
|
||||
import ru.dragonestia.picker.api.repository.InstanceRepository;
|
||||
import ru.dragonestia.picker.api.repository.query.node.GetAllNodes;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||
import ru.dragonestia.picker.cp.repository.dto.InstanceDTO;
|
||||
import ru.dragonestia.picker.cp.repository.graphql.AllInstances;
|
||||
import ru.dragonestia.picker.cp.util.Notifications;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class NodeList extends VerticalLayout implements RefreshableTable {
|
||||
public class InstanceList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
private final InstanceRepository instanceRepository;
|
||||
private final Grid<INode> nodesGrid;
|
||||
private final RoomPickerClient client;
|
||||
private final Grid<InstanceDTO> instancesGrid;
|
||||
private final TextField searchField;
|
||||
private List<INode> cachedNodes;
|
||||
private List<InstanceDTO> cachedInstances;
|
||||
|
||||
public NodeList(InstanceRepository instanceRepository) {
|
||||
super();
|
||||
this.instanceRepository = instanceRepository;
|
||||
public InstanceList(RoomPickerClient client) {
|
||||
this.client = client;
|
||||
|
||||
add(new H2("Nodes"));
|
||||
add(new H2("Instances"));
|
||||
add(searchField = createSearchField());
|
||||
add(nodesGrid = createGrid());
|
||||
add(instancesGrid = createGrid());
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private TextField createSearchField() {
|
||||
var field = new TextField("Search node");
|
||||
var field = new TextField("Search instance");
|
||||
field.setPrefixComponent(new Icon(VaadinIcon.SEARCH));
|
||||
field.setClearButtonVisible(true);
|
||||
field.setHelperText("Press Enter to search");
|
||||
@ -53,25 +53,28 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
||||
private void applySearch(String input) {
|
||||
var temp = input.trim();
|
||||
|
||||
nodesGrid.setItems(cachedNodes.stream()
|
||||
.filter(node -> node.getIdentifier().startsWith(temp))
|
||||
.toList());
|
||||
var instances = cachedInstances.stream()
|
||||
.filter(instance -> instance.getId().startsWith(temp))
|
||||
.map(instance -> (InstanceDTO) instance)
|
||||
.toList();
|
||||
|
||||
instancesGrid.setItems(instances);
|
||||
}
|
||||
|
||||
private Grid<INode> createGrid() {
|
||||
var grid = new Grid<>(INode.class, false);
|
||||
private Grid<InstanceDTO> createGrid() {
|
||||
var grid = new Grid<>(InstanceDTO.class, false);
|
||||
|
||||
grid.addComponentColumn(node -> {
|
||||
if (Boolean.parseBoolean(node.getDetail(NodeDetails.PERSIST))) {
|
||||
return new Span(node.getIdentifier());
|
||||
grid.addComponentColumn(instance -> {
|
||||
if (instance.isPersist()) {
|
||||
return new Span(instance.getId());
|
||||
}
|
||||
|
||||
var result = new Span(node.getIdentifier());
|
||||
var result = new Span(instance.getId());
|
||||
result.add(grayBadge("(temp)"));
|
||||
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)
|
||||
.setTextAlign(ColumnTextAlign.END).setHeader(createRefreshButton());
|
||||
@ -80,33 +83,33 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
||||
return grid;
|
||||
}
|
||||
|
||||
private HorizontalLayout createManageButtons(INode node) {
|
||||
private HorizontalLayout createManageButtons(InstanceDTO instance) {
|
||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||
|
||||
{
|
||||
var button = new Button("Details");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
button.addClickListener(event -> clickDetailsButton(node));
|
||||
button.addClickListener(event -> clickDetailsButton(instance));
|
||||
layout.add(button);
|
||||
}
|
||||
|
||||
{
|
||||
var button = new Button("Remove");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
button.addClickListener(event -> clickRemoveButton(node));
|
||||
button.addClickListener(event -> clickRemoveButton(instance));
|
||||
layout.add(button);
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void clickDetailsButton(INode node) {
|
||||
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.getIdentifier()));
|
||||
private void clickDetailsButton(InstanceDTO instance) {
|
||||
getUI().ifPresent(ui -> ui.navigate("/instances/" + instance.getId()));
|
||||
}
|
||||
|
||||
private void clickRemoveButton(INode node) {
|
||||
var dialog = new Dialog("Confirm node 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>"));
|
||||
private void clickRemoveButton(InstanceDTO instance) {
|
||||
var dialog = new Dialog("Confirm instance deletion");
|
||||
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();
|
||||
inputField.setWidth("100%");
|
||||
@ -116,13 +119,13 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
||||
var button = new Button("Confirm");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
button.addClickListener(event -> {
|
||||
if (!node.getIdentifier().equals(inputField.getValue())) {
|
||||
if (!instance.getId().equals(inputField.getValue())) {
|
||||
Notifications.error("Invalid input");
|
||||
return;
|
||||
}
|
||||
|
||||
removeNode(node);
|
||||
Notifications.success("Node <b>" + node.getIdentifier() + "</b> was successfully removed!");
|
||||
removeInstance(instance);
|
||||
Notifications.success("Instance <b>" + instance.getId() + "</b> was successfully removed!");
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
@ -138,14 +141,16 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
private void removeNode(INode node) {
|
||||
instanceRepository.removeNode(node);
|
||||
private void removeInstance(InstanceDTO instance) {
|
||||
client.getInstanceRepository().deleteInstance(InstanceId.of(instance.getId()));
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
|
||||
@ -58,19 +58,19 @@ public class NavPath extends HorizontalLayout{
|
||||
return button;
|
||||
}
|
||||
|
||||
public static NavPath rootNodes() {
|
||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"));
|
||||
public static NavPath rootInstances() {
|
||||
return new NavPath(new NavPath.Point("Instances", "/instances"));
|
||||
}
|
||||
|
||||
public static NavPath toNode(String nodeId) {
|
||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"),
|
||||
new NavPath.Point(nodeId, "/nodes/" + nodeId));
|
||||
public static NavPath toInstance(String instanceId) {
|
||||
return new NavPath(new NavPath.Point("Instances", "/instances"),
|
||||
new NavPath.Point(instanceId, "/instances/" + instanceId));
|
||||
}
|
||||
|
||||
public static NavPath toRoom(String nodeId, String roomId) {
|
||||
return new NavPath(new NavPath.Point("Nodes", "/nodes"),
|
||||
new NavPath.Point(nodeId, "/nodes/" + nodeId),
|
||||
new NavPath.Point(roomId, "/nodes/" + nodeId + "/rooms/" + roomId));
|
||||
public static NavPath toRoom(String instanceId, String roomId) {
|
||||
return new NavPath(new NavPath.Point("Instances", "/instances"),
|
||||
new NavPath.Point(instanceId, "/instances/" + instanceId),
|
||||
new NavPath.Point(roomId, "/instances/" + instanceId + "/rooms/" + roomId));
|
||||
}
|
||||
|
||||
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.data.renderer.ComponentRenderer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import ru.dragonestia.picker.api.model.node.NodeDefinition;
|
||||
import ru.dragonestia.picker.api.model.node.PickingMethod;
|
||||
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
|
||||
import ru.dragonestia.picker.api.exception.InvalidIdentifierException;
|
||||
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||
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;
|
||||
|
||||
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 RadioButtonGroup<PickingMethod> modeRadio;
|
||||
private final Checkbox persistField;
|
||||
|
||||
public RegisterNode(Function<NodeDefinition, Response> onSubmit) {
|
||||
super(new H2("Register node"));
|
||||
public RegisterInstance(Function<InstanceDTO, Response> onSubmit) {
|
||||
super(new H2("Register instance"));
|
||||
this.onSubmit = onSubmit;
|
||||
|
||||
var layout = new VerticalLayout();
|
||||
layout.add(identifierField = createNodeIdentifierField());
|
||||
layout.add(identifierField = createInstanceIdentifierField());
|
||||
layout.add(modeRadio = createModeRadio());
|
||||
layout.add(persistField = createPersistField());
|
||||
layout.add(createSubmitButton());
|
||||
@ -41,10 +43,10 @@ public class RegisterNode extends Details {
|
||||
add(layout);
|
||||
}
|
||||
|
||||
private TextField createNodeIdentifierField() {
|
||||
private TextField createInstanceIdentifierField() {
|
||||
var field = new TextField("Identifier");
|
||||
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.setPattern("^[a-z\\d-]+$");
|
||||
field.setRequired(true);
|
||||
@ -83,36 +85,61 @@ public class RegisterNode extends Details {
|
||||
|
||||
private @Nullable String validateForm(String identifier) {
|
||||
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;
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
String nodeIdentifier = identifierField.getValue();
|
||||
String instanceIdentifier = identifierField.getValue();
|
||||
|
||||
String error = null;
|
||||
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
|
||||
if (identifierField.isInvalid() || (error = validateForm(instanceIdentifier)) != null) {
|
||||
if (identifierField.isInvalid()) {
|
||||
error = "Invalid node id format";
|
||||
error = "Invalid instance id format";
|
||||
}
|
||||
|
||||
Notifications.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var node = new NodeDefinition(NodeIdentifier.of(nodeIdentifier))
|
||||
.setPickingMethod(modeRadio.getValue())
|
||||
.setPersist(persistField.getValue());
|
||||
var response = onSubmit.apply(node);
|
||||
var response = onSubmit.apply(new InstanceDTO() {
|
||||
|
||||
@Override
|
||||
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();
|
||||
if (response.error()) {
|
||||
Notifications.error(response.reason());
|
||||
return;
|
||||
}
|
||||
|
||||
Notifications.success("Node was successfully registered");
|
||||
Notifications.success("Instance was successfully registered");
|
||||
}
|
||||
|
||||
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.TextField;
|
||||
import org.springframework.lang.Nullable;
|
||||
import ru.dragonestia.picker.api.model.node.INode;
|
||||
import ru.dragonestia.picker.api.model.room.IRoom;
|
||||
import ru.dragonestia.picker.api.model.room.RoomDefinition;
|
||||
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
|
||||
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||
import ru.dragonestia.picker.api.model.room.Room;
|
||||
import ru.dragonestia.picker.api.model.room.RoomId;
|
||||
import ru.dragonestia.picker.cp.util.Notifications;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RegisterRoom extends Details {
|
||||
|
||||
private final INode node;
|
||||
private final Function<RoomDefinition, Response> onSubmit;
|
||||
private final Instance instance;
|
||||
private final Function<Room, Response> onSubmit;
|
||||
private final TextField identifierField;
|
||||
private final TextArea payloadField;
|
||||
private final Checkbox lockedField;
|
||||
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"));
|
||||
this.node = node;
|
||||
this.instance = instance;
|
||||
this.onSubmit = onSubmit;
|
||||
|
||||
var layout = new VerticalLayout();
|
||||
layout.add(createNodeIdentifierField());
|
||||
layout.add(createInstanceIdentifierField());
|
||||
layout.add(identifierField = createRoomIdentifierField());
|
||||
layout.add(payloadField = createPayloadField());
|
||||
layout.add(lockedField = createLockedField());
|
||||
@ -43,10 +43,10 @@ public class RegisterRoom extends Details {
|
||||
add(layout);
|
||||
}
|
||||
|
||||
private TextField createNodeIdentifierField() {
|
||||
var field = new TextField("Node identifier");
|
||||
private TextField createInstanceIdentifierField() {
|
||||
var field = new TextField("Instance identifier");
|
||||
field.setMinWidth(20, Unit.REM);
|
||||
field.setValue(node.getIdentifier());
|
||||
field.setValue(instance.id().getValue());
|
||||
field.setReadOnly(true);
|
||||
return field;
|
||||
}
|
||||
@ -97,7 +97,7 @@ public class RegisterRoom extends Details {
|
||||
|
||||
private @Nullable String validateForm(String identifier) {
|
||||
if (identifier.isEmpty()) {
|
||||
return "Node identifier cannot be empty";
|
||||
return "Instance identifier cannot be empty";
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -116,13 +116,7 @@ public class RegisterRoom extends Details {
|
||||
return;
|
||||
}
|
||||
|
||||
var room = new RoomDefinition(node.getIdentifierObject(), RoomIdentifier.of(roomId))
|
||||
.setMaxSlots(IRoom.UNLIMITED_SLOTS)
|
||||
.setPayload(payloadField.getValue())
|
||||
.setPersist(persistField.getValue());
|
||||
|
||||
room.setLocked(lockedField.getValue());
|
||||
var response = onSubmit.apply(room);
|
||||
var response = onSubmit.apply(new Room(RoomId.of(roomId), instance.id(), -1, lockedField.getValue(), payloadField.getValue(), persistField.getValue()));
|
||||
clear();
|
||||
if (response.error()) {
|
||||
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.data.value.ValueChangeMode;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import ru.dragonestia.picker.api.model.node.INode;
|
||||
import ru.dragonestia.picker.api.model.room.RoomDetails;
|
||||
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
|
||||
import ru.dragonestia.picker.api.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.api.repository.query.room.GetAllRooms;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.model.instance.Instance;
|
||||
import ru.dragonestia.picker.api.model.instance.InstanceId;
|
||||
import ru.dragonestia.picker.api.model.room.RoomId;
|
||||
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;
|
||||
|
||||
@Log4j2
|
||||
public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
private final INode node;
|
||||
private final RoomRepository roomRepository;
|
||||
private final Grid<ShortResponseRoom> roomsGrid;
|
||||
private final Instance instance;
|
||||
private final RoomPickerClient client;
|
||||
private final Grid<RoomDTO> roomsGrid;
|
||||
private final TextField searchField;
|
||||
private List<ShortResponseRoom> cachedRooms;
|
||||
private final Span totalUsers = new Span();
|
||||
private List<RoomDTO> cachedRooms;
|
||||
private final Span totalEntities = new Span();
|
||||
|
||||
public RoomList(INode node, RoomRepository roomRepository) {
|
||||
this.node = node;
|
||||
this.roomRepository = roomRepository;
|
||||
public RoomList(Instance instance, RoomPickerClient client) {
|
||||
this.instance = instance;
|
||||
this.client = client;
|
||||
|
||||
add(new H2("Rooms"));
|
||||
add(searchField = createSearchField());
|
||||
@ -58,39 +62,39 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
var temp = input.trim();
|
||||
|
||||
roomsGrid.setItems(cachedRooms.stream()
|
||||
.filter(room -> room.getIdentifier().startsWith(temp))
|
||||
.filter(room -> room.getId().startsWith(temp))
|
||||
.toList());
|
||||
}
|
||||
|
||||
private Grid<ShortResponseRoom> createGrid() {
|
||||
var grid = new Grid<>(ShortResponseRoom.class, false);
|
||||
private Grid<RoomDTO> createGrid() {
|
||||
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 -> {
|
||||
var result = new Span();
|
||||
if (room.getMaxSlots() == -1) {
|
||||
if (room.getSlots() == -1) {
|
||||
result.setText("Unlimited");
|
||||
result.getElement().getThemeList().add("badge contrast");
|
||||
} else {
|
||||
result.setText(Integer.toString(room.getMaxSlots()));
|
||||
result.setText(Integer.toString(room.getSlots()));
|
||||
}
|
||||
return result;
|
||||
}).setHeader("Slots").setComparator((room1, room2) -> {
|
||||
var r1 = room1.hasUnlimitedSlots()? Integer.MAX_VALUE : room1.getMaxSlots();
|
||||
var r2 = room2.hasUnlimitedSlots()? Integer.MAX_VALUE : room2.getMaxSlots();
|
||||
var r1 = room1.getSlots() == 1? Integer.MAX_VALUE : room1.getSlots();
|
||||
var r2 = room2.getSlots() == 1? Integer.MAX_VALUE : room2.getSlots();
|
||||
|
||||
return Integer.compare(r1, r2);
|
||||
}).setSortable(true).setTextAlign(ColumnTextAlign.CENTER);
|
||||
|
||||
grid.addColumn(this::getUsers).setHeader("Users")
|
||||
.setComparator((room1, room2) -> Integer.compare(getUsers(room1), getUsers(room2))).setSortable(true)
|
||||
.setTextAlign(ColumnTextAlign.CENTER).setFooter(totalUsers);
|
||||
grid.addColumn(RoomDTO::getCountEntities).setHeader("Entities")
|
||||
.setComparator(Comparator.comparingInt(RoomDTO::getCountEntities)).setSortable(true)
|
||||
.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) -> {
|
||||
var p1 = UserList.getUsingPercentage(room1.getMaxSlots(), getUsers(room1));
|
||||
var p2 = UserList.getUsingPercentage(room2.getMaxSlots(), getUsers(room2));
|
||||
var p1 = UsingSlots.getUsingPercentage(room1.getSlots(), room1.getCountEntities());
|
||||
var p2 = UsingSlots.getUsingPercentage(room2.getSlots(), room2.getCountEntities());
|
||||
|
||||
return Integer.compare(p1, p2);
|
||||
}).setHeader("Occupancy").setTextAlign(ColumnTextAlign.CENTER);
|
||||
@ -114,7 +118,7 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
return grid;
|
||||
}
|
||||
|
||||
private HorizontalLayout createManageButtons(ShortResponseRoom room) {
|
||||
private HorizontalLayout createManageButtons(RoomDTO room) {
|
||||
var layout = new HorizontalLayout(JustifyContentMode.END);
|
||||
|
||||
{
|
||||
@ -134,15 +138,15 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
return layout;
|
||||
}
|
||||
|
||||
private void clickDetailsButton(ShortResponseRoom room) {
|
||||
private void clickDetailsButton(RoomDTO room) {
|
||||
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");
|
||||
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();
|
||||
inputField.setWidth("100%");
|
||||
@ -152,13 +156,13 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
var button = new Button("Confirm");
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||
button.addClickListener(event -> {
|
||||
if (!room.getIdentifier().equals(inputField.getValue())) {
|
||||
if (!room.getId().equals(inputField.getValue())) {
|
||||
Notifications.error("Invalid input");
|
||||
return;
|
||||
}
|
||||
|
||||
removeRoom(room);
|
||||
Notifications.success("Room <b>" + room.getIdentifier() + "</b> was successfully removed!");
|
||||
Notifications.success("Room <b>" + room.getId() + "</b> was successfully removed!");
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
@ -174,31 +178,23 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
public void removeRoom(ShortResponseRoom room) {
|
||||
roomRepository.removeRoom(room);
|
||||
public void removeRoom(RoomDTO room) {
|
||||
client.getRoomRepository().deleteRoom(InstanceId.of(room.getInstanceId()), RoomId.of(room.getId()));
|
||||
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
|
||||
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());
|
||||
|
||||
int users = 0;
|
||||
int entities = 0;
|
||||
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.context.annotation.Bean;
|
||||
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.model.Account;
|
||||
import ru.dragonestia.picker.cp.model.provider.AccountProvider;
|
||||
import ru.dragonestia.picker.cp.util.AdminRoomPickerClient;
|
||||
|
||||
@Configuration
|
||||
public class RoomPickerConfig {
|
||||
@ -15,25 +11,9 @@ public class RoomPickerConfig {
|
||||
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080}")
|
||||
private String serverUrl;
|
||||
|
||||
@Value("${ROOMPICKER_ADMIN_USERNAME:admin}")
|
||||
private String adminUsername;
|
||||
|
||||
@Value("${ROOMPICKER_ADMIN_PASSWORD:qwerty123}")
|
||||
private String adminPassword;
|
||||
|
||||
@ServerURL
|
||||
@Bean
|
||||
String severUrl() {
|
||||
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 ru.dragonestia.picker.api.exception.ApiException;
|
||||
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;
|
||||
|
||||
@ -21,30 +21,22 @@ public class ApplicationErrorHandler implements ErrorHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorEvent.getThrowable() instanceof ApiException ex) {
|
||||
execute(() -> {
|
||||
Notifications.error(ex.getMessage());
|
||||
});
|
||||
if (errorEvent.getThrowable().getClass().getAnnotation(ApiException.class) != null) {
|
||||
execute(() -> Notifications.error(errorEvent.getThrowable().getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorEvent.getThrowable() instanceof InvalidParameterException ex) {
|
||||
execute(() -> {
|
||||
Notifications.error(ex.getMessage());
|
||||
});
|
||||
execute(() -> Notifications.error(ex.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorEvent.getThrowable() instanceof NotEnoughPermissions) {
|
||||
execute(() -> {
|
||||
Notifications.error("Not enough permissions to this action");
|
||||
});
|
||||
execute(() -> Notifications.error("Not enough permissions to this action"));
|
||||
return;
|
||||
}
|
||||
|
||||
execute(() -> {
|
||||
Notifications.error("Internal server error");
|
||||
});
|
||||
execute(() -> Notifications.error("Internal server error"));
|
||||
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.VaadinServiceInitListener;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import ru.dragonestia.picker.cp.error.ApplicationErrorHandler;
|
||||
|
||||
@Log4j2
|
||||
@SpringComponent
|
||||
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.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.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.H3;
|
||||
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.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.account.IAccount;
|
||||
import com.vaadin.flow.router.*;
|
||||
import ru.dragonestia.picker.api.model.account.Account;
|
||||
import ru.dragonestia.picker.api.model.account.Permission;
|
||||
import ru.dragonestia.picker.cp.component.AccountList;
|
||||
import ru.dragonestia.picker.cp.component.Notifications;
|
||||
import ru.dragonestia.picker.cp.model.Permission;
|
||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
||||
import ru.dragonestia.picker.cp.service.SessionService;
|
||||
import ru.dragonestia.picker.cp.util.Notifications;
|
||||
import ru.dragonestia.picker.cp.util.RouteParamExtractor;
|
||||
import ru.dragonestia.picker.cp.view.layout.MainLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@RolesAllowed("ADMIN")
|
||||
@PageTitle("Account details")
|
||||
@PageTitle("UserDetails details")
|
||||
@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 final RouteParamsExtractor paramsExtractor;
|
||||
private Account account;
|
||||
|
||||
private RoomPickerClient client;
|
||||
private IAccount account;
|
||||
|
||||
@PostConstruct
|
||||
void postConstruct() {
|
||||
client = securityService.getAuthenticatedAccount().getClient();
|
||||
public AccountView(SessionService sessionService, RouteParamExtractor paramsExtractor) {
|
||||
super(sessionService, paramsExtractor, Permission.ADMIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeEnter(BeforeEnterEvent event) {
|
||||
account = paramsExtractor.extractAccount(event);
|
||||
|
||||
init();
|
||||
protected void preRender(RouteParameters routeParams) {
|
||||
account = getParamsExtractor().account(routeParams);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
add(new H2("Account management"));
|
||||
add(new Html("<span>Username: <b>%s</b></span>".formatted(account.getUsername())));
|
||||
@Override
|
||||
protected void render() {
|
||||
add(new H2("UserDetails management"));
|
||||
add(new Html("<span>Entityname: <b>%s</b></span>".formatted(account.id())));
|
||||
|
||||
add(new Hr());
|
||||
add(new H3("Change password"));
|
||||
@ -91,7 +76,7 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
||||
return;
|
||||
}
|
||||
|
||||
client.getAccountRepository().setPassword(account, pass);
|
||||
getClient().getAccountRepository().changePassword(account, pass);
|
||||
Notifications.success("Password successfully changed!");
|
||||
newPassword.setValue("");
|
||||
confirmPassword.setValue("");
|
||||
@ -102,9 +87,11 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
||||
|
||||
private void createEditPermissions() {
|
||||
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);
|
||||
comp.setValue(account.getPermissions().contains(permission.name()));
|
||||
comp.setValue(account.permissions().contains(permission));
|
||||
permissionsList.add(comp);
|
||||
add(comp);
|
||||
}
|
||||
@ -113,10 +100,9 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
||||
var permissions = permissionsList.stream()
|
||||
.filter(AbstractField::getValue)
|
||||
.map(AccountList.PermissionCheckBox::getOption)
|
||||
.map(Enum::name)
|
||||
.toList();
|
||||
|
||||
client.getAccountRepository().setPermissions(account, permissions);
|
||||
getClient().getAccountRepository().setPermissions(account, permissions);
|
||||
Notifications.success("Permissions successfully changed!");
|
||||
});
|
||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||
@ -125,8 +111,8 @@ public class AccountDetailsPage extends VerticalLayout implements BeforeEnterObs
|
||||
|
||||
private Button createDeleteAccountButton() {
|
||||
var button = new Button("Delete this account", event -> {
|
||||
client.getAccountRepository().removeAccount(account);
|
||||
Notifications.warn("Account '%s' was deleted.".formatted(account.getUsername()));
|
||||
getClient().getAccountRepository().deleteAccount(account.id());
|
||||
Notifications.warn("UserDetails '%s' was deleted.".formatted(account.id()));
|
||||
|
||||
getUI().ifPresent(ui -> {
|
||||
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.Html;
|
||||
@ -16,36 +16,45 @@ import com.vaadin.flow.component.orderedlayout.Scroller;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.sidenav.SideNav;
|
||||
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.cp.annotation.ServerURL;
|
||||
import ru.dragonestia.picker.cp.model.Account;
|
||||
import ru.dragonestia.picker.cp.service.SecurityService;
|
||||
import ru.dragonestia.picker.cp.model.AccountSession;
|
||||
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 {
|
||||
|
||||
private final SecurityService securityService;
|
||||
private final RoomPickerInfoResponse serverInfo;
|
||||
private final SessionService sessionService;
|
||||
private final String serverUrl;
|
||||
private final Account account;
|
||||
private final AccountSession session;
|
||||
private final RoomPickerInfoResponse info;
|
||||
private final boolean isAdmin;
|
||||
|
||||
public MainLayout(SecurityService securityService, RoomPickerClient adminClient, @ServerURL String serverUrl) {
|
||||
this.securityService = securityService;
|
||||
this.serverInfo = adminClient.getServerInfo();
|
||||
this.serverUrl = serverUrl;
|
||||
account = securityService.getAuthenticatedAccount();
|
||||
isAdmin = securityService.hasRole("ADMIN");
|
||||
public MainLayout(SessionService sessionService, @ServerURL String serverURL) {
|
||||
this.sessionService = sessionService;
|
||||
this.serverUrl = serverURL;
|
||||
this.session = sessionService.getSession();
|
||||
|
||||
var toggle = new DrawerToggle();
|
||||
var scroller = new Scroller(createSideNav());
|
||||
scroller.setWidth(100, Unit.PERCENTAGE);
|
||||
if (session == null) {
|
||||
info = null;
|
||||
isAdmin = false;
|
||||
} else {
|
||||
info = session.getClient().getServerInfo();
|
||||
isAdmin = session.getData().permissions().contains(Permission.ADMIN);
|
||||
|
||||
var navLayout = new VerticalLayout(createAccountButtons(), new Hr(), scroller);
|
||||
navLayout.setPadding(false);
|
||||
var toggle = new DrawerToggle();
|
||||
var scroller = new Scroller(createSideNav());
|
||||
scroller.setWidth(100, Unit.PERCENTAGE);
|
||||
|
||||
addToDrawer(navLayout);
|
||||
addToNavbar(toggle, createLogo());
|
||||
var navLayout = new VerticalLayout(createAccountButtons(), new Hr(), scroller);
|
||||
navLayout.setPadding(false);
|
||||
|
||||
addToDrawer(navLayout);
|
||||
addToNavbar(toggle, createLogo());
|
||||
}
|
||||
}
|
||||
|
||||
private Component createLogo() {
|
||||
@ -53,17 +62,17 @@ public class MainLayout extends AppLayout {
|
||||
layout.setAlignItems(FlexComponent.Alignment.END);
|
||||
layout.setPadding(true);
|
||||
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;
|
||||
}
|
||||
|
||||
private Component createAccountButtons() {
|
||||
var layout = new VerticalLayout();
|
||||
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);
|
||||
|
||||
var logoutButton = new Button("Logout", event -> securityService.logout());
|
||||
var logoutButton = new Button("Logout", event -> getUI().ifPresent(sessionService::logout));
|
||||
logoutButton.setWidth(100, Unit.PERCENTAGE);
|
||||
layout.add(logoutButton);
|
||||
|
||||
@ -72,10 +81,10 @@ public class MainLayout extends AppLayout {
|
||||
|
||||
private SideNav createSideNav() {
|
||||
var nav = new SideNav();
|
||||
nav.addItem(new SideNavItem("Nodes list", NodesPage.class, VaadinIcon.FOLDER_O.create()));
|
||||
nav.addItem(new SideNavItem("Search users", UserSearchPage.class, VaadinIcon.SEARCH.create()));
|
||||
nav.addItem(new SideNavItem("Instances list", AllInstancesView.class, VaadinIcon.FOLDER_O.create()));
|
||||
nav.addItem(new SideNavItem("Search entities", SearchEntityView.class, VaadinIcon.SEARCH.create()));
|
||||
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("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.H1;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.ParentLayout;
|
||||
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)
|
||||
public abstract class ErrorPlug extends VerticalLayout {
|
||||
|
||||
public void init(NavPath path, String title, String description) {
|
||||
add(path);
|
||||
public void init(String title, String description) {
|
||||
add(new H1(title));
|
||||
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.Paragraph;
|
||||
@ -6,7 +6,6 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.ErrorParameter;
|
||||
import com.vaadin.flow.router.HasErrorParameter;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import ru.dragonestia.picker.api.impl.exception.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) {
|
||||
var nodeId = instance.getId();
|
||||
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId.getValue()).users())
|
||||
.tag("nodeId", nodeId.getValue())
|
||||
.tag("instanceId", nodeId.getValue())
|
||||
.register(meterRegistry);
|
||||
|
||||
var counter = Counter.builder("roompicker_picks")
|
||||
.tag("nodeId", nodeId.getValue())
|
||||
.tag("instanceId", nodeId.getValue())
|
||||
.baseUnit("1s")
|
||||
.register(meterRegistry);
|
||||
|
||||
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId.getValue()).locked())
|
||||
.tag("nodeId", nodeId.getValue())
|
||||
.tag("instanceId", nodeId.getValue())
|
||||
.register(meterRegistry);
|
||||
|
||||
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance.getId()).size())
|
||||
.tag("nodeId", nodeId.getValue())
|
||||
.tag("instanceId", nodeId.getValue())
|
||||
.register(meterRegistry);
|
||||
|
||||
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