From 3ee4f96b688b3f62a98d6f03c009af47b97b4d69 Mon Sep 17 00:00:00 2001
From: Ricardo Mestre
Date: Wed, 29 Jan 2025 18:57:21 +0000
Subject: [PATCH] Add proxy onboarding feature
# Conflicts:
# java/code/src/com/suse/manager/webui/services/SaltServerActionService.java
---
java/buildconf/LICENSE.txt | 2 +-
.../rhn/common/ErrorReportingStrategies.java | 20 +-
.../src/com/redhat/rhn/common/RhnError.java | 37 +
.../com/redhat/rhn/common/RhnErrorReport.java | 66 ++
.../rhn/common/RhnGeneralException.java | 47 ++
.../redhat/rhn/common/RhnReportStrategy.java | 27 +
.../db/datasource/xml/System_queries.xml | 1 +
.../rhn/common/security/acl/Access.java | 26 +
.../rhn/domain/action/ActionFactory.java | 10 +-
.../action/ProxyConfigurationApplyAction.java | 75 ++
.../domain/entitlement/ProxyEntitlement.java | 71 ++
.../com/redhat/rhn/domain/server/Server.java | 17 +
.../rhn/domain/server/ServerConstants.java | 10 +-
.../strings/java/StringResource_en_US.xml | 12 +
.../strings/jsp/StringResource_en_US.xml | 3 +
.../redhat/rhn/frontend/taglibs/IconTag.java | 2 +
.../rhn/frontend/taglibs/ToolbarTag.java | 109 ++-
.../rhn/frontend/taglibs/rhn-taglib.tld | 10 +
.../activationkey/ActivationKeyHandler.java | 3 +
.../entitlement/EntitlementManager.java | 8 +
.../rhn/manager/system/SystemManager.java | 52 +-
.../ProxyContainerConfigCreate.java | 52 +-
.../src/com/suse/manager/webui/Router.java | 7 +-
.../ProxyConfigurationController.java | 344 ++++++++
.../services/SaltServerActionService.java | 18 +-
.../webui/templates/minion/proxy-config.jade | 21 +
.../webui/templates/system-common.jade | 4 +
.../webui/utils/SparkApplicationHelper.java | 11 +
.../utils/gson/ProxyConfigUpdateJson.java | 157 ++++
.../src/com/suse/proxy/ProxyConfigUtils.java | 289 +++++++
.../suse/proxy/ProxyContainerImagesEnum.java | 70 ++
.../com/suse/proxy/ProxyRegistryUtils.java | 200 +++++
java/code/src/com/suse/proxy/RegistryUrl.java | 120 +++
.../com/suse/proxy/get/ProxyConfigGet.java | 43 +
.../src/com/suse/proxy/model/ProxyConfig.java | 159 ++++
.../suse/proxy/model/ProxyConfigImage.java | 54 ++
.../suse/proxy/update/ProxyConfigUpdate.java | 62 ++
.../update/ProxyConfigUpdateAcquisitor.java | 166 ++++
.../ProxyConfigUpdateApplySaltState.java | 75 ++
.../update/ProxyConfigUpdateContext.java | 177 ++++
.../ProxyConfigUpdateContextHandler.java | 24 +
.../ProxyConfigUpdateFileAcquisitor.java | 104 +++
...roxyConfigUpdateRegistryPreConditions.java | 61 ++
.../update/ProxyConfigUpdateSavePillars.java | 86 ++
.../update/ProxyConfigUpdateValidation.java | 165 ++++
java/code/src/com/suse/rest/RestClient.java | 152 ++++
.../com/suse/rest/RestClientException.java | 96 +++
java/code/src/com/suse/rest/RestRequest.java | 90 +++
.../com/suse/rest/RestRequestAuthEnum.java | 25 +
.../src/com/suse/rest/RestRequestBuilder.java | 164 ++++
.../com/suse/rest/RestRequestMethodEnum.java | 23 +
java/code/src/com/suse/rest/RestResponse.java | 88 ++
.../code/webapp/WEB-INF/nav/system_detail.xml | 5 +
.../fragments/systems/system-header.jspf | 4 +-
java/code/webapp/WEB-INF/struts-config.xml | 1 +
...nges.rjpmestre.simplified-proxy-onboarding | 1 +
.../spacewalk/common/data/rhnActionType.sql | 2 +
.../common/data/rhnSGTypeBaseAddonCompat.sql | 5 +
.../common/data/rhnServerGroupType.sql | 11 +
.../data/rhnServerServerGroupArchCompat.sql | 24 +
.../postgres/procs/create_new_org.sql | 9 +
...nges.rjpmestre.simplified-proxy-onboarding | 1 +
.../010-proxy-entitled.sql | 72 ++
...-proxy_configuration_apply-action-type.sql | 3 +
.../salt/apply_proxy_config.sls | 141 ++++
.../salt/install_mgrpxy.service | 22 +
...nges.rjpmestre.simplified-proxy-onboarding | 1 +
web/html/src/components/buttons.tsx | 7 +
web/html/src/components/icontag.tsx | 1 +
web/html/src/manager/minion/index.ts | 1 +
.../minion/proxy/proxy-config-messages.tsx | 40 +
.../minion/proxy/proxy-config.renderer.tsx | 25 +
.../src/manager/minion/proxy/proxy-config.tsx | 755 ++++++++++++++++++
web/html/src/manager/systems/list-filter.tsx | 1 +
...nges.rjpmestre.simplified-proxy-onboarding | 1 +
75 files changed, 4824 insertions(+), 24 deletions(-)
create mode 100644 java/code/src/com/redhat/rhn/common/RhnError.java
create mode 100644 java/code/src/com/redhat/rhn/common/RhnErrorReport.java
create mode 100644 java/code/src/com/redhat/rhn/common/RhnGeneralException.java
create mode 100644 java/code/src/com/redhat/rhn/common/RhnReportStrategy.java
create mode 100644 java/code/src/com/redhat/rhn/domain/action/ProxyConfigurationApplyAction.java
create mode 100644 java/code/src/com/redhat/rhn/domain/entitlement/ProxyEntitlement.java
create mode 100644 java/code/src/com/suse/manager/webui/controllers/ProxyConfigurationController.java
create mode 100644 java/code/src/com/suse/manager/webui/templates/minion/proxy-config.jade
create mode 100644 java/code/src/com/suse/manager/webui/utils/gson/ProxyConfigUpdateJson.java
create mode 100644 java/code/src/com/suse/proxy/ProxyConfigUtils.java
create mode 100644 java/code/src/com/suse/proxy/ProxyContainerImagesEnum.java
create mode 100644 java/code/src/com/suse/proxy/ProxyRegistryUtils.java
create mode 100644 java/code/src/com/suse/proxy/RegistryUrl.java
create mode 100644 java/code/src/com/suse/proxy/get/ProxyConfigGet.java
create mode 100644 java/code/src/com/suse/proxy/model/ProxyConfig.java
create mode 100644 java/code/src/com/suse/proxy/model/ProxyConfigImage.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdate.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateAcquisitor.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateApplySaltState.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateContext.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateContextHandler.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateFileAcquisitor.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateRegistryPreConditions.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateSavePillars.java
create mode 100644 java/code/src/com/suse/proxy/update/ProxyConfigUpdateValidation.java
create mode 100644 java/code/src/com/suse/rest/RestClient.java
create mode 100644 java/code/src/com/suse/rest/RestClientException.java
create mode 100644 java/code/src/com/suse/rest/RestRequest.java
create mode 100644 java/code/src/com/suse/rest/RestRequestAuthEnum.java
create mode 100644 java/code/src/com/suse/rest/RestRequestBuilder.java
create mode 100644 java/code/src/com/suse/rest/RestRequestMethodEnum.java
create mode 100644 java/code/src/com/suse/rest/RestResponse.java
create mode 100644 java/spacewalk-java.changes.rjpmestre.simplified-proxy-onboarding
create mode 100644 schema/spacewalk/susemanager-schema.changes.rjpmestre.simplified-proxy-onboarding
create mode 100644 schema/spacewalk/upgrade/susemanager-schema-5.1.2-to-susemanager-schema-5.1.3/010-proxy-entitled.sql
create mode 100644 schema/spacewalk/upgrade/susemanager-schema-5.1.2-to-susemanager-schema-5.1.3/011-add-proxy_configuration_apply-action-type.sql
create mode 100644 susemanager-utils/susemanager-sls/salt/apply_proxy_config.sls
create mode 100644 susemanager-utils/susemanager-sls/salt/install_mgrpxy.service
create mode 100644 susemanager-utils/susemanager-sls/susemanager-sls.changes.rjpmestre.simplified-proxy-onboarding
create mode 100644 web/html/src/manager/minion/proxy/proxy-config-messages.tsx
create mode 100644 web/html/src/manager/minion/proxy/proxy-config.renderer.tsx
create mode 100644 web/html/src/manager/minion/proxy/proxy-config.tsx
create mode 100644 web/spacewalk-web.changes.rjpmestre.simplified-proxy-onboarding
diff --git a/java/buildconf/LICENSE.txt b/java/buildconf/LICENSE.txt
index 0c52c8aea8fc..e737706cd03b 100644
--- a/java/buildconf/LICENSE.txt
+++ b/java/buildconf/LICENSE.txt
@@ -1,5 +1,5 @@
^/\*$
-(^ \* Copyright \(c\) (20([0123]\d|20)--)?20(1\d|2[01234]) (Red Hat, Inc.|SUSE LLC)$)+
+(^ \* Copyright \(c\) (20([01234]\d|20)--)?20(1\d|2[012345]) (Red Hat, Inc.|SUSE LLC)$)+
^ \*$
^ \* This software is licensed to you under the GNU General Public License,$
^ \* version 2 \(GPLv2\). There is NO WARRANTY for this software, express or$
diff --git a/java/code/src/com/redhat/rhn/common/ErrorReportingStrategies.java b/java/code/src/com/redhat/rhn/common/ErrorReportingStrategies.java
index fb3787fb2adf..28c69ac62b7b 100644
--- a/java/code/src/com/redhat/rhn/common/ErrorReportingStrategies.java
+++ b/java/code/src/com/redhat/rhn/common/ErrorReportingStrategies.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024 SUSE LLC
+ * Copyright (c) 2024-2025 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
@@ -32,6 +32,24 @@ private ErrorReportingStrategies() {
}
private static final Map
,
+ },
+ ]}
+ />
+ );
+ } else if (messagesIn.length > 0) {
+ return (
+
+ );
+ } else if (loading) {
+ return (
+ {t("Applying proxy configuration: waiting for a response...")},
+ },
+ ]}
+ />
+ );
+ }
+ return null;
+};
diff --git a/web/html/src/manager/minion/proxy/proxy-config.renderer.tsx b/web/html/src/manager/minion/proxy/proxy-config.renderer.tsx
new file mode 100644
index 000000000000..8876bd6538a3
--- /dev/null
+++ b/web/html/src/manager/minion/proxy/proxy-config.renderer.tsx
@@ -0,0 +1,25 @@
+import SpaRenderer from "core/spa/spa-renderer";
+
+import { ProxyConfig } from "./proxy-config";
+
+export const renderer = (
+ id: string,
+ {
+ serverId,
+ isUyuni,
+ parents,
+ currentConfig,
+ initFailMessage,
+ }: { serverId: string; isUyuni: boolean; parents: any[]; currentConfig: any; initFailMessage: string }
+) => {
+ return SpaRenderer.renderNavigationReact(
+ ,
+ document.getElementById(id)
+ );
+};
diff --git a/web/html/src/manager/minion/proxy/proxy-config.tsx b/web/html/src/manager/minion/proxy/proxy-config.tsx
new file mode 100644
index 000000000000..a70491ea82d3
--- /dev/null
+++ b/web/html/src/manager/minion/proxy/proxy-config.tsx
@@ -0,0 +1,755 @@
+import * as React from "react";
+import { useCallback, useEffect, useState } from "react";
+
+import { debounce } from "lodash";
+
+import { AsyncButton, SubmitButton } from "components/buttons";
+import { Select } from "components/input";
+import { Form } from "components/input/form/Form";
+import { FormMultiInput } from "components/input/form-multi-input/FormMultiInput";
+import { unflattenModel } from "components/input/form-utils";
+import { Radio } from "components/input/radio/Radio";
+import { Text } from "components/input/text/Text";
+import { Panel } from "components/panels/Panel";
+import { TopPanel } from "components/panels/TopPanel";
+import Validation from "components/validation";
+
+import Network from "utils/network";
+
+import { ContainerConfigMessages } from "./proxy-config-messages";
+
+// See java/code/src/com/suse/manager/webui/templates/proxy/proxy-config.jade
+enum UseCertsMode {
+ Replace = "replace",
+ Keep = "keep",
+}
+
+enum SourceMode {
+ Registry = "registry",
+ RPM = "rpm",
+}
+
+enum RegistryMode {
+ Simple = "simple",
+ Advanced = "advanced",
+}
+
+type ProxyConfigModel = {
+ rootCA: string;
+ rootCA_safe?: string;
+ proxyCertificate: string;
+ proxyCertificate_safe?: string;
+ proxyKey: string;
+ proxyKey_safe?: string;
+ intermediateCAs?: string[];
+ intermediateCAs_safe?: string[];
+ proxyAdminEmail: string;
+ maxSquidCacheSize: string;
+ parentFQDN: string;
+ proxyPort: string;
+ useCertsMode: UseCertsMode;
+ sourceMode: SourceMode;
+ registryMode: RegistryMode;
+ registryBaseURL: string;
+ registryBaseTag: string;
+ registryHttpdURL: string;
+ registryHttpdTag: string;
+ registrySaltbrokerURL: string;
+ registrySaltbrokerTag: string;
+ registrySquidURL: string;
+ registrySquidTag: string;
+ registrySshURL: string;
+ registrySshTag: string;
+ registryTftpdURL: string;
+ registryTftpdTag: string;
+};
+
+const modelDefaults = {
+ rootCA: "",
+ proxyCertificate: "",
+ proxyKey: "",
+ proxyAdminEmail: "",
+ maxSquidCacheSize: "",
+ parentFQDN: "",
+ proxyPort: "8022",
+ useCertsMode: UseCertsMode.Replace,
+ sourceMode: SourceMode.Registry,
+ registryMode: RegistryMode.Simple,
+ registryBaseURL: "",
+ registryBaseTag: "",
+ registryHttpdURL: "",
+ registryHttpdTag: "",
+ registrySaltbrokerURL: "",
+ registrySaltbrokerTag: "",
+ registrySquidURL: "",
+ registrySquidTag: "",
+ registrySshURL: "",
+ registrySshTag: "",
+ registryTftpdURL: "",
+ registryTftpdTag: "",
+};
+
+interface Parent {
+ id: number;
+ name: string;
+ selected: boolean;
+ disabled: boolean;
+}
+
+interface ProxyConfigProps {
+ serverId: string;
+ isUyuni: boolean;
+ parents: Parent[];
+ currentConfig: ProxyConfigModel;
+ initFailMessage?: string;
+}
+
+type TagOptions = {
+ registryBaseURL?: string[];
+ registryHttpdURL?: string[];
+ registrySaltbrokerURL?: string[];
+ registrySquidURL?: string[];
+ registrySshURL?: string[];
+ registryTftpdURL?: string[];
+};
+
+const imageNames = [
+ "registryHttpdURL",
+ "registrySaltbrokerURL",
+ "registrySquidURL",
+ "registrySshURL",
+ "registryTftpdURL",
+];
+
+export function ProxyConfig({ serverId, isUyuni, parents, currentConfig, initFailMessage }: ProxyConfigProps) {
+ const [messages, setMessages] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [success, setSuccess] = useState();
+ const [isValidated, setIsValidated] = useState(false);
+ const [errors, setErrors] = useState({});
+ const [tagOptions, setTagOptions] = useState({});
+
+ const showUseCertsMode =
+ currentConfig.rootCA_safe || currentConfig.proxyKey_safe || currentConfig.proxyCertificate_safe;
+ const originalConfig = { ...currentConfig };
+
+ const [model, setModel] = useState(() => {
+ const initialModel = {
+ ...modelDefaults,
+ ...currentConfig,
+ };
+
+ if (showUseCertsMode) {
+ return {
+ ...initialModel,
+ useCertsMode: UseCertsMode.Keep,
+ };
+ }
+
+ return initialModel;
+ });
+
+ useEffect(() => {
+ imageNames.forEach((url) => {
+ if (currentConfig[url]) {
+ retrieveRegistryTags(currentConfig, url);
+ }
+ });
+ if (initFailMessage) {
+ setSuccess(false);
+ setMessages([initFailMessage]);
+ }
+ }, [currentConfig]);
+
+ const registryUrlExample = isUyuni ? "registry.opensuse.org/.../uyuni" : "registry.suse.com/suse/manager/...";
+
+ const onSubmit = () => {
+ setMessages([]);
+ setLoading(true);
+
+ const fileFields = ["rootCA", "intermediateCAs", "proxyCertificate", "proxyKey"];
+
+ const fileReaders = Object.keys(model)
+ .filter((key) => {
+ const matcher = key.match(/^([a-zA-Z0-9]*[A-Za-z])[0-9]*$/);
+ const fieldName = matcher ? matcher[1] : key;
+ return fileFields.includes(fieldName);
+ })
+ .map((fieldName) => {
+ const field = document.getElementById(fieldName) as HTMLInputElement;
+ if (field?.files?.[0]) {
+ const file = field.files[0];
+ return new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ if (e.target?.result instanceof ArrayBuffer) {
+ // Should never happen since we call readAsText, just quiets tsc
+ resolve(undefined);
+ } else {
+ resolve({ [fieldName]: e.target?.result });
+ }
+ };
+ reader.readAsText(file);
+ });
+ }
+ return undefined;
+ })
+ .filter((promise) => promise !== undefined);
+
+ Promise.all(fileReaders).then((values) => {
+ const commonData = {
+ serverId: serverId,
+ proxyPort: model.proxyPort ? parseInt(model.proxyPort, 10) : 8022,
+ parentFQDN: model.parentFQDN,
+ maxSquidCacheSize: parseInt(model.maxSquidCacheSize, 10),
+ proxyAdminEmail: model.proxyAdminEmail,
+ sourceMode: model.sourceMode,
+ registryMode: model.registryMode,
+ useCertsMode: model.useCertsMode,
+ };
+ const registryData =
+ model.sourceMode === SourceMode.Registry
+ ? Object.assign(
+ {},
+ model.registryMode === RegistryMode.Simple
+ ? {
+ registryBaseURL: model.registryBaseURL,
+ registryBaseTag: model.registryBaseTag,
+ }
+ : {
+ registryHttpdURL: model.registryHttpdURL,
+ registryHttpdTag: model.registryHttpdTag,
+ registrySaltbrokerURL: model.registrySaltbrokerURL,
+ registrySaltbrokerTag: model.registrySaltbrokerTag,
+ registrySquidURL: model.registrySquidURL,
+ registrySquidTag: model.registrySquidTag,
+ registrySshURL: model.registrySshURL,
+ registrySshTag: model.registrySshTag,
+ registryTftpdURL: model.registryTftpdURL,
+ registryTftpdTag: model.registryTftpdTag,
+ }
+ )
+ : {};
+
+ const formData = unflattenModel(Object.assign({}, commonData, registryData, ...values));
+ Network.post("/rhn/manager/systems/details/proxy-config", formData).then(
+ (data) => {
+ setSuccess(data.success);
+ setMessages([]);
+ setLoading(false);
+ },
+ (xhr) => {
+ try {
+ setSuccess(false);
+ setMessages(JSON.parse(xhr.responseText).messages);
+ setLoading(false);
+ } catch (err) {
+ const errMessages =
+ xhr.status === 0
+ ? t("Request interrupted or invalid response received from the server.")
+ : Network.errorMessageByStatus(xhr.status)[0];
+ setSuccess(false);
+ setMessages([errMessages]);
+ setLoading(false);
+ }
+ }
+ );
+ });
+ };
+
+ const clearFields = () => {
+ setModel(modelDefaults);
+ };
+
+ const onValidate = (isValidated: boolean) => {
+ setIsValidated(isValidated);
+ };
+
+ const onChange = (newModel) => {
+ setModel(Object.assign({}, newModel));
+ asyncValidate(newModel);
+ };
+
+ const onAddField = (fieldName: string) => {
+ return (index: number) => setModel(Object.assign({}, model, { [fieldName + index]: "" }));
+ };
+
+ const onRemoveField = (fieldName: string) => {
+ return (index: number) => {
+ const newModel = { ...model };
+ delete newModel[`${fieldName}${index}`];
+ setModel(newModel);
+ };
+ };
+
+ /**
+ * Restore registry inputs
+ */
+ const restoreRegistryInputs = () => {
+ setModel({
+ ...model,
+ registryMode: RegistryMode.Advanced,
+ registryHttpdURL: originalConfig.registryHttpdURL,
+ registryHttpdTag: originalConfig.registryHttpdTag,
+ registrySaltbrokerURL: originalConfig.registrySaltbrokerURL,
+ registrySaltbrokerTag: originalConfig.registrySaltbrokerTag,
+ registrySquidURL: originalConfig.registrySquidURL,
+ registrySquidTag: originalConfig.registrySquidTag,
+ registrySshURL: originalConfig.registrySshURL,
+ registrySshTag: originalConfig.registrySshTag,
+ registryTftpdURL: originalConfig.registryTftpdURL,
+ registryTftpdTag: originalConfig.registryTftpdTag,
+ });
+ };
+
+ const onChangeSourceMode = (e, v) => {
+ if (SourceMode.Registry === v && SourceMode.Registry === originalConfig.sourceMode) {
+ restoreRegistryInputs();
+ }
+ };
+
+ const onChangeRegistryeMode = (e, v) => {
+ if (RegistryMode.Advanced === v && Object.keys(originalConfig).length > 0) {
+ restoreRegistryInputs();
+ }
+ };
+
+ const getMinionNames = (data: any[] = []) => {
+ return Array.from(new Set(data.map((item) => item.name))).sort();
+ };
+
+ const useDebounce = (callback: (...args: any) => any, timeoutMs: number) =>
+ useCallback(debounce(callback, timeoutMs), []);
+
+ const asyncValidate = useDebounce(async (newModel: typeof model) => {
+ setErrors({});
+ if (newModel.registryMode === RegistryMode.Simple) {
+ if (newModel.registryBaseURL && !tagOptions.registryBaseURL?.length) {
+ retrieveRegistryTags(newModel, "registryBaseURL");
+ }
+ } else if (newModel.registryMode === RegistryMode.Advanced) {
+ imageNames.forEach((property) => {
+ if (newModel[property] && !tagOptions[property]?.length) {
+ retrieveRegistryTags(newModel, property);
+ }
+ });
+ }
+ }, 500);
+
+ const retrieveRegistryTags = async (newModel: typeof model, name) => {
+ const registryUrl = newModel[name];
+ if (!registryUrl) {
+ setErrors((prev) => ({ ...prev, [name]: [] }));
+ setTagOptions((prev) => ({ ...prev, [name]: [] }));
+ return;
+ }
+
+ try {
+ const response = await Network.post("/rhn/manager/systems/details/proxy-config/registry-url", {
+ registryUrl: registryUrl,
+ isExact: name !== "registryBaseURL",
+ });
+
+ if (response?.success) {
+ setErrors((prev) => ({ ...prev, [name]: [] }));
+ setTagOptions((prev) => ({
+ ...prev,
+ [name]: response.data || [],
+ }));
+ } else {
+ const errorMessage = response?.messages?.join(", ") || "Validation Failed";
+ setErrors((prev) => ({ ...prev, [name]: errorMessage }));
+ setTagOptions((prev) => ({ ...prev, [name]: [] }));
+ }
+ } catch (error) {
+ setErrors((prev) => ({ ...prev, [name]: "Error during validation" }));
+ setTagOptions((prev) => ({ ...prev, [name]: [] }));
+ }
+ };
+
+ return (
+
+ {t("Convert an already onboarded minion to a proxy or update the configuration of an existing proxy.")}
+ {ContainerConfigMessages(success, messages, loading)}
+ {!initFailMessage && (
+
+ )}
+
+ );
+}
diff --git a/web/html/src/manager/systems/list-filter.tsx b/web/html/src/manager/systems/list-filter.tsx
index d30e6866dddb..f307a9059ba7 100644
--- a/web/html/src/manager/systems/list-filter.tsx
+++ b/web/html/src/manager/systems/list-filter.tsx
@@ -18,6 +18,7 @@ const SYSTEM_TYPE_OPTIONS = [
{ value: "osimage_build_host", label: t("OS Image Build Host") },
{ value: "salt_entitled", label: t("Salt") },
{ value: "virtualization_host", label: t("Virtualization Host") },
+ { value: "proxy_entitled", label: t("Proxy") },
];
const STATUS_TYPE_OPTIONS = [
diff --git a/web/spacewalk-web.changes.rjpmestre.simplified-proxy-onboarding b/web/spacewalk-web.changes.rjpmestre.simplified-proxy-onboarding
new file mode 100644
index 000000000000..648110a2b22b
--- /dev/null
+++ b/web/spacewalk-web.changes.rjpmestre.simplified-proxy-onboarding
@@ -0,0 +1 @@
+- Add proxy onboarding feature