mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(kubernetes): edit yaml support EE-2855 (#8016)
This commit is contained in:
parent
7006c17ce4
commit
0f0513c684
11 changed files with 195 additions and 0 deletions
|
@ -21,5 +21,7 @@
|
|||
<span id="copyNotificationYAML" style="display: none" class="small vertical-center ml-1">
|
||||
<pr-icon class="vertical-center" icon="'check'" size="'md'" mode="'success'" feather="true"></pr-icon> copied
|
||||
</span>
|
||||
|
||||
<yaml-replace class="float-right" feature-id="$ctrl.limitedFeature"></yaml-replace>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
28
app/kubernetes/react/views/yamlReplace/index.tsx
Normal file
28
app/kubernetes/react/views/yamlReplace/index.tsx
Normal file
|
@ -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 (
|
||||
<TooltipWithChildren
|
||||
className="float-right"
|
||||
heading="Apply YAML changes"
|
||||
BEFeatureID={featureId}
|
||||
message="Applies any changes that you make in the YAML editor by calling the Kubernetes API to patch the relevant resources. Any resource removals or unexpected resource additions that you make in the YAML will be ignored. Note that editing is disabled for resources in namespaces marked as system."
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
color="warninglight"
|
||||
size="small"
|
||||
onClick={() => {}}
|
||||
disabled
|
||||
>
|
||||
Apply changes
|
||||
</Button>
|
||||
</TooltipWithChildren>
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 = (
|
||||
<div className={styles.tooltipContainer}>
|
||||
<div
|
||||
className={clsx(
|
||||
'w-full mb-2 inline-flex justify-between',
|
||||
styles.tooltipHeading
|
||||
)}
|
||||
>
|
||||
{heading && <span>{heading}</span>}
|
||||
{BEFeatureID && limitedToBE && (
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.tooltipBeteaser}
|
||||
>
|
||||
Business Edition Only
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<span
|
||||
data-html
|
||||
data-multiline
|
||||
data-tip={ReactDOMServer.renderToString(messageHTML)}
|
||||
data-for={id}
|
||||
className={clsx(styles.icon, 'inline-flex text-base')}
|
||||
>
|
||||
{children}
|
||||
<ReactTooltip
|
||||
id={id}
|
||||
multiline
|
||||
type="info"
|
||||
place={position}
|
||||
effect="solid"
|
||||
className={clsx(styles.tooltip, className)}
|
||||
arrowColor="var(--bg-tooltip-color)"
|
||||
delayHide={400}
|
||||
clickable
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
1
app/react/components/Tip/TooltipWithChildren/index.ts
Normal file
1
app/react/components/Tip/TooltipWithChildren/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { TooltipWithChildren } from './TooltipWithChildren';
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ type Color =
|
|||
| 'link'
|
||||
| 'light'
|
||||
| 'dangerlight'
|
||||
| 'warninglight'
|
||||
| 'none';
|
||||
type Size = 'xsmall' | 'small' | 'medium' | 'large';
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue