/**
*
*/
package org.github.etcd.viewer.html.node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.basic.MultiLineLabel;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.ChainingModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.github.etcd.service.EtcdProxyFactory;
import org.github.etcd.service.rest.EtcdNode;
import org.github.etcd.service.rest.EtcdProxy;
import org.github.etcd.viewer.ConvertUtils;
import org.github.etcd.viewer.html.modal.TriggerModalLink;
import org.github.etcd.viewer.html.pages.NavigationPage;
import org.github.etcd.viewer.html.pages.NavigationPageLink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EtcdNodePanel extends GenericPanel<EtcdNode> {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(EtcdNodePanel.class);
private static final Comparator<EtcdNode> NODE_SORTER = new Comparator<EtcdNode>() {
@Override
public int compare(EtcdNode o1, EtcdNode o2) {
if (o1.isDir() == o2.isDir()) {
return o1.getKey().compareTo(o2.getKey());
}
if (o1.isDir()) {
return -1;
}
if (o2.isDir()) {
return 1;
}
return 0;
}
};
public static final String ROOT_KEY = "/";
private static final List<String> ROOT_BREADCRUMB = Collections.unmodifiableList(Arrays.asList(ROOT_KEY));
private IModel<EtcdNode> actionModel = Model.of(new EtcdNode());
private IModel<Boolean> updating = Model.of(false);
private EditNodeModalPanel editNodeModal;
private DeleteNodeModalPanel deleteNodeModal;
private WebMarkupContainer breadcrumbAndActions;
private WebMarkupContainer contents;
@Inject
private EtcdProxyFactory proxyFactory;
private IModel<String> registry;
private final IModel<String> key;
private final IModel<String> parentKey;
public EtcdNodePanel(String id, IModel<String> etcdRegistry, IModel<String> keyModel) {
super(id);
this.registry = etcdRegistry;
this.key = keyModel;
setModel(new LoadableDetachableModel<EtcdNode>() {
private static final long serialVersionUID = 1L;
@Override
protected EtcdNode load() {
if (registry.getObject() == null) {
return null;
}
try (EtcdProxy p = proxyFactory.getEtcdProxy(registry.getObject())) {
return p.getNode(key.getObject());
} catch (Exception e) {
log.warn(e.getLocalizedMessage(), e);
// TODO: handle this exception and show some alert on page
error("Could not retrieve key " + key.getObject() + ": " + e.toString());
EtcdNodePanel.this.setEnabled(false);
return null;
}
}
});
parentKey = new ParentKeyModel();
setOutputMarkupId(true);
createModalPanels();
add(breadcrumbAndActions = new WebMarkupContainer("breadcrumbAndActions"));
breadcrumbAndActions.setOutputMarkupId(true);
createBreadcrumb();
createNodeActions();
add(new WebMarkupContainer("icon").add(new AttributeModifier("class", new StringResourceModel("icon.node.dir.${dir}", getModel(), ""))));
add(new Label("key", new PropertyModel<>(getModel(), "key")));
add(contents = new WebMarkupContainer("contents"));
contents.setOutputMarkupId(true);
WebMarkupContainer currentNode;
contents.add(currentNode = new WebMarkupContainer("currentNode"));
currentNode.add(new AttributeAppender("class", new StringResourceModel("nodeClass", getModel(), "") , " "));
currentNode.add(new Label("createdIndex", new PropertyModel<>(getModel(), "createdIndex")));
currentNode.add(new Label("modifiedIndex", new PropertyModel<>(getModel(), "modifiedIndex")));
currentNode.add(new Label("ttl", new PropertyModel<>(getModel(), "ttl")));
currentNode.add(new Label("expiration", new PropertyModel<>(getModel(), "expiration")));
contents.add(new TriggerModalLink<EtcdNode>("editValue", getModel(), editNodeModal) {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
if (key.getObject() == null || "".equals(key.getObject()) || "/".equals(key.getObject())) {
add(AttributeAppender.append("disabled", "disabled"));
} else {
add(AttributeModifier.remove("disabled"));
}
// hide value for directory entries
setVisible(EtcdNodePanel.this.getModelObject() != null && !EtcdNodePanel.this.getModelObject().isDir());
}
@Override
protected void onModalTriggerClick(AjaxRequestTarget target) {
updating.setObject(true);
actionModel.setObject(getModelObject());
}
} .add(new MultiLineLabel("value", new PropertyModel<>(getModel(), "value"))));
AbstractLink goUp;
contents.add(goUp = createNavigationLink("goUp", parentKey));
goUp.add(new Behavior() {
private static final long serialVersionUID = 1L;
@Override
public void onConfigure(Component component) {
super.onConfigure(component);
component.setEnabled(key.getObject() != null && !"".equals(key.getObject()) && !"/".equals(key.getObject()));
}
});
contents.add(createNodesView("nodes"));
}
@Override
protected void onDetach() {
super.onDetach();
registry.detach();
key.detach();
parentKey.detach();
}
protected void onNodeKeyUpdated(AjaxRequestTarget target) {
}
protected void onNodedSaved(AjaxRequestTarget target) {
}
protected void onNodedDeleted(AjaxRequestTarget target) {
}
private void createModalPanels() {
add(editNodeModal = new EditNodeModalPanel("editNodeModal", actionModel, registry, updating) {
private static final long serialVersionUID = 1L;
@Override
protected void onNodeSaved(AjaxRequestTarget target) {
super.onNodeSaved(target);
target.add(contents);
EtcdNodePanel.this.onNodedSaved(target);
}
});
add(deleteNodeModal = new DeleteNodeModalPanel("deleteNodeModal", actionModel, registry) {
private static final long serialVersionUID = 1L;
@Override
protected void onNodeDeleted(AjaxRequestTarget target) {
super.onNodeDeleted(target);
PageParameters params = ConvertUtils.getPageParameters(parentKey.getObject());
params.add("cluster", registry.getObject());
setResponsePage(NavigationPage.class, params);
key.setObject(parentKey.getObject());
// target.add(EtcdNodePanel.this);
onNodeKeyUpdated(target);
target.add(contents, breadcrumbAndActions);
EtcdNodePanel.this.onNodedDeleted(target);
}
});
}
private void createNodeActions() {
breadcrumbAndActions.add(new TriggerModalLink<Void>("addNode", editNodeModal) {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
if (EtcdNodePanel.this.getModelObject() != null && EtcdNodePanel.this.getModelObject().isDir()) {
add(AttributeModifier.remove("disabled"));
} else {
add(AttributeAppender.append("disabled", "disabled"));
}
}
@Override
protected void onModalTriggerClick(AjaxRequestTarget target) {
updating.setObject(false);
actionModel.setObject(new EtcdNode());
String currentKey = key != null? key.getObject() : "";
StringBuffer newKey = new StringBuffer(currentKey);
if (!currentKey.endsWith("/")) {
newKey.append('/');
}
newKey.append("new_node");
actionModel.getObject().setKey(newKey.toString());
}
});
breadcrumbAndActions.add(new TriggerModalLink<EtcdNode>("editNode", getModel(), editNodeModal) {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
if (key.getObject() == null || "".equals(key.getObject()) || "/".equals(key.getObject())) {
add(AttributeAppender.append("disabled", "disabled"));
} else {
add(AttributeModifier.remove("disabled"));
}
}
@Override
protected void onModalTriggerClick(AjaxRequestTarget target) {
updating.setObject(true);
actionModel.setObject(getModelObject());
}
});
breadcrumbAndActions.add(new TriggerModalLink<EtcdNode>("deleteNode", getModel(), deleteNodeModal) {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
if (key.getObject() == null || "".equals(key.getObject()) || "/".equals(key.getObject())) {
add(AttributeAppender.append("disabled", "disabled"));
} else {
add(AttributeModifier.remove("disabled"));
}
}
@Override
protected void onModalTriggerClick(AjaxRequestTarget target) {
actionModel.setObject(getModelObject());
}
});
}
private void createBreadcrumb() {
IModel<List<String>> breadcrumb = new ChainingModel<List<String>>(new PropertyModel<>(getModel(), "key")) {
private static final long serialVersionUID = 1L;
@Override
public List<String> getObject() {
@SuppressWarnings("unchecked")
String key = ((IModel<String>) super.getChainedModel()).getObject();
if (key == null || key.length() == 0 || "/".equals(key)) {
return ROOT_BREADCRUMB;
}
List<String> crumbs = new ArrayList<>();
int index = -1;
while ((index = key.indexOf('/', index + 1)) != -1) {
if (index == 0) {
crumbs.add("/");
} else {
crumbs.add(key.substring(0, index));
}
}
crumbs.add(key);
return crumbs;
}
};
breadcrumbAndActions.add(new ListView<String>("breadcrumb", breadcrumb) {
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(ListItem<String> item) {
AbstractLink link;
item.add(link = createNavigationLink("key", item.getModel()));
link.add(new Label("label", new KeyLabelModel(item.getModel())));
// Last breadcrumb part should be active
if (item.getIndex() == getViewSize() - 1) {
item.add(new AttributeAppender("class", Model.of("active"), " "));
item.setEnabled(false);
}
}
});
}
private Component createNodesView(String id) {
IModel<List<EtcdNode>> nodes = new LoadableDetachableModel<List<EtcdNode>>() {
private static final long serialVersionUID = 1L;
@Override
protected List<EtcdNode> load() {
if (getModelObject() == null || getModelObject().getNodes() == null) {
return Collections.emptyList();
}
List<EtcdNode> nodes = getModelObject().getNodes();
Collections.sort(nodes, NODE_SORTER);
return nodes;
}
};
return new ListView<EtcdNode>(id, nodes) {
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(ListItem<EtcdNode> item) {
item.add(new AttributeModifier("class", new StringResourceModel("nodeClass", item.getModel(), "")));
AbstractLink link;
item.add(link = createNavigationLink("key", new PropertyModel<String>(item.getModel(), "key")));
link.add(new Label("label", new KeyLabelModel(new PropertyModel<String>(item.getModel(), "key"))));
item.add(new MultiLineLabel("value", new PropertyModel<>(item.getModel(), "value")));
item.add(new Label("createdIndex", new PropertyModel<>(item.getModel(), "createdIndex")));
item.add(new Label("modifiedIndex", new PropertyModel<>(item.getModel(), "modifiedIndex")));
item.add(new Label("ttl", new PropertyModel<>(item.getModel(), "ttl")));
item.add(new Label("expiration", new PropertyModel<>(item.getModel(), "expiration")));
}
};
}
private AbstractLink createNavigationLink(final String id, final IModel<String> targetKey) {
return new NavigationPageLink(id, registry, targetKey);
/* return new AjaxLink<String>(id, targetKey) {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
key.setObject(getModelObject());
target.add(EtcdNodePanel.this);
onNodeKeyUpdated(target);
}
@Override
public String getBeforeDisabledLink() {
return "";
}
@Override
public String getAfterDisabledLink() {
return "";
}
@Override
protected void onConfigure() {
super.onConfigure();
setEnabled(selectedCluster != null && selectedCluster.getObject() != null && getModelObject() != null );
}
};*/
}
private class ParentKeyModel extends LoadableDetachableModel<String> {
private static final long serialVersionUID = 1L;
@Override
protected String load() {
String etcdKey = key.getObject();
if (etcdKey == null || etcdKey.indexOf('/') == -1) {
return etcdKey;
}
return etcdKey.substring(0, etcdKey.lastIndexOf('/'));
}
}
private class KeyLabelModel extends ChainingModel<String> {
private static final long serialVersionUID = 1L;
public KeyLabelModel(IModel<String> keyModel) {
super(keyModel);
}
public KeyLabelModel(String key) {
super(key);
}
@Override
public String getObject() {
String etcdKey = super.getObject();
if (etcdKey == null || etcdKey.indexOf('/') == -1) {
return etcdKey;
}
return etcdKey.substring(etcdKey.lastIndexOf('/') + 1);
}
}
}