diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/BucketList.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/BucketList.java new file mode 100644 index 0000000..8a6d332 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/BucketList.java @@ -0,0 +1,142 @@ +package ru.dragonestia.loadbalancer.web.component; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.dialog.Dialog; +import com.vaadin.flow.component.grid.ColumnTextAlign; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Paragraph; +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.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import ru.dragonestia.loadbalancer.web.model.Bucket; + +import java.util.List; + +public class BucketList extends VerticalLayout { + + private final Grid bucketsGrid; + private final TextField searchField; + private List cachedBuckets; + + public BucketList(List buckets) { + cachedBuckets = buckets; + + add(new H2("Buckets")); + add(searchField = createSearchField()); + add(bucketsGrid = createGrid()); + + update(buckets); + } + + private TextField createSearchField() { + var field = new TextField("Search bucket"); + field.setPrefixComponent(new Icon(VaadinIcon.SEARCH)); + field.setClearButtonVisible(true); + field.setHelperText("Press Enter to search"); + field.addValueChangeListener(event -> applySearch(event.getValue())); + return field; + } + + private void applySearch(String input) { + var temp = input.trim(); + + bucketsGrid.setItems(cachedBuckets.stream() + .filter(bucket -> bucket.getIdentifier().startsWith(temp)) + .toList()); + } + + private Grid createGrid() { + var grid = new Grid<>(Bucket.class, false); + grid.addColumn(Bucket::getIdentifier).setHeader("Identifier"); + grid.addComponentColumn(bucket -> { + var result = new Span(); + if (bucket.getSlots().isUnlimited()) { + result.setText("Unlimited"); + result.getElement().getThemeList().add("badge contrast"); + } else { + result.setText(Integer.toString(bucket.getSlots().getSlots())); + } + return result; + }).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER); + grid.addComponentColumn(this::createManageButtons).setHeader("Manage"); + return grid; + } + + private HorizontalLayout createManageButtons(Bucket bucket) { + var layout = new HorizontalLayout(); + + { + var button = new Button("Details"); + button.addClickListener(event -> clickDetailsButton(bucket)); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + layout.add(button); + } + + { + var button = new Button("Remove"); + button.addClickListener(event -> clickRemoveButton(bucket)); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); + layout.add(button); + } + + return layout; + } + + private void clickDetailsButton(Bucket bucket) { + getUI().ifPresent(ui -> { + ui.navigate("/nodes/" + bucket.getNodeIdentifier() + + "/" + bucket.getIdentifier()); + }); + } + + private void clickRemoveButton(Bucket bucket) { + var dialog = new Dialog("Confirm bucket deletion"); + dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.getIdentifier() + "' to field below and confirm.")); + + var inputField = new TextField(); + dialog.add(inputField); + + { // confirm + var button = new Button("Confirm"); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); + button.addClickListener(event -> { + if (!bucket.getIdentifier().equals(inputField.getValue())) { + Notification.show("Invalid input", 3000, Notification.Position.TOP_END) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + + removeBucket(bucket); + Notification.show("Bucket '" + bucket.getIdentifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END) + .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + dialog.close(); + }); + + dialog.getFooter().add(button); + } + + { // cancel + var button = new Button("Cancel", event -> dialog.close()); + button.addThemeVariants(ButtonVariant.LUMO_TERTIARY); + dialog.getFooter().add(button); + } + + dialog.open(); + } + + public void update(List buckets) { + cachedBuckets = buckets; + applySearch(searchField.getValue()); + } + + private void removeBucket(Bucket bucket) { + // TODO: send remove request and getting list + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NavPath.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NavPath.java index 96c746e..b5c4195 100644 --- a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NavPath.java +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NavPath.java @@ -1,7 +1,6 @@ package ru.dragonestia.loadbalancer.web.component; import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.html.Span; diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java index af47982..999fda0 100644 --- a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java @@ -13,7 +13,6 @@ import com.vaadin.flow.component.notification.NotificationVariant; 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.component.textfield.TextFieldVariant; import ru.dragonestia.loadbalancer.web.model.Node; import java.util.List; @@ -82,7 +81,7 @@ public class NodeList extends VerticalLayout { } private void clickDetailsButton(Node node) { - // TODO + getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.identifier())); } private void clickRemoveButton(Node node) { diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodeDetailsPage.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodeDetailsPage.java new file mode 100644 index 0000000..e850e6a --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodeDetailsPage.java @@ -0,0 +1,47 @@ +package ru.dragonestia.loadbalancer.web.page; + +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 lombok.Getter; +import ru.dragonestia.loadbalancer.web.component.BucketList; +import ru.dragonestia.loadbalancer.web.component.NavPath; +import ru.dragonestia.loadbalancer.web.model.Bucket; +import ru.dragonestia.loadbalancer.web.model.Node; +import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod; +import ru.dragonestia.loadbalancer.web.model.type.SlotLimit; + +import java.util.List; + +@Getter +@PageTitle("Buckets") +@Route("/nodes/:nodeId") +public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver { + + private Node node; + private BucketList bucketList; + + @Override + public void beforeEnter(BeforeEnterEvent event) { + var nodeIdOpt = event.getRouteParameters().get("nodeId"); + if (nodeIdOpt.isEmpty()) { + getUI().ifPresent(ui -> ui.navigate("/nodes")); + return; + } + node = new Node(nodeIdOpt.get(), LoadBalancingMethod.ROUND_ROBIN); // TODO: getting node + + add(new NavPath(new NavPath.Point("Nodes", "/nodes"), + new NavPath.Point(node.identifier(), "/nodes/" + node.identifier()))); + + // TODO: getting buckets + add(bucketList = new BucketList(List.of( + Bucket.create("test-1", node, SlotLimit.unlimited(), "Hello world!"), + Bucket.create("test-2", node, SlotLimit.of(12), "Hello world!"), + Bucket.create("test-3", node, SlotLimit.unlimited(), "Hello world!"), + Bucket.create("test-4", node, SlotLimit.of(32), "Hello world!"), + Bucket.create("test-5", node, SlotLimit.of(54), "Hello world!") + ))); + } +}