diff --git a/app/kubernetes/components/yaml-inspector/yamlInspector.html b/app/kubernetes/components/yaml-inspector/yamlInspector.html
index 5db527700..bca7d5a4c 100644
--- a/app/kubernetes/components/yaml-inspector/yamlInspector.html
+++ b/app/kubernetes/components/yaml-inspector/yamlInspector.html
@@ -21,5 +21,7 @@
copied
+
+
diff --git a/app/kubernetes/components/yaml-inspector/yamlInspectorController.js b/app/kubernetes/components/yaml-inspector/yamlInspectorController.js
index e0c737710..dd5986e10 100644
--- a/app/kubernetes/components/yaml-inspector/yamlInspectorController.js
+++ b/app/kubernetes/components/yaml-inspector/yamlInspectorController.js
@@ -1,4 +1,6 @@
import angular from 'angular';
+import YAML from 'yaml';
+import { FeatureId } from '@/react/portainer/feature-flags/enums';
class KubernetesYamlInspectorController {
/* @ngInject */
@@ -8,6 +10,23 @@ class KubernetesYamlInspectorController {
this.expanded = false;
}
+ cleanYamlUnwantedFields(yml) {
+ try {
+ const ymls = yml.split('---');
+ const cleanYmls = ymls.map((yml) => {
+ const y = YAML.parse(yml);
+ if (y.metadata) {
+ delete y.metadata.managedFields;
+ delete y.metadata.resourceVersion;
+ }
+ return YAML.stringify(y);
+ });
+ return cleanYmls.join('---\n');
+ } catch (e) {
+ return yml;
+ }
+ }
+
copyYAML() {
this.clipboard.copyText(this.data);
$('#copyNotificationYAML').show().fadeOut(2500);
@@ -19,6 +38,11 @@ class KubernetesYamlInspectorController {
$(selector).css({ height: height });
this.expanded = !this.expanded;
}
+
+ $onInit() {
+ this.data = this.cleanYamlUnwantedFields(this.data);
+ this.limitedFeature = FeatureId.K8S_EDIT_YAML;
+ }
}
export default KubernetesYamlInspectorController;
diff --git a/app/kubernetes/react/views/index.ts b/app/kubernetes/react/views/index.ts
index 89e9b14c3..d2408b12e 100644
--- a/app/kubernetes/react/views/index.ts
+++ b/app/kubernetes/react/views/index.ts
@@ -4,6 +4,7 @@ import { r2a } from '@/react-tools/react2angular';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
+import { YAMLReplace } from '@/kubernetes/react/views/yamlReplace';
import { IngressesDatatableView } from '@/react/kubernetes/ingresses/IngressDatatable';
import { CreateIngressView } from '@/react/kubernetes/ingresses/CreateIngressView';
@@ -19,4 +20,10 @@ export const viewsModule = angular
.component(
'kubernetesIngressesCreateView',
r2a(withUIRouter(withReactQuery(withCurrentUser(CreateIngressView))), [])
+ )
+ .component(
+ 'yamlReplace',
+ r2a(withUIRouter(withReactQuery(withCurrentUser(YAMLReplace))), [
+ 'featureId',
+ ])
).name;
diff --git a/app/kubernetes/react/views/yamlReplace/index.tsx b/app/kubernetes/react/views/yamlReplace/index.tsx
new file mode 100644
index 000000000..1c45da9d2
--- /dev/null
+++ b/app/kubernetes/react/views/yamlReplace/index.tsx
@@ -0,0 +1,28 @@
+import { FeatureId } from '@/react/portainer/feature-flags/enums';
+
+import { Button } from '@@/buttons';
+import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
+
+interface Props {
+ featureId: FeatureId;
+}
+export function YAMLReplace({ featureId }: Props) {
+ return (
+
+
+
+ );
+}
diff --git a/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css
new file mode 100644
index 000000000..856845d55
--- /dev/null
+++ b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.module.css
@@ -0,0 +1,41 @@
+:global(portainer-tooltip) {
+ @apply inline-flex;
+}
+
+.tooltip-wrapper {
+ display: inline-block;
+ position: relative;
+}
+
+.tooltip {
+ background-color: var(--bg-tooltip-color) !important;
+ padding: 0.833em 1em !important;
+ color: var(--text-tooltip-color) !important;
+ border-radius: 10px !important;
+ box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
+ max-width: 400px;
+ text-align: left;
+ font-size: 12px !important;
+ font-weight: 400;
+}
+
+.tooltip:hover {
+ visibility: visible !important;
+ opacity: 1 !important;
+}
+
+.tooltip-container {
+ line-height: 18px;
+}
+
+.tooltip-heading {
+ font-weight: 500;
+}
+
+.tooltip-beteaser {
+ color: var(--ui-warning-5);
+}
+
+.tooltip-beteaser:hover {
+ color: var(--ui-warning-5);
+}
diff --git a/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.tsx b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.tsx
new file mode 100644
index 000000000..6f0beb3b2
--- /dev/null
+++ b/app/react/components/Tip/TooltipWithChildren/TooltipWithChildren.tsx
@@ -0,0 +1,83 @@
+import ReactTooltip from 'react-tooltip';
+import clsx from 'clsx';
+import _ from 'lodash';
+import ReactDOMServer from 'react-dom/server';
+
+import { FeatureId } from '@/react/portainer/feature-flags/enums';
+
+import { getFeatureDetails } from '@@/BEFeatureIndicator/utils';
+
+import styles from './TooltipWithChildren.module.css';
+
+type Position = 'top' | 'right' | 'bottom' | 'left';
+
+export interface Props {
+ position?: Position;
+ message: string;
+ className?: string;
+ children: React.ReactNode;
+ heading?: string;
+ BEFeatureID?: FeatureId;
+}
+
+export function TooltipWithChildren({
+ message,
+ position = 'bottom',
+ className,
+ children,
+ heading,
+ BEFeatureID,
+}: Props) {
+ const id = _.uniqueId('tooltip-');
+
+ const { url, limitedToBE } = BEFeatureID
+ ? getFeatureDetails(BEFeatureID)
+ : { url: '', limitedToBE: false };
+
+ const messageHTML = (
+
+ );
+
+ return (
+
+ {children}
+
+
+ );
+}
diff --git a/app/react/components/Tip/TooltipWithChildren/index.ts b/app/react/components/Tip/TooltipWithChildren/index.ts
new file mode 100644
index 000000000..42df25cc4
--- /dev/null
+++ b/app/react/components/Tip/TooltipWithChildren/index.ts
@@ -0,0 +1 @@
+export { TooltipWithChildren } from './TooltipWithChildren';
diff --git a/app/react/components/buttons/Button.css b/app/react/components/buttons/Button.css
index aaaf34a79..300982b7f 100644
--- a/app/react/components/buttons/Button.css
+++ b/app/react/components/buttons/Button.css
@@ -8,3 +8,9 @@
.btn-none:focus {
outline: none;
}
+
+.btn-warninglight {
+ @apply border-warning-5 bg-warning-2 text-black;
+ @apply th-dark:bg-warning-5 th-dark:bg-opacity-10 th-dark:text-white;
+ @apply th-highcontrast:bg-warning-5 th-highcontrast:bg-opacity-10 th-highcontrast:text-white;
+}
diff --git a/app/react/components/buttons/Button.tsx b/app/react/components/buttons/Button.tsx
index 19128c7b1..618dae097 100644
--- a/app/react/components/buttons/Button.tsx
+++ b/app/react/components/buttons/Button.tsx
@@ -21,6 +21,7 @@ type Color =
| 'link'
| 'light'
| 'dangerlight'
+ | 'warninglight'
| 'none';
type Size = 'xsmall' | 'small' | 'medium' | 'large';
diff --git a/app/react/portainer/feature-flags/enums.ts b/app/react/portainer/feature-flags/enums.ts
index 7544e8aee..e8887daa3 100644
--- a/app/react/portainer/feature-flags/enums.ts
+++ b/app/react/portainer/feature-flags/enums.ts
@@ -13,6 +13,7 @@ export enum FeatureId {
K8S_RESOURCE_POOL_LB_QUOTA = 'k8s-resourcepool-Ibquota',
K8S_RESOURCE_POOL_STORAGE_QUOTA = 'k8s-resourcepool-storagequota',
K8S_CREATE_FROM_KUBECONFIG = 'k8s-create-from-kubeconfig',
+ K8S_EDIT_YAML = 'k8s-edit-yaml',
KAAS_PROVISIONING = 'kaas-provisioning',
NOMAD = 'nomad',
RBAC_ROLES = 'rbac-roles',
diff --git a/app/react/portainer/feature-flags/feature-flags.service.ts b/app/react/portainer/feature-flags/feature-flags.service.ts
index c108cac25..8f806925c 100644
--- a/app/react/portainer/feature-flags/feature-flags.service.ts
+++ b/app/react/portainer/feature-flags/feature-flags.service.ts
@@ -37,6 +37,7 @@ export async function init(edition: Edition) {
[FeatureId.POD_SECURITY_POLICY_CONSTRAINT]: Edition.BE,
[FeatureId.HIDE_DOCKER_HUB_ANONYMOUS]: Edition.BE,
[FeatureId.CUSTOM_LOGIN_BANNER]: Edition.BE,
+ [FeatureId.K8S_EDIT_YAML]: Edition.BE,
[FeatureId.ENFORCE_DEPLOYMENT_OPTIONS]: Edition.BE,
[FeatureId.K8S_ADM_ONLY_USR_INGRESS_DEPLY]: Edition.BE,
};