Added Node control page (without implementation)

This commit is contained in:
Andrey Terentev 2023-11-16 20:27:20 +07:00
parent 0104db48f5
commit e55a16265a
8 changed files with 319 additions and 0 deletions

View File

@ -0,0 +1,44 @@
package ru.dragonestia.loadbalancer.web.component;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import ru.dragonestia.loadbalancer.web.model.Node;
import java.util.List;
public class NodeList extends VerticalLayout {
private final Grid<Node> nodesGrid;
public NodeList(List<Node> nodes) {
super();
add(new H2("Nodes"));
add(nodesGrid = createGrid());
update(nodes);
}
private Grid<Node> createGrid() {
var grid = new Grid<>(Node.class, false);
grid.addColumn(Node::identifier).setHeader("Identifier");
grid.addColumn(node -> node.method().getName()).setHeader("Mode");
return grid;
}
public void update(List<Node> nodes) {
nodesGrid.setItems(nodes);
}
private void registerNode(Node node) {
// TODO: send request for register node and get all nodes
update(List.of(node));
}
public interface OnRegister {
List<Node> register(Node node);
}
}

View File

@ -0,0 +1,109 @@
package ru.dragonestia.loadbalancer.web.component;
import com.vaadin.flow.component.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.html.H2;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.component.radiobutton.RadioGroupVariant;
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.loadbalancer.web.model.Node;
import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod;
import java.util.function.Function;
public class RegisterNode extends VerticalLayout {
private final Function<Node, Response> onSubmit;
private final TextField identifierField;
private final RadioButtonGroup<LoadBalancingMethod> modeRadio;
public RegisterNode(Function<Node, Response> onSubmit) {
this.onSubmit = onSubmit;
add(new H2("Register node"));
add(identifierField = createNodeIdentifierField());
add(modeRadio = createModeRadio());
add(createSubmitButton());
}
private TextField createNodeIdentifierField() {
var field = new TextField("Identifier");
field.setMinWidth(20, Unit.REM);
field.setPlaceholder("example-node-id");
field.setHelperText("The field can contain only lowercase letters, numbers and a dash character");
field.setPattern("^[a-z\\d-]+$");
field.setRequired(true);
field.setAutocomplete(Autocomplete.OFF);
field.addValueChangeListener(event -> field.setValue(event.getValue().trim()));
return field;
}
private Button createSubmitButton() {
var button = new Button("Register");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(event -> onClick());
return button;
}
private RadioButtonGroup<LoadBalancingMethod> createModeRadio() {
var radio = new RadioButtonGroup<LoadBalancingMethod>("Mode");
radio.addThemeVariants(RadioGroupVariant.LUMO_VERTICAL);
radio.setRenderer(new ComponentRenderer<Component, LoadBalancingMethod>(mode -> new Span(mode.getName())));
radio.setItems(LoadBalancingMethod.SEQUENTIAL_FILLING,
LoadBalancingMethod.ROUND_ROBIN,
LoadBalancingMethod.LEAST_PICKED);
radio.setValue(LoadBalancingMethod.SEQUENTIAL_FILLING);
return radio;
}
public void clear() {
identifierField.clear();
}
private @Nullable String validateForm(String identifier) {
if (identifier.isEmpty()) {
return "Node identifier cannot be empty";
}
return null;
}
private void onClick() {
String nodeIdentifier = identifierField.getValue();
String error = null;
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
if (identifierField.isInvalid()) {
error = "Invalid node identifier format";
}
Notification.show(error, 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
return;
}
var node = new Node(nodeIdentifier, modeRadio.getValue());
var response = onSubmit.apply(node);
clear();
if (response.error()) {
Notification.show(response.reason(), 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_ERROR);
return;
}
Notification.show("Node was successfully registered", 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
}
public record Response(boolean error, @Nullable String reason) {}
}

View File

@ -0,0 +1,46 @@
package ru.dragonestia.loadbalancer.web.model;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.loadbalancer.web.model.type.SlotLimit;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Bucket {
private final String identifier;
private final String nodeIdentifier;
private final SlotLimit slots;
private final String payload;
private boolean locked = false;
public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) {
return new Bucket(identifier, node.identifier(), limit, payload);
}
public void setLocked(boolean value) {
locked = value;
}
public boolean isAvailable(int usedSlots, int requiredSlots) {
if (locked) return false;
if (slots.isUnlimited()) return true;
return slots.getSlots() >= usedSlots + requiredSlots;
}
@Override
public int hashCode() {
return identifier.hashCode();
}
@Override
public boolean equals(Object object) {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Bucket other) {
return identifier.equals(other.identifier);
}
return false;
}
}

View File

@ -0,0 +1,22 @@
package ru.dragonestia.loadbalancer.web.model;
import lombok.NonNull;
import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod;
public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod method) {
@Override
public int hashCode() {
return identifier.hashCode();
}
@Override
public boolean equals(Object object) {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Node other) {
return identifier.equals(other.identifier);
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package ru.dragonestia.loadbalancer.web.model;
import lombok.NonNull;
public record User(@NonNull String identifier) {
@Override
public int hashCode() {
return identifier.hashCode();
}
@Override
public boolean equals(Object object) {
if (object == this) return true;
if (object == null) return false;
if (object instanceof User other) {
return identifier.equals(other.identifier);
}
return false;
}
}

View File

@ -0,0 +1,14 @@
package ru.dragonestia.loadbalancer.web.model.type;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum LoadBalancingMethod {
SEQUENTIAL_FILLING("Sequential filling"),
ROUND_ROBIN("Round Robin"),
LEAST_PICKED("Least Picked");
private final String name;
}

View File

@ -0,0 +1,27 @@
package ru.dragonestia.loadbalancer.web.model.type;
import lombok.Getter;
@Getter
public class SlotLimit {
private final static int UNLIMITED_VALUE = -1;
private final int slots;
private SlotLimit(int slots) {
this.slots = slots;
}
public static SlotLimit unlimited() {
return new SlotLimit(UNLIMITED_VALUE);
}
public static SlotLimit of(int slots) {
return new SlotLimit(slots);
}
public boolean isUnlimited() {
return slots == UNLIMITED_VALUE;
}
}

View File

@ -0,0 +1,36 @@
package ru.dragonestia.loadbalancer.web.page;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import lombok.Getter;
import ru.dragonestia.loadbalancer.web.component.NodeList;
import ru.dragonestia.loadbalancer.web.component.RegisterNode;
import java.util.List;
@Getter
@Route("/nodes")
public class NodesPage extends VerticalLayout {
private final RegisterNode registerNode;
private final NodeList nodeList;
public NodesPage() {
super();
add(registerNode = createRegisterNodeElement());
add(nodeList = createNodeListElement());
}
protected RegisterNode createRegisterNodeElement() {
return new RegisterNode(node -> {
nodeList.update(List.of(node)); // remove it later
return new RegisterNode.Response(false, "Some error"); // TODO
});
}
protected NodeList createNodeListElement() {
// TODO: getting nodes list
return new NodeList(List.of());
}
}