From 4e7129e84263846259df12e908b7a6522da2adb6 Mon Sep 17 00:00:00 2001 From: Michal Petrov Date: Mon, 15 Dec 2025 16:48:54 +0100 Subject: [PATCH 1/2] HAL-2043: fix JGroups transport processing --- .../subsystem/jgroups/StackElement.java | 4 +- .../subsystem/jgroups/TransportElement.java | 124 ++++++++++++------ 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java index aad6e5532e..92eed7f802 100644 --- a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java @@ -113,9 +113,7 @@ class StackElement implements IsElement, Attachable, HasPresenter, Attachable, HasPresenter { + private Form transportForm; private Form threadPoolDefaultForm; - - TransportElement(MetadataRegistry metadataRegistry, TableButtonFactory tableButtonFactory, - Metadata formMetadata, Resources resources, AddressTemplate template, - String name, String resourceId) { - super(formMetadata, tableButtonFactory, resources, template, name, resourceId); - + private JGroupsPresenter presenter; + private final Tabs transportTabs; + private final HTMLElement section; + private final HTMLElement heading; + private final HTMLElement description; + private final MetadataRegistry metadataRegistry; + private final Resources resources; + private String currentTransportType; + + TransportElement(MetadataRegistry metadataRegistry, Resources resources) { + this.metadataRegistry = metadataRegistry; + this.resources = resources; + + final String threadPoolDefaultName = Names.THREAD_POOL + " Default"; Metadata threadPoolDefaultMetadata = metadataRegistry.lookup(TRANSPORT_THREAD_POOL_DEFAULT_TEMPLATE); - threadPoolDefaultForm = new ModelNodeForm.Builder<>(Ids.JGROUPS_TRANSPORT_THREADPOOL_DEFAULT_FORM, threadPoolDefaultMetadata) .onSave((form, changedValues) -> { AddressTemplate template1 = SELECTED_TRANSPORT_THREAD_POOL_TEMPLATE - .replaceWildcards(table.selectedRow().getName(), DEFAULT); + .replaceWildcards(currentTransportType, DEFAULT); presenter.saveSingleton(template1, threadPoolDefaultMetadata, changedValues, - resources.messages().modifySingleResourceSuccess(Names.THREAD_POOL + " Default")); + resources.messages().modifySingleResourceSuccess(threadPoolDefaultName)); }) .prepareReset(form -> { AddressTemplate template1 = SELECTED_TRANSPORT_THREAD_POOL_TEMPLATE - .replaceWildcards(table.selectedRow().getName(), DEFAULT); - presenter.resetSingleton(template1, Names.THREAD_POOL + " Default", form, + .replaceWildcards(currentTransportType, DEFAULT); + presenter.resetSingleton(template1, threadPoolDefaultName, form, threadPoolDefaultMetadata); }) .build(); - HTMLElement parentElement = (HTMLElement) table.element().parentNode; - // as we are reusing the GenericElement, the form is already added to the section element, then we need to - // retrieve the form element and add it to the tab - HTMLElement form1 = (HTMLElement) parentElement.lastElementChild; - // remove the element, then adds to the tab element - parentElement.removeChild(form1); - - Tabs threadPoolTabs = new Tabs(Ids.JGROUPS_TRANSPORT_THREADPOOL_TAB_CONTAINER); - threadPoolTabs.add(Ids.build("jgroups-transport", Ids.FORM), resources.constants().attributes(), form1); - threadPoolTabs.add(Ids.JGROUPS_TRANSPORT_THREADPOOL_DEFAULT_TAB, "Thread Pool Default", + transportTabs = new Tabs(Ids.JGROUPS_TRANSPORT_THREADPOOL_TAB_CONTAINER); + transportTabs.add(Ids.build("jgroups-transport", Ids.FORM, Ids.TAB), resources.constants().attributes(), + section().element()); + transportTabs.add(Ids.JGROUPS_TRANSPORT_THREADPOOL_DEFAULT_TAB, threadPoolDefaultName, threadPoolDefaultForm.element()); - parentElement.appendChild(threadPoolTabs.element()); + section = section() + .add(heading = h(1).element()) + .add(description = p().element()) + .add(transportTabs).element(); } @Override public void attach() { - super.attach(); threadPoolDefaultForm.attach(); - - table.onSelectionChange(table -> { - if (table.hasSelection()) { - NamedNode selectedTransport = table.selectedRow(); - threadPoolDefaultForm.view(selectedTransport.get(THREAD_POOL).get(DEFAULT)); - } else { - threadPoolDefaultForm.clear(); - } - }); } @Override public void detach() { - super.detach(); + if (transportForm != null) { + transportForm.detach(); + } threadPoolDefaultForm.detach(); } - @Override void update(List models) { - super.update(models); - // disable the ADD and REMOVE buttons, as the transport is a required singleton resource, but the r-r-d - // doesn't says so - // super.update enables the "remove" button if the model is not empty - table.enableButton(0, false); - table.enableButton(1, false); + if (models.isEmpty()) { + return; + } + NamedNode transport = models.get(0); + String transportType = transport.getName(); + + if (!transportType.equals(currentTransportType)) { + currentTransportType = transportType; + // metadata is at .../stack=*/transport=, the first wildcard needs to be preserved + AddressTemplate metadataTemplate = TRANSPORT_TEMPLATE.replaceWildcards("*", currentTransportType); + Metadata transportMetadata = metadataRegistry.lookup(metadataTemplate); + AddressTemplate transportTemplate = SELECTED_TRANSPORT_TEMPLATE.replaceWildcards(currentTransportType); + String fullName = Names.TRANSPORT + ": " + currentTransportType; + + transportForm = new ModelNodeForm.Builder<>(Ids.build(Ids.JGROUPS_TRANSPORT, Ids.FORM), transportMetadata) + .onSave((form, changedValues) -> { + presenter.saveSingleton(transportTemplate, transportMetadata, changedValues, + resources.messages().modifySingleResourceSuccess(fullName)); + }) + .prepareReset(form -> { + presenter.resetSingleton(transportTemplate, fullName, form, + transportMetadata); + }) + .build(); + transportForm.attach(); + + heading.textContent = fullName; + description.textContent = transportMetadata.getDescription().getDescription(); + transportTabs.setContent(0, transportForm.element()); + } + + transportForm.view(transport); + threadPoolDefaultForm.view(transport.get(THREAD_POOL).get(DEFAULT)); + } + + @Override + public HTMLElement element() { + return section; + } + + @Override + public void setPresenter(JGroupsPresenter presenter) { + this.presenter = presenter; } } From 01a5e91605172273be7187625082b7673b6830e0 Mon Sep 17 00:00:00 2001 From: Michal Petrov Date: Tue, 16 Dec 2025 15:29:52 +0100 Subject: [PATCH 2/2] HAL-2044: rework the JGroups protocols view --- .../subsystem/jgroups/AddressTemplates.java | 4 + .../subsystem/jgroups/ChooseProtocolStep.java | 66 ++++++ .../subsystem/jgroups/JGroupsPresenter.java | 134 +++++++++++ .../subsystem/jgroups/JGroupsView.java | 5 + .../subsystem/jgroups/PropertiesStep.java | 75 ++++++ .../subsystem/jgroups/ProtocolElement.java | 219 ++++++++++++++++++ .../subsystem/jgroups/ProtocolWizard.java | 72 ++++++ .../subsystem/jgroups/StackElement.java | 4 +- .../hal/core/elytron/CredentialReference.java | 17 +- .../hal/core/mbui/form/ModelNodeForm.java | 3 +- .../hal/core/mbui/form/PropertyFilter.java | 7 +- .../hal/dmr/ModelDescriptionConstants.java | 2 + .../org/jboss/hal/resources/Constants.java | 1 + .../org/jboss/hal/resources/Messages.java | 1 + .../jboss/hal/resources/Constants.properties | 1 + .../jboss/hal/resources/Messages.properties | 1 + 16 files changed, 604 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ChooseProtocolStep.java create mode 100644 app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/PropertiesStep.java create mode 100644 app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolElement.java create mode 100644 app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolWizard.java diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/AddressTemplates.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/AddressTemplates.java index 80c7ebcb99..bc1895ac20 100644 --- a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/AddressTemplates.java +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/AddressTemplates.java @@ -40,6 +40,8 @@ interface AddressTemplates { String SELECTED_CHANNEL_FORK_ADDRESS = JGROUPS_ADDRESS + "/channel={selected.channel}/fork=*"; String SELECTED_CHANNEL_FORK_PROTOCOL_ADDRESS = JGROUPS_ADDRESS + "/channel={selected.channel}/fork={selected.fork}/protocol=*"; + String AUTH_TOKEN_ADDRESS = JGROUPS_ADDRESS + "/stack=*/protocol=AUTH/token=*"; + String SELECTED_AUTH_TOKEN_ADDRESS = JGROUPS_ADDRESS + "/stack={selected.stack}/protocol=AUTH/token=*"; AddressTemplate JGROUPS_TEMPLATE = AddressTemplate.of(JGROUPS_ADDRESS); AddressTemplate STACK_TEMPLATE = AddressTemplate.of(STACK_ADDRESS); @@ -64,5 +66,7 @@ interface AddressTemplates { AddressTemplate CHANNEL_FORK_PROTOCOL_TEMPLATE = AddressTemplate.of(CHANNEL_FORK_PROTOCOL_ADDRESS); AddressTemplate SELECTED_CHANNEL_FORK_PROTOCOL_TEMPLATE = AddressTemplate.of( SELECTED_CHANNEL_FORK_PROTOCOL_ADDRESS); + AddressTemplate AUTH_TOKEN_TEMPLATE = AddressTemplate.of(AUTH_TOKEN_ADDRESS); + AddressTemplate SELECTED_AUTH_TOKEN_TEMPLATE = AddressTemplate.of(SELECTED_AUTH_TOKEN_ADDRESS); } diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ChooseProtocolStep.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ChooseProtocolStep.java new file mode 100644 index 0000000000..fb5288cad1 --- /dev/null +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ChooseProtocolStep.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.hal.client.configuration.subsystem.jgroups; + +import java.util.Set; + +import org.jboss.elemento.InputType; +import org.jboss.hal.ballroom.wizard.WizardStep; +import org.jboss.hal.resources.Ids; +import org.jboss.hal.resources.Resources; +import org.jboss.hal.resources.UIConstants; + +import elemental2.dom.HTMLElement; + +import static org.jboss.elemento.Elements.div; +import static org.jboss.elemento.Elements.input; +import static org.jboss.elemento.Elements.label; +import static org.jboss.elemento.Elements.p; +import static org.jboss.elemento.Elements.span; +import static org.jboss.elemento.EventType.click; +import static org.jboss.hal.dmr.ModelDescriptionConstants.CUSTOM; +import static org.jboss.hal.resources.CSS.radio; + +class ChooseProtocolStep extends WizardStep { + + private final HTMLElement root; + + ChooseProtocolStep(Resources resources, Set protocolNames, ProtocolWizard wizard) { + super(resources.constants().chooseProtocol()); + root = div() + .add(p().textContent(resources.messages().chooseProtocol(CUSTOM))).element(); + + for (String protocolName : protocolNames) { + String name = protocolName; + if (name.equals("*")) { + name = CUSTOM; + } + root.appendChild(div().css(radio) + .add(label() + .add(input(InputType.radio) + .id(Ids.build(Ids.JGROUPS_PROTOCOL, name)) + .attr(UIConstants.NAME, Ids.JGROUPS_PROTOCOL) + .on(click, e -> wizard.setProtocol(protocolName)).element()) + .add(span().textContent(name))) + .element()); + } + } + + @Override + public HTMLElement element() { + return root; + } +} diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsPresenter.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsPresenter.java index 2b0433aeac..0845d6d5c9 100644 --- a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsPresenter.java +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsPresenter.java @@ -15,13 +15,18 @@ */ package org.jboss.hal.client.configuration.subsystem.jgroups; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; import org.jboss.hal.ballroom.LabelBuilder; +import org.jboss.hal.ballroom.autocomplete.StaticAutoComplete; import org.jboss.hal.ballroom.form.Form; import org.jboss.hal.ballroom.form.Form.FinishReset; import org.jboss.hal.ballroom.form.TextBoxItem; @@ -48,6 +53,7 @@ import org.jboss.hal.meta.Metadata; import org.jboss.hal.meta.MetadataRegistry; import org.jboss.hal.meta.StatementContext; +import org.jboss.hal.meta.description.ResourceDescription; import org.jboss.hal.meta.token.NameTokens; import org.jboss.hal.resources.Ids; import org.jboss.hal.resources.Names; @@ -62,18 +68,30 @@ import com.gwtplatform.mvp.client.annotations.ProxyCodeSplit; import com.gwtplatform.mvp.client.proxy.ProxyPlace; +import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.AUTH_TOKEN_TEMPLATE; import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.JGROUPS_TEMPLATE; +import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.PROTOCOL_TEMPLATE; +import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.SELECTED_AUTH_TOKEN_TEMPLATE; +import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.SELECTED_PROTOCOL_TEMPLATE; import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.SELECTED_RELAY_TEMPLATE; import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.STACK_TEMPLATE; import static org.jboss.hal.client.configuration.subsystem.jgroups.AddressTemplates.TRANSPORT_TEMPLATE; import static org.jboss.hal.dmr.ModelDescriptionConstants.ADD; +import static org.jboss.hal.dmr.ModelDescriptionConstants.ADD_INDEX; import static org.jboss.hal.dmr.ModelDescriptionConstants.CHANNEL; +import static org.jboss.hal.dmr.ModelDescriptionConstants.DEPRECATED; import static org.jboss.hal.dmr.ModelDescriptionConstants.FORK; +import static org.jboss.hal.dmr.ModelDescriptionConstants.INCLUDE_SINGLETONS; import static org.jboss.hal.dmr.ModelDescriptionConstants.JGROUPS; +import static org.jboss.hal.dmr.ModelDescriptionConstants.MODULE; +import static org.jboss.hal.dmr.ModelDescriptionConstants.PROPERTIES; import static org.jboss.hal.dmr.ModelDescriptionConstants.PROTOCOL; +import static org.jboss.hal.dmr.ModelDescriptionConstants.READ_CHILDREN_TYPES_OPERATION; import static org.jboss.hal.dmr.ModelDescriptionConstants.RELAY; +import static org.jboss.hal.dmr.ModelDescriptionConstants.RESULT; import static org.jboss.hal.dmr.ModelDescriptionConstants.SOCKET_BINDING; import static org.jboss.hal.dmr.ModelDescriptionConstants.STACK; +import static org.jboss.hal.dmr.ModelDescriptionConstants.STATISTICS_ENABLED; import static org.jboss.hal.dmr.ModelDescriptionConstants.TRANSPORT; import static org.jboss.hal.dmr.ModelNodeHelper.asNamedNodes; import static org.jboss.hal.dmr.ModelNodeHelper.failSafePropertyList; @@ -93,6 +111,12 @@ public class JGroupsPresenter extends ApplicationFinderPresenter { + stackSingletons = result.step(0).get(RESULT).asList().stream() + .map(s -> { + String name = s.asString(); + if (!name.contains("=")) { + name += "=*"; + } + return metadataRegistry.lookup(STACK_TEMPLATE.append(name)); + }) + .filter(metadata -> !metadata.getDescription().has(DEPRECATED)) + .collect(Collectors.toMap(m -> m.getTemplate().lastName() + "=" + m.getTemplate().lastValue(), + m -> m, (m1, m2) -> null, TreeMap::new)); + + // AUTH has child singletons, we present them as separate AUTH options + result.step(1).get(RESULT).asList().forEach(token -> { + Metadata mdCopy = new Metadata(authMd.getTemplate(), authMd::getSecurityContext, + new ResourceDescription(authMd.getDescription()), authMd.getCapabilities()); + String tokenName = token.asString().substring(6); + + Metadata tokenMd = metadataRegistry + .lookup(AUTH_TOKEN_TEMPLATE.replaceWildcards("*", tokenName)); + mdCopy.getDescription().requestProperties().addAll(tokenMd.getDescription().requestProperties()); + stackSingletons.put(PROTOCOL_AUTH + " (" + tokenName + ")", mdCopy); + }); + stackSingletons.remove(PROTOCOL_AUTH); + }); + } + + private Set getNamesOf(String resource) { + return stackSingletons.keySet().stream() + .filter(name -> name.startsWith(resource)) + .map(name -> name.substring(resource.length() + 1)) + .collect(Collectors.toSet()); + } + + public Set getProtocolNames() { + return getNamesOf(PROTOCOL); + } + + public Set getTransportNames() { + return getNamesOf(TRANSPORT); + } + + public Metadata getAuthTokenMetadata(String token) { + return metadataRegistry.lookup(AUTH_TOKEN_TEMPLATE.replaceWildcards("*", token)); + } + @SuppressWarnings("ConstantConditions") void addStack() { Metadata metadata = metadataRegistry.lookup(STACK_TEMPLATE); @@ -254,6 +339,7 @@ void addStack() { String transportLabel = new LabelBuilder().label(TRANSPORT); TextBoxItem transportItem = new TextBoxItem(TRANSPORT, transportLabel); transportItem.setRequired(true); + transportItem.registerSuggestHandler(new StaticAutoComplete(new ArrayList<>(getTransportNames()))); String id = Ids.build(Ids.JGROUPS_STACK_CONFIG, Ids.ADD); ModelNodeForm form = new ModelNodeForm.Builder<>(id, metadata) .unboundFormItem(nameItem, 0) @@ -314,6 +400,54 @@ void showRemoteSites(NamedNode row) { // protocol resources + public Metadata getProtocolMetadata(String name) { + Metadata metadata = stackSingletons.get(PROTOCOL + "=" + name); + if (metadata == null) { + metadata = stackSingletons.get(PROTOCOL + "=*"); + } + return metadata; + } + + public void addProtocol(Set currentProtocols) { + Set availableProtocols = getProtocolNames().stream() + .filter(prot -> !currentProtocols.contains(prot) + && !((prot.startsWith(AUTH + " (") && currentProtocols.contains(AUTH)))) + .collect(Collectors.toSet()); + new ProtocolWizard(resources, availableProtocols, this::getProtocolMetadata, + (wizard, context) -> addProtocolSingleton(dispatcher, filterStatementContext, context.protocolName, + context.payload, (n, a) -> { + MessageEvent.fire(getEventBus(), + Message.success(resources.messages().addResourceSuccess(PROTOCOL, context.protocolName))); + reload(); + })) + .show(); + } + + private void addProtocolSingleton(Dispatcher dispatcher, StatementContext statementContext, String protocolName, + ModelNode payload, CrudOperations.AddCallback callback) { + if (!protocolName.startsWith(AUTH + " (")) { + crud.add(PROTOCOL, protocolName, + SELECTED_PROTOCOL_TEMPLATE.resolve(filterStatementContext, protocolName), + payload, (n, a) -> reload()); + return; + } + + List operations = new ArrayList<>(); + ModelNode protocolPayload = new ModelNode(); + for (String s : AUTH_ATTRIBUTES) { + if (payload.has(s)) { + ModelNode value = payload.remove(s); + protocolPayload.get(s).set(value); + } + } + final String tokenName = protocolName.substring(AUTH.length() + 2, protocolName.length() - 1); + operations.add(new Operation.Builder(SELECTED_PROTOCOL_TEMPLATE.resolve(statementContext, AUTH), ADD) + .payload(protocolPayload).build()); + operations.add(new Operation.Builder(SELECTED_AUTH_TOKEN_TEMPLATE.resolve(statementContext, tokenName), ADD) + .payload(payload).build()); + dispatcher.execute(new Composite(operations), (CompositeResult result) -> callback.execute(null, null)); + } + void showProtocols(NamedNode selectedStack) { this.selectedStack = selectedStack.getName(); getView().updateProtocols(asNamedNodes(failSafePropertyList(selectedStack, PROTOCOL))); diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsView.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsView.java index 3c874a0d55..b22ad7697a 100644 --- a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsView.java +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/JGroupsView.java @@ -141,4 +141,9 @@ public void updateChannelProtocols(List model) { channelConfig.updateProtocol(model); } + public void attach() { + super.attach(); + presenter.processStackSingletons(); + } + } diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/PropertiesStep.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/PropertiesStep.java new file mode 100644 index 0000000000..6647440220 --- /dev/null +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/PropertiesStep.java @@ -0,0 +1,75 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.hal.client.configuration.subsystem.jgroups; + +import org.jboss.hal.ballroom.wizard.WizardStep; +import org.jboss.hal.core.mbui.dialog.NameItem; +import org.jboss.hal.core.mbui.form.ModelNodeForm; +import org.jboss.hal.dmr.ModelNode; +import org.jboss.hal.dmr.ModelNodeHelper; +import org.jboss.hal.resources.Ids; +import org.jboss.hal.resources.Resources; + +import elemental2.dom.HTMLElement; + +import static org.jboss.elemento.Elements.div; + +class PropertiesStep extends WizardStep { + + private final HTMLElement root; + private ModelNodeForm form; + private String selectedProtocol; + private final Resources resources; + + PropertiesStep(Resources resources) { + super(resources.constants().attributes()); + this.resources = resources; + root = div().element(); + } + + @Override + public HTMLElement element() { + return root; + } + + @Override + protected void onShow(ProtocolWizard.Context context) { + if (context.protocolName.equals(selectedProtocol)) { + return; + } + selectedProtocol = context.protocolName; + + ModelNodeForm.Builder builder = new ModelNodeForm.Builder<>( + Ids.build(Ids.JGROUPS_PROTOCOL, Ids.ADD, Ids.FORM), context.protocolMetadata); + if (context.protocolName.equals("*")) { + builder.unboundFormItem(new NameItem(), 0); + } + builder.fromRequestProperties(); + + form = builder.build(); + form.attach(); + ProtocolElement.addCrValidation(resources, form, context.protocolMetadata.getDescription().requestProperties()); + root.replaceChildren(form.element()); + form.edit(new ModelNode()); + } + + @Override + protected boolean onNext(ProtocolWizard.Context context) { + boolean valid = form.save(); + context.payload = ModelNodeHelper.flatToNested(form.getModel()); + return valid; + } +} diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolElement.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolElement.java new file mode 100644 index 0000000000..13c4f3a895 --- /dev/null +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolElement.java @@ -0,0 +1,219 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.hal.client.configuration.subsystem.jgroups; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jboss.elemento.IsElement; +import org.jboss.hal.ballroom.Attachable; +import org.jboss.hal.ballroom.LabelBuilder; +import org.jboss.hal.ballroom.Tabs; +import org.jboss.hal.ballroom.form.Form; +import org.jboss.hal.ballroom.table.ButtonHandler; +import org.jboss.hal.ballroom.table.Table; +import org.jboss.hal.core.elytron.CredentialReference; +import org.jboss.hal.core.mbui.form.ModelNodeForm; +import org.jboss.hal.core.mbui.table.ModelNodeTable; +import org.jboss.hal.core.mbui.table.TableButtonFactory; +import org.jboss.hal.core.mvp.HasPresenter; +import org.jboss.hal.dmr.ModelNode; +import org.jboss.hal.dmr.NamedNode; +import org.jboss.hal.dmr.Property; +import org.jboss.hal.meta.AddressTemplate; +import org.jboss.hal.meta.AttributeCollection; +import org.jboss.hal.meta.Metadata; +import org.jboss.hal.resources.Ids; +import org.jboss.hal.resources.Names; +import org.jboss.hal.resources.Resources; + +import com.google.common.collect.Iterables; + +import elemental2.dom.HTMLElement; + +import static org.jboss.elemento.Elements.div; +import static org.jboss.elemento.Elements.h; +import static org.jboss.elemento.Elements.p; +import static org.jboss.elemento.Elements.section; +import static org.jboss.hal.client.configuration.subsystem.jgroups.JGroupsPresenter.AUTH; +import static org.jboss.hal.dmr.ModelDescriptionConstants.NAME; +import static org.jboss.hal.dmr.ModelDescriptionConstants.TOKEN; + +public class ProtocolElement implements IsElement, Attachable, HasPresenter { + + protected final Table table; + protected final Resources resources; + private final String resourceName; + private final String resourceId; + private Form form; + private Form tokenForm; + protected JGroupsPresenter presenter; + private HTMLElement section; + private HTMLElement formContainer; + private final AddressTemplate template; + private String currentProtocolName; + + ProtocolElement(Metadata metadata, TableButtonFactory tableButtonFactory, + Resources resources, + AddressTemplate template, String resourceName, String resourceId) { + this.resources = resources; + this.template = template; + this.resourceName = resourceName; + this.resourceId = resourceId; + + ButtonHandler launchWizard = (Table table) -> { + Set current = table.getRows().stream() + .map(NamedNode::getName) + .collect(Collectors.toSet()); + presenter.addProtocol(current); + }; + + table = new ModelNodeTable.Builder(Ids.build(resourceId, Ids.TABLE), metadata) + .button(tableButtonFactory.add(template, launchWizard)) + .button(tableButtonFactory.remove(template, + table -> presenter.removeResource(template, table.selectedRow().getName(), resourceName))) + .column(NAME, (cell, t, row, meta) -> row.getName()) + .build(); + form = createForm(metadata); + + section = section() + .add(h(1).textContent(resourceName)) + .add(p().textContent(metadata.getDescription().getDescription())) + .add(table) + .add(formContainer = div().add(form).element()).element(); + } + + @Override + public HTMLElement element() { + return section; + } + + @Override + @SuppressWarnings("ConstantConditions") + public void attach() { + table.attach(); + form.attach(); + + // we cannot use table.bind since the form might be changing + table.onSelectionChange(table -> { + if (table.hasSelection()) { + adjustAndView(table.selectedRow()); + } else { + form.clear(); + } + }); + } + + @Override + public void detach() { + form.detach(); + table.detach(); + } + + @Override + public void setPresenter(JGroupsPresenter presenter) { + this.presenter = presenter; + } + + void update(List models) { + table.update(models); + form.clear(); + table.enableButton(1, !models.isEmpty()); + } + + private void adjustAndView(NamedNode selectedRow) { + String protocolName = selectedRow.getName(); + String metadataName = protocolName; + String token = null; + if (protocolName.equals(AUTH)) { + token = selectedRow.get(TOKEN).asProperty().getName(); + metadataName = AUTH + " (" + token + ")"; + } + Metadata metadata = presenter.getProtocolMetadata(metadataName); + Set attributeNames = metadata.getDescription().attributes().stream() + .map(Property::getName).collect(Collectors.toSet()); + + // most protocols have the same attributes -> form doesn't need replacing + if (attributeNames.size() == Iterables.size(form.getFormItems()) && + !Iterables.any(form.getFormItems(), item -> !attributeNames.contains(item.getName())) && + !(AUTH.equals(protocolName)) || (AUTH.equals(currentProtocolName))) { + currentProtocolName = protocolName; + form.view(selectedRow); + return; + } + + form.detach(); + if (protocolName.equals(AUTH) && tokenForm != null) { + tokenForm.detach(); + } + + currentProtocolName = protocolName; + form = createForm(metadata); + + HTMLElement formElement; + + if (protocolName.equals(AUTH)) { + String tokenType = new LabelBuilder().label(TOKEN + ": " + token); + Metadata tokenMetadata = presenter.getAuthTokenMetadata(token); + AddressTemplate tokenTemplate = template.append(TOKEN + "=" + token); + Tabs tabs = new Tabs(Ids.build(resourceName, Ids.TAB_CONTAINER)); + tabs.add(Ids.TAB, Names.PROTOCOL, form.element()); + + tokenForm = new ModelNodeForm.Builder<>(Ids.build(resourceName, TOKEN, Ids.FORM), tokenMetadata) + .onSave((form, changedValues) -> presenter + .saveResource(tokenTemplate, protocolName, changedValues, + tokenMetadata, + resources.messages().modifySingleResourceSuccess(TOKEN))) + .prepareReset( + form -> presenter.resetSingleton(tokenTemplate.replaceWildcards(protocolName), tokenType, form, + tokenMetadata)) + .build(); + + addCrValidation(resources, tokenForm, tokenMetadata.getDescription().attributes()); + tabs.add(Ids.build(TOKEN, Ids.TAB), tokenType, tokenForm.element()); + formElement = tabs.element(); + } else { + formElement = form.element(); + } + + formContainer.replaceChildren(formElement); + form.attach(); + form.view(selectedRow); + if (protocolName.equals(AUTH)) { + tokenForm.attach(); + tokenForm.view(selectedRow.get(TOKEN).get(token)); + } + } + + private Form createForm(Metadata metadata) { + return new ModelNodeForm.Builder(Ids.build(resourceId, Ids.FORM), metadata) + .onSave((form, changedValues) -> presenter + .saveResource(template, table.selectedRow().getName(), changedValues, metadata, + resources.messages().modifySingleResourceSuccess(resourceName))) + .prepareReset(form -> presenter.resetResource(template, table.selectedRow().getName(), resourceName, form, + metadata)) + .build(); + } + + protected static void addCrValidation(Resources resources, Form form, AttributeCollection attributes) { + for (Property prop : attributes) { + if (prop.getName().endsWith("reference")) { + form.addFormValidation(new CredentialReference.CrFormValuesValidation(resources, prop.getName())); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolWizard.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolWizard.java new file mode 100644 index 0000000000..ebcaf40892 --- /dev/null +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/ProtocolWizard.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.hal.client.configuration.subsystem.jgroups; + +import java.util.Set; +import java.util.function.Function; + +import org.jboss.hal.ballroom.wizard.Wizard; +import org.jboss.hal.dmr.ModelNode; +import org.jboss.hal.meta.Metadata; +import org.jboss.hal.resources.Names; +import org.jboss.hal.resources.Resources; + +import static org.jboss.hal.client.configuration.subsystem.jgroups.ProtocolWizard.State.CHOOSE_PROTOCOL_STEP; +import static org.jboss.hal.client.configuration.subsystem.jgroups.ProtocolWizard.State.PROPERTIES_STEP; + +public class ProtocolWizard { + + private final Wizard wizard; + private final Function getProtocolMetadata; + + public ProtocolWizard(Resources resources, Set protocolNames, + Function getProtocolMetadata, + Wizard.FinishCallback callback) { + this.getProtocolMetadata = getProtocolMetadata; + + wizard = new Wizard.Builder(resources.messages().addResourceTitle(Names.PROTOCOL), + new Context()) + .onBack((context, state) -> state == PROPERTIES_STEP ? CHOOSE_PROTOCOL_STEP : null) + .onNext((context, state) -> state == CHOOSE_PROTOCOL_STEP ? PROPERTIES_STEP : null) + .onFinish(callback) + .addStep(CHOOSE_PROTOCOL_STEP, new ChooseProtocolStep(resources, protocolNames, this)) + .addStep(PROPERTIES_STEP, new PropertiesStep(resources)) + .setInitialState(protocolNames.size() > 1 ? CHOOSE_PROTOCOL_STEP : PROPERTIES_STEP) + .build(); + + setProtocol("*"); + } + + public void show() { + wizard.show(); + } + + public void setProtocol(String name) { + Context context = wizard.getContext(); + context.protocolName = name; + context.protocolMetadata = getProtocolMetadata.apply(name); + } + + static class Context { + ModelNode payload = new ModelNode(); + String protocolName; + Metadata protocolMetadata; + } + + enum State { + CHOOSE_PROTOCOL_STEP, PROPERTIES_STEP; + } +} diff --git a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java index 92eed7f802..de1910a06e 100644 --- a/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java +++ b/app/src/main/java/org/jboss/hal/client/configuration/subsystem/jgroups/StackElement.java @@ -60,7 +60,7 @@ class StackElement implements IsElement, Attachable, HasPresenter, Attachable, HasPresenter private final Resources resources; private final boolean prefixed; + private final String customName; public CrFormValuesValidation(Resources resources) { this(resources, false); @@ -320,13 +321,23 @@ public CrFormValuesValidation(Resources resources) { public CrFormValuesValidation(Resources resources, boolean prefixed) { this.resources = resources; this.prefixed = prefixed; + this.customName = null; + } + + public CrFormValuesValidation(Resources resources, String customName) { + this.resources = resources; + this.prefixed = false; + this.customName = customName; } @Override public ValidationResult validate(Form form) { - FormItem storeItem = form.getFormItem(prefixed ? STORE_PREFIXED : STORE); - FormItem aliasItem = form.getFormItem(prefixed ? ALIAS_PREFIXED : ALIAS); - FormItem clearTextItem = form.getFormItem(prefixed ? CLEAR_TEXT_PREFIXED : CLEAR_TEXT); + FormItem storeItem = form + .getFormItem(prefixed ? STORE_PREFIXED : (customName != null ? customName + DOT + STORE : STORE)); + FormItem aliasItem = form + .getFormItem(prefixed ? ALIAS_PREFIXED : (customName != null ? customName + DOT + ALIAS : ALIAS)); + FormItem clearTextItem = form.getFormItem( + prefixed ? CLEAR_TEXT_PREFIXED : (customName != null ? customName + DOT + CLEAR_TEXT : CLEAR_TEXT)); if (!clearTextItem.isEmpty() && storeItem.isEmpty() && aliasItem.isEmpty()) { // clear-text only not recommended mode return ValidationResult.OK; diff --git a/core/src/main/java/org/jboss/hal/core/mbui/form/ModelNodeForm.java b/core/src/main/java/org/jboss/hal/core/mbui/form/ModelNodeForm.java index 1f8dfb376c..8415ed118c 100644 --- a/core/src/main/java/org/jboss/hal/core/mbui/form/ModelNodeForm.java +++ b/core/src/main/java/org/jboss/hal/core/mbui/form/ModelNodeForm.java @@ -297,7 +297,8 @@ public void attach() { protected void prepare(State state) { super.prepare(state); - SecurityContext securityContext = metadata.getSecurityContext(); + SecurityContext securityContext = isFromRequestProperties ? metadata.forOperation(ADD).getSecurityContext() + : metadata.getSecurityContext(); switch (state) { case EMPTY: ElementGuard.processElements( diff --git a/core/src/main/java/org/jboss/hal/core/mbui/form/PropertyFilter.java b/core/src/main/java/org/jboss/hal/core/mbui/form/PropertyFilter.java index 106d61616d..d97f1f8f1d 100644 --- a/core/src/main/java/org/jboss/hal/core/mbui/form/PropertyFilter.java +++ b/core/src/main/java/org/jboss/hal/core/mbui/form/PropertyFilter.java @@ -32,8 +32,11 @@ class PropertyFilter implements Predicate { @Override public boolean test(final Property property) { Predicate filter; - Predicate required = p -> ((p.getValue().hasDefined(REQUIRED) && p.getValue().get(REQUIRED).asBoolean()) || - (p.getValue().hasDefined(NILLABLE) && !p.getValue().get(NILLABLE).asBoolean())); + // always include add-index + Predicate isAddIndex = p -> p.getName().equals(ADD_INDEX); + Predicate required = isAddIndex + .or(p -> ((p.getValue().hasDefined(REQUIRED) && p.getValue().get(REQUIRED).asBoolean()) || + (p.getValue().hasDefined(NILLABLE) && !p.getValue().get(NILLABLE).asBoolean()))); // do not include "deprecated" attributes if (builder.hideDeprecated && property.getValue().hasDefined(DEPRECATED)) { diff --git a/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java b/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java index a7b314ade4..b24232d278 100644 --- a/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java +++ b/dmr/src/main/java/org/jboss/hal/dmr/ModelDescriptionConstants.java @@ -47,6 +47,7 @@ public interface ModelDescriptionConstants { String ADD_CONTENT = "add-content"; String ADD_IDENTITY = "add-identity"; String ADD_IDENTITY_ATTRIBUTE = "add-identity-attribute"; + String ADD_INDEX = "add-index"; String ADD_PREFIX_ROLE_MAPPER = "add-prefix-role-mapper"; String ADD_SUFFIX_ROLE_MAPPER = "add-suffix-role-mapper"; String ADDRESS = "address"; @@ -1026,6 +1027,7 @@ public interface ModelDescriptionConstants { String TIMESTAMP_COLUMN = "timestamp-column"; String TO = "to"; String TO_PROFILE = "to-profile"; + String TOKEN = "token"; String TOKEN_REALM = "token-realm"; String TOPIC_ADDRESS = "topic-address"; String TOTAL_PROCESSING_TIME = "total-processing-time"; diff --git a/resources/src/main/java/org/jboss/hal/resources/Constants.java b/resources/src/main/java/org/jboss/hal/resources/Constants.java index 00d3899135..fc47c8b8be 100644 --- a/resources/src/main/java/org/jboss/hal/resources/Constants.java +++ b/resources/src/main/java/org/jboss/hal/resources/Constants.java @@ -74,6 +74,7 @@ public interface Constants extends com.google.gwt.i18n.client.Constants { String chooseOrDragFile(); String chooseOrDragFiles(); String choosePolicy(); + String chooseProtocol(); String chooseSingleton(); String chooseStrategy(); String chooseTemplate(); diff --git a/resources/src/main/java/org/jboss/hal/resources/Messages.java b/resources/src/main/java/org/jboss/hal/resources/Messages.java index 4ca3ace3ed..b83d0e13a4 100644 --- a/resources/src/main/java/org/jboss/hal/resources/Messages.java +++ b/resources/src/main/java/org/jboss/hal/resources/Messages.java @@ -545,6 +545,7 @@ public interface Messages extends com.google.gwt.i18n.client.Messages { String bootErrors(); String cancelNonProgressingOperation(); String changeAccountKeyQuestion(String name); + String chooseProtocol(String custom); String chooseTemplate(String custom); String chooseUpdateType(String custom); String cleanPatchHistory(); diff --git a/resources/src/main/resources/org/jboss/hal/resources/Constants.properties b/resources/src/main/resources/org/jboss/hal/resources/Constants.properties index 5b9697c8f4..f1fd3afb3a 100644 --- a/resources/src/main/resources/org/jboss/hal/resources/Constants.properties +++ b/resources/src/main/resources/org/jboss/hal/resources/Constants.properties @@ -72,6 +72,7 @@ chooseIdentityPasswordTitle=Choose password type chooseOrDragFile=Choose a file or drag it here chooseOrDragFiles=Choose one or more files or drag them here. choosePolicy=Choose Policy +chooseProtocol=Choose Protocol chooseSingleton=Choose Singleton chooseStrategy=Choose Strategy chooseTemplate=Choose Template diff --git a/resources/src/main/resources/org/jboss/hal/resources/Messages.properties b/resources/src/main/resources/org/jboss/hal/resources/Messages.properties index cfb7b91934..3a84bc6f2a 100644 --- a/resources/src/main/resources/org/jboss/hal/resources/Messages.properties +++ b/resources/src/main/resources/org/jboss/hal/resources/Messages.properties @@ -63,6 +63,7 @@ changeAliasError=Failed to change alias {0} to {1}{0} successfully changed to {1} for {2}. changePrioritySuccess=The priority has been successfully changed to {0, number}. chooseContentToDeploy=Choose the content for deploying to server group {0}. +chooseProtocol=Choose one of the predefined protocols or choose "{0}" to specify your own settings. chooseReplication=Choose which role the server should take in the data replication policy: chooseServerGroupsToDeploy=Choose the server groups for deploying {0}. chooseServerGroupsToUndeploy=Choose the server groups for undeploying {0}.