Skip to content

Commit e21ce96

Browse files
authored
Merge pull request #1715 from iamfaran/add-new-deploy-plugin-endpoint
Add new deploy plugin endpoint for Environments
2 parents 767da72 + 38cd16b commit e21ce96

File tree

8 files changed

+213
-9
lines changed

8 files changed

+213
-9
lines changed

client/packages/lowcoder/src/pages/setting/environments/components/DeployItemModal.tsx

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// components/DeployItemModal.tsx
22
import React, { useState, useEffect } from 'react';
3-
import { Modal, Form, Select, Checkbox, Button, Spin, Input, Tag, Space } from 'antd';
3+
import { Modal, Form, Select, Checkbox, Button, Spin, Input, Tag, Space, Alert } from 'antd';
44
import { messageInstance } from 'lowcoder-design/src/components/GlobalInstances';
55
import { Environment } from '../types/environment.types';
66
import { DeployableItemConfig } from '../types/deployable-item.types';
77
import { useEnvironmentContext } from '../context/EnvironmentContext';
88
import { getEnvironmentTagColor, formatEnvironmentType } from '../utils/environmentUtils';
9+
import { ExclamationCircleOutlined } from '@ant-design/icons';
10+
import { showFirstCredentialOverwriteConfirm, showSecondCredentialOverwriteConfirm } from './credentialConfirmations';
911

1012
interface DeployItemModalProps {
1113
visible: boolean;
@@ -27,10 +29,12 @@ function DeployItemModal({
2729
const [form] = Form.useForm();
2830
const { environments, isLoading } = useEnvironmentContext();
2931
const [deploying, setDeploying] = useState(false);
32+
const [credentialConfirmationStep, setCredentialConfirmationStep] = useState(0); // 0: not started, 1: first confirmation, 2: confirmed
3033

3134
useEffect(() => {
3235
if (visible) {
3336
form.resetFields();
37+
setCredentialConfirmationStep(0);
3438
}
3539
}, [visible, form]);
3640

@@ -39,6 +43,40 @@ function DeployItemModal({
3943
(env: Environment) => env.environmentId !== sourceEnvironment.environmentId && env.isLicensed !== false
4044
);
4145

46+
// Handle credential checkbox change with double confirmation
47+
const handleCredentialCheckboxChange = (checked: boolean, fieldName: string) => {
48+
if (!checked) {
49+
// If unchecking, reset confirmation and update form
50+
setCredentialConfirmationStep(0);
51+
form.setFieldsValue({ [fieldName]: false });
52+
return;
53+
}
54+
55+
// First confirmation
56+
if (credentialConfirmationStep === 0) {
57+
showFirstCredentialOverwriteConfirm({
58+
onOk: () => {
59+
setCredentialConfirmationStep(1);
60+
// Show second confirmation immediately
61+
showSecondCredentialOverwriteConfirm({
62+
onOk: () => {
63+
setCredentialConfirmationStep(2);
64+
form.setFieldsValue({ [fieldName]: true });
65+
},
66+
onCancel: () => {
67+
setCredentialConfirmationStep(0);
68+
form.setFieldsValue({ [fieldName]: false });
69+
}
70+
});
71+
},
72+
onCancel: () => {
73+
setCredentialConfirmationStep(0);
74+
form.setFieldsValue({ [fieldName]: false });
75+
}
76+
});
77+
}
78+
};
79+
4280
const handleDeploy = async () => {
4381
if (!config.deploy || !item) return;
4482

@@ -50,6 +88,12 @@ function DeployItemModal({
5088
messageInstance.error('Target environment not found');
5189
return;
5290
}
91+
92+
// Additional check for credential overwrite
93+
if (values.deployCredential && credentialConfirmationStep !== 2) {
94+
messageInstance.error('Please confirm credential overwrite before deploying');
95+
return;
96+
}
5397

5498
setDeploying(true);
5599

@@ -124,14 +168,36 @@ function DeployItemModal({
124168
{config.deploy?.fields.map(field => {
125169
switch (field.type) {
126170
case 'checkbox':
171+
// Special handling for credential-related checkboxes
172+
const isCredentialField = field.name === 'deployCredential';
127173
return (
128174
<Form.Item
129175
key={field.name}
130176
name={field.name}
131177
valuePropName="checked"
132178
initialValue={field.defaultValue}
133179
>
134-
<Checkbox>{field.label}</Checkbox>
180+
<Checkbox
181+
onChange={(e) => {
182+
if (isCredentialField) {
183+
handleCredentialCheckboxChange(e.target.checked, field.name);
184+
} else {
185+
// For non-credential checkboxes, handle normally
186+
form.setFieldsValue({ [field.name]: e.target.checked });
187+
}
188+
}}
189+
>
190+
{field.label}
191+
{isCredentialField && credentialConfirmationStep === 2 && (
192+
<Tag
193+
color="red"
194+
style={{ marginLeft: 8 }}
195+
icon={<ExclamationCircleOutlined />}
196+
>
197+
Confirmed
198+
</Tag>
199+
)}
200+
</Checkbox>
135201
</Form.Item>
136202
);
137203
case 'select':
@@ -168,7 +234,7 @@ function DeployItemModal({
168234
return null;
169235
}
170236
})}
171-
237+
172238
<Form.Item>
173239
<Button type="default" onClick={onClose} style={{ marginRight: 8 }}>
174240
Cancel
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
2+
import { Modal, Alert } from 'antd';
3+
import { ExclamationCircleOutlined, WarningOutlined } from '@ant-design/icons';
4+
5+
interface ConfirmHandlers {
6+
onOk: () => void;
7+
onCancel: () => void;
8+
}
9+
10+
/**
11+
* First-step confirmation modal (orange / warning).
12+
*/
13+
export function showFirstCredentialOverwriteConfirm({ onOk, onCancel }: ConfirmHandlers) {
14+
Modal.confirm({
15+
title: (
16+
<div style={{ display: 'flex', alignItems: 'center', color: '#ff7a00' }}>
17+
<WarningOutlined style={{ marginRight: 8, fontSize: 18 }} />
18+
<span style={{ fontSize: 16, fontWeight: 600 }}>Overwrite Credentials Warning</span>
19+
</div>
20+
),
21+
icon: null,
22+
content: (
23+
<div style={{ padding: '16px 0' }}>
24+
<Alert
25+
message="This action will overwrite existing credentials in the target environment."
26+
description={
27+
<div style={{ marginTop: 8 }}>
28+
<p style={{ margin: 0, fontWeight: 500 }}>
29+
This is a serious operation that may affect other applications and users.
30+
</p>
31+
<p style={{ margin: '8px 0 0 0', color: '#8c8c8c' }}>
32+
Are you sure you want to proceed?
33+
</p>
34+
</div>
35+
}
36+
type="warning"
37+
showIcon
38+
style={{ marginBottom: 0, border: '1px solid #fff2e8', borderRadius: 8 }}
39+
/>
40+
</div>
41+
),
42+
okText: 'Continue',
43+
cancelText: 'Cancel',
44+
okButtonProps: {
45+
style: { backgroundColor: '#ff7a00', borderColor: '#ff7a00', fontWeight: 500 }
46+
},
47+
cancelButtonProps: {
48+
style: { fontWeight: 500 }
49+
},
50+
width: 520,
51+
centered: false,
52+
onOk,
53+
onCancel
54+
});
55+
}
56+
57+
/**
58+
* Second-step (final) confirmation modal (red / danger).
59+
*/
60+
export function showSecondCredentialOverwriteConfirm({ onOk, onCancel }: ConfirmHandlers) {
61+
Modal.confirm({
62+
title: (
63+
<div style={{ display: 'flex', alignItems: 'center', color: '#ff4d4f' }}>
64+
<ExclamationCircleOutlined style={{ marginRight: 8, fontSize: 18 }} />
65+
<span style={{ fontSize: 16, fontWeight: 600 }}>Final Confirmation Required</span>
66+
</div>
67+
),
68+
icon: null,
69+
content: (
70+
<div style={{ padding: '16px 0' }}>
71+
<Alert
72+
message="Final Warning: Credential Overwrite"
73+
description={
74+
<div style={{ marginTop: 8 }}>
75+
<p style={{ margin: 0, fontWeight: 500 }}>
76+
You are about to overwrite credentials in the target environment. This action cannot be undone and may break existing integrations.
77+
</p>
78+
<p style={{ margin: '8px 0 0 0', color: '#8c8c8c' }}>
79+
Please confirm one more time.
80+
</p>
81+
</div>
82+
}
83+
type="error"
84+
showIcon
85+
style={{ marginBottom: 16, border: '1px solid #ffebee', borderRadius: 8 }}
86+
/>
87+
<div
88+
style={{
89+
padding: '12px 16px',
90+
backgroundColor: '#fff2f0',
91+
borderRadius: 8,
92+
border: '1px solid #ffccc7'
93+
}}
94+
>
95+
<p style={{ margin: 0, fontWeight: 600, color: '#cf1322', fontSize: 14 }}>
96+
Are you absolutely certain you want to overwrite the credentials?
97+
</p>
98+
</div>
99+
</div>
100+
),
101+
okText: 'Yes, Overwrite Credentials',
102+
okType: 'danger',
103+
cancelText: 'Cancel',
104+
okButtonProps: { style: { fontWeight: 500 } },
105+
cancelButtonProps: { style: { fontWeight: 500 } },
106+
width: 520,
107+
centered: false,
108+
onOk,
109+
onCancel
110+
});
111+
}

client/packages/lowcoder/src/pages/setting/environments/config/apps.config.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export const appsConfig: DeployableItemConfig = {
3939
label: 'Public To Marketplace',
4040
type: 'checkbox',
4141
defaultValue: false
42+
},
43+
{
44+
name: 'deployCredential',
45+
label: 'Overwrite Credentials',
46+
type: 'checkbox',
47+
defaultValue: false
4248
}
4349
],
4450
prepareParams: (item: App, values: any, sourceEnv: Environment, targetEnv: Environment) => {
@@ -51,6 +57,7 @@ export const appsConfig: DeployableItemConfig = {
5157
publicToAll: values.publicToAll,
5258
publicToMarketplace: values.publicToMarketplace,
5359
applicationGid: item.applicationGid,
60+
deployCredential: values.deployCredential ?? false
5461
};
5562
},
5663
execute: (params: any) => deployApp(params)

client/packages/lowcoder/src/pages/setting/environments/config/data-sources.config.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export const dataSourcesConfig: DeployableItemConfig = {
1616
label: 'Update Dependencies If Needed',
1717
type: 'checkbox',
1818
defaultValue: false
19+
},
20+
{
21+
name: 'deployCredential',
22+
label: 'Overwrite Credentials',
23+
type: 'checkbox',
24+
defaultValue: false
1925
}
2026
],
2127
prepareParams: (item: DataSource, values: any, sourceEnv: Environment, targetEnv: Environment) => {
@@ -24,7 +30,8 @@ export const dataSourcesConfig: DeployableItemConfig = {
2430
targetEnvId: targetEnv.environmentId,
2531
datasourceId: item.id,
2632
updateDependenciesIfNeeded: values.updateDependenciesIfNeeded,
27-
datasourceGid: item.gid
33+
datasourceGid: item.gid,
34+
deployCredential: values.deployCredential ?? false
2835
};
2936
},
3037
execute: (params: any) => deployDataSource(params)

client/packages/lowcoder/src/pages/setting/environments/config/workspace.config.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ export const workspaceConfig: DeployableItemConfig = {
1212
// Deploy configuration
1313
deploy: {
1414
singularLabel: 'Workspace',
15-
fields: [],
15+
fields: [
16+
{
17+
name: 'deployCredential',
18+
label: 'Overwrite Credentials',
19+
type: 'checkbox',
20+
defaultValue: false
21+
}
22+
],
1623
prepareParams: (item: Workspace, values: any, sourceEnv: Environment, targetEnv: Environment) => {
1724
if (!item.gid) {
1825
console.error('Missing workspace.gid when deploying workspace:', item);
@@ -22,7 +29,8 @@ export const workspaceConfig: DeployableItemConfig = {
2229
return {
2330
envId: sourceEnv.environmentId,
2431
targetEnvId: targetEnv.environmentId,
25-
workspaceId: item.gid
32+
workspaceId: item.gid,
33+
deployCredential: values.deployCredential ?? false
2634
};
2735
},
2836
execute: (params: any) => deployWorkspace(params)

client/packages/lowcoder/src/pages/setting/environments/services/apps.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface DeployAppParams {
2323
publishOnTarget?: boolean;
2424
publicToAll?: boolean;
2525
publicToMarketplace?: boolean;
26+
deployCredential: boolean;
2627
}
2728

2829

@@ -119,7 +120,8 @@ export const deployApp = async (params: DeployAppParams): Promise<boolean> => {
119120
updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false,
120121
publishOnTarget: params.publishOnTarget ?? false,
121122
publicToAll: params.publicToAll ?? false,
122-
publicToMarketplace: params.publicToMarketplace ?? false
123+
publicToMarketplace: params.publicToMarketplace ?? false,
124+
deployCredential: params.deployCredential
123125
}
124126
}
125127
);

client/packages/lowcoder/src/pages/setting/environments/services/datasources.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface DeployDataSourceParams {
2222
datasourceId: string;
2323
datasourceGid: string;
2424
updateDependenciesIfNeeded?: boolean;
25-
25+
deployCredential: boolean;
2626
}
2727
// Get data sources for a workspace - using your correct implementation
2828
export async function getWorkspaceDataSources(
@@ -159,6 +159,7 @@ export async function deployDataSource(params: DeployDataSourceParams): Promise<
159159
targetEnvId: params.targetEnvId,
160160
datasourceId: params.datasourceId,
161161
updateDependenciesIfNeeded: params.updateDependenciesIfNeeded ?? false,
162+
deployCredential: params.deployCredential
162163
}
163164
});
164165
if (response.status === 200) {

client/packages/lowcoder/src/pages/setting/environments/services/workspace.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,16 @@ export async function deployWorkspace(params: {
8484
envId: string;
8585
targetEnvId: string;
8686
workspaceId: string;
87+
deployCredential: boolean; // Mandatory parameter
8788
}): Promise<boolean> {
8889
try {
8990
// Use the new endpoint format with only essential parameters
9091
const response = await axios.post('/api/plugins/enterprise/org/deploy', null, {
9192
params: {
9293
orgGid: params.workspaceId, // Using workspaceId as orgGid
9394
envId: params.envId,
94-
targetEnvId: params.targetEnvId
95+
targetEnvId: params.targetEnvId,
96+
deployCredential: params.deployCredential
9597
}
9698
});
9799

0 commit comments

Comments
 (0)