whistle.mock-plugins 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -127,6 +127,8 @@
|
|
|
127
127
|
|
|
128
128
|
/** */
|
|
129
129
|
|
|
130
|
+
/** */
|
|
131
|
+
|
|
130
132
|
/** */
|
|
131
133
|
|
|
132
134
|
/** */
|
package/lib/rules-server.js
CHANGED
|
@@ -192,6 +192,50 @@ const validateRedirectRule = (interface) => {
|
|
|
192
192
|
return true;
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
+
// 生成随机数工具函数
|
|
196
|
+
const generateRandomValue = (pattern) => {
|
|
197
|
+
// 如果不是以@开头的模式,直接返回原值
|
|
198
|
+
if (!pattern || !pattern.startsWith('@')) {
|
|
199
|
+
return pattern;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const formatPattern = pattern.substring(1); // 去掉@前缀
|
|
203
|
+
// 为每个x生成一个随机字符(字母或数字)
|
|
204
|
+
return formatPattern.replace(/x/g, () => {
|
|
205
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
206
|
+
return chars.charAt(Math.floor(Math.random() * chars.length));
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// 构建重定向规则
|
|
211
|
+
const buildRedirectRule = (fullUrl, targetUrl, customHeaders, pattern, isUrlRedirect = 0) => {
|
|
212
|
+
if(!isUrlRedirect){
|
|
213
|
+
targetUrl = fullUrl.replace(pattern, targetUrl);
|
|
214
|
+
}else{
|
|
215
|
+
targetUrl = `redirect://${targetUrl}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let rule = `${fullUrl} ${targetUrl}`;
|
|
219
|
+
|
|
220
|
+
// 如果有自定义请求头,添加到规则中
|
|
221
|
+
if (customHeaders && Object.keys(customHeaders).length > 0) {
|
|
222
|
+
// 构建headerReplace规则,格式: headerReplace://req.header-name:pattern1=replacement1&pattern2=replacement2
|
|
223
|
+
const headerRuleLines = Object.entries(customHeaders).map(([key, value]) => {
|
|
224
|
+
// 处理随机值
|
|
225
|
+
const processedValue = generateRandomValue(value);
|
|
226
|
+
|
|
227
|
+
return `${fullUrl} headerReplace://req.${key}:/(.*)/=${processedValue}`;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (headerRuleLines.length > 0) {
|
|
231
|
+
// 将所有规则合并为一个多行规则字符串
|
|
232
|
+
rule = `${rule}\n${headerRuleLines.join('\n')}`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return rule;
|
|
237
|
+
};
|
|
238
|
+
|
|
195
239
|
// Whistle 规则服务器实现
|
|
196
240
|
module.exports = (server, options) => {
|
|
197
241
|
server.on('request', (req, res) => {
|
|
@@ -337,12 +381,15 @@ module.exports = (server, options) => {
|
|
|
337
381
|
// 执行重定向:将fullUrl中匹配的pattern替换为targetUrl
|
|
338
382
|
// 对于redirect类型,我们使用indexOf确认前缀匹配
|
|
339
383
|
if (fullUrl.indexOf(pattern) === 0) {
|
|
340
|
-
//
|
|
341
|
-
const
|
|
384
|
+
// 获取自定义请求头
|
|
385
|
+
const customHeaders = matchedInterface.customHeaders || {};
|
|
386
|
+
|
|
387
|
+
// 构建重定向规则,包括自定义请求头
|
|
388
|
+
const redirectRule = buildRedirectRule(fullUrl, targetUrl, customHeaders, pattern, 0);
|
|
342
389
|
|
|
343
|
-
//
|
|
344
|
-
res.end(
|
|
345
|
-
log(`重定向规则: ${
|
|
390
|
+
// 返回规则
|
|
391
|
+
res.end(redirectRule);
|
|
392
|
+
log(`重定向规则: ${redirectRule}`);
|
|
346
393
|
return;
|
|
347
394
|
} else {
|
|
348
395
|
log(`URL ${fullUrl} 不以 ${pattern} 开头,跳过处理`);
|
|
@@ -365,9 +412,15 @@ module.exports = (server, options) => {
|
|
|
365
412
|
|
|
366
413
|
// 对于url_redirect,我们需要完全匹配
|
|
367
414
|
if (fullUrl === matchedInterface.urlPattern) {
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
415
|
+
// 获取自定义请求头
|
|
416
|
+
const customHeaders = matchedInterface.customHeaders || {};
|
|
417
|
+
|
|
418
|
+
// 构建重定向规则,包括自定义请求头
|
|
419
|
+
const redirectRule = buildRedirectRule(fullUrl, targetUrl, customHeaders, pattern, 1);
|
|
420
|
+
|
|
421
|
+
// 返回规则
|
|
422
|
+
res.end(redirectRule);
|
|
423
|
+
log(`URL重定向规则: ${redirectRule}`);
|
|
371
424
|
return;
|
|
372
425
|
} else {
|
|
373
426
|
log(`URL ${fullUrl} 不完全匹配 ${matchedInterface.urlPattern},跳过处理`);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { useParams, useHistory } from 'react-router-dom';
|
|
3
3
|
import AppLayout from '../components/AppLayout';
|
|
4
|
-
import { Table, Button, Modal, Form, Input, Select, message, Switch, Popconfirm, Alert, Space, Tabs } from 'antd';
|
|
5
|
-
import { PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, PlayCircleOutlined, FormatPainterOutlined, EyeOutlined } from '@ant-design/icons';
|
|
4
|
+
import { Table, Button, Modal, Form, Input, Select, message, Switch, Popconfirm, Alert, Space, Tabs, Card, Divider, Badge, Tooltip, Row, Col } from 'antd';
|
|
5
|
+
import { PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, PlayCircleOutlined, FormatPainterOutlined, EyeOutlined, CopyOutlined, CodeOutlined, CheckCircleOutlined, CloseCircleOutlined, MenuOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
|
|
6
6
|
import '../styles/interface-management.css';
|
|
7
7
|
import axios from 'axios';
|
|
8
8
|
|
|
@@ -137,6 +137,16 @@ const InterfaceManagement = () => {
|
|
|
137
137
|
|
|
138
138
|
const handleEditInterface = (record) => {
|
|
139
139
|
setEditingInterface(record);
|
|
140
|
+
|
|
141
|
+
// 将自定义请求头转换为数组格式,用于动态表单项
|
|
142
|
+
let headersArray = [];
|
|
143
|
+
if (record.customHeaders && typeof record.customHeaders === 'object') {
|
|
144
|
+
headersArray = Object.entries(record.customHeaders).map(([key, value]) => ({
|
|
145
|
+
headerName: key,
|
|
146
|
+
headerValue: value
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
form.setFieldsValue({
|
|
141
151
|
name: record.name,
|
|
142
152
|
pattern: record.urlPattern,
|
|
@@ -146,6 +156,7 @@ const InterfaceManagement = () => {
|
|
|
146
156
|
responseBody: record.responseContent || '',
|
|
147
157
|
httpMethod: record.httpMethod || 'ALL',
|
|
148
158
|
targetUrl: record.targetUrl || '',
|
|
159
|
+
headerItems: headersArray // 使用数组存储表单项
|
|
149
160
|
});
|
|
150
161
|
setModalVisible(true);
|
|
151
162
|
};
|
|
@@ -207,29 +218,27 @@ const InterfaceManagement = () => {
|
|
|
207
218
|
try {
|
|
208
219
|
const values = await form.validateFields();
|
|
209
220
|
|
|
210
|
-
//
|
|
211
|
-
if (values.proxyType === 'response' && values.contentType.includes('json')) {
|
|
221
|
+
// 检查responseBody是否是有效的JSON
|
|
222
|
+
if (values.proxyType === 'response' && values.contentType.includes('application/json')) {
|
|
212
223
|
try {
|
|
213
|
-
|
|
214
|
-
const cleanedJson = cleanJsonResponse(values.responseBody);
|
|
215
|
-
JSON.parse(cleanedJson);
|
|
216
|
-
values.responseBody = cleanedJson;
|
|
224
|
+
JSON.parse(values.responseBody);
|
|
217
225
|
} catch (e) {
|
|
218
|
-
message.error('JSON
|
|
226
|
+
message.error('响应体不是有效的JSON格式');
|
|
219
227
|
return;
|
|
220
228
|
}
|
|
221
229
|
}
|
|
222
230
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
message.error('重定向目标URL不能为空');
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
231
|
+
// 处理自定义请求头,将表单项数组转换为对象格式
|
|
232
|
+
let customHeaders = {};
|
|
228
233
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
if (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') {
|
|
235
|
+
if (values.headerItems && values.headerItems.length > 0) {
|
|
236
|
+
values.headerItems.forEach(item => {
|
|
237
|
+
if (item && item.headerName && item.headerName.trim()) {
|
|
238
|
+
customHeaders[item.headerName.trim()] = item.headerValue || '';
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
233
242
|
}
|
|
234
243
|
|
|
235
244
|
const interfaceData = {
|
|
@@ -239,6 +248,7 @@ const InterfaceManagement = () => {
|
|
|
239
248
|
proxyType: values.proxyType,
|
|
240
249
|
responseContent: values.proxyType === 'response' ? values.responseBody : '',
|
|
241
250
|
targetUrl: (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') ? values.targetUrl : '',
|
|
251
|
+
customHeaders: (values.proxyType === 'redirect' || values.proxyType === 'url_redirect') ? customHeaders : {},
|
|
242
252
|
httpStatus: parseInt(values.statusCode, 10), // 转换为数字
|
|
243
253
|
contentType: values.contentType,
|
|
244
254
|
responseDelay: 0,
|
|
@@ -516,6 +526,47 @@ const InterfaceManagement = () => {
|
|
|
516
526
|
return (record.proxyType === 'redirect' || record.proxyType === 'url_redirect') ? text : '-';
|
|
517
527
|
}
|
|
518
528
|
},
|
|
529
|
+
{
|
|
530
|
+
title: '自定义头',
|
|
531
|
+
dataIndex: 'customHeaders',
|
|
532
|
+
key: 'customHeaders',
|
|
533
|
+
width: 100,
|
|
534
|
+
render: (_, record) => {
|
|
535
|
+
if (record.proxyType !== 'redirect' && record.proxyType !== 'url_redirect') {
|
|
536
|
+
return '-';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const headers = record.customHeaders || {};
|
|
540
|
+
const count = Object.keys(headers).length;
|
|
541
|
+
|
|
542
|
+
if (count === 0) {
|
|
543
|
+
return '-';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 检查是否包含随机值
|
|
547
|
+
const hasRandomValue = Object.values(headers).some(v => v && v.startsWith('@'));
|
|
548
|
+
|
|
549
|
+
return (
|
|
550
|
+
<Tooltip title={
|
|
551
|
+
<div>
|
|
552
|
+
{Object.entries(headers).map(([key, value]) => (
|
|
553
|
+
<div key={key}>
|
|
554
|
+
{key}: {value}
|
|
555
|
+
{value && value.startsWith('@') && (
|
|
556
|
+
<span style={{ color: '#52c41a' }}> (随机)</span>
|
|
557
|
+
)}
|
|
558
|
+
</div>
|
|
559
|
+
))}
|
|
560
|
+
</div>
|
|
561
|
+
}>
|
|
562
|
+
<span style={{ color: '#1890ff' }}>
|
|
563
|
+
{count}个
|
|
564
|
+
{hasRandomValue && <span style={{ marginLeft: 4 }}>🎲</span>}
|
|
565
|
+
</span>
|
|
566
|
+
</Tooltip>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
},
|
|
519
570
|
{
|
|
520
571
|
title: '请求方法',
|
|
521
572
|
dataIndex: 'httpMethod',
|
|
@@ -640,7 +691,8 @@ const InterfaceManagement = () => {
|
|
|
640
691
|
contentType: 'application/json; charset=utf-8',
|
|
641
692
|
responseBody: '{\n "code": 0,\n "message": "success",\n "data": {}\n}',
|
|
642
693
|
httpMethod: 'ALL',
|
|
643
|
-
targetUrl: ''
|
|
694
|
+
targetUrl: '',
|
|
695
|
+
headerItems: []
|
|
644
696
|
}}
|
|
645
697
|
>
|
|
646
698
|
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px' }}>
|
|
@@ -866,51 +918,183 @@ const InterfaceManagement = () => {
|
|
|
866
918
|
|
|
867
919
|
if (proxyType === 'redirect' || proxyType === 'url_redirect') {
|
|
868
920
|
return (
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
{
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
921
|
+
<>
|
|
922
|
+
<Form.Item
|
|
923
|
+
name="targetUrl"
|
|
924
|
+
label="重定向目标URL"
|
|
925
|
+
rules={[
|
|
926
|
+
{ required: true, message: '请输入重定向目标URL' },
|
|
927
|
+
{
|
|
928
|
+
validator: (_, value) => {
|
|
929
|
+
if (!value) return Promise.resolve();
|
|
930
|
+
|
|
931
|
+
try {
|
|
932
|
+
new URL(value);
|
|
933
|
+
return Promise.resolve();
|
|
934
|
+
} catch (e) {
|
|
935
|
+
return Promise.reject(new Error('请输入有效的URL,必须包含http://或https://'));
|
|
936
|
+
}
|
|
883
937
|
}
|
|
884
938
|
}
|
|
939
|
+
]}
|
|
940
|
+
tooltip={proxyType === 'redirect' ?
|
|
941
|
+
"重定向模式:输入完整的目标URL。匹配时使用前缀匹配,只要请求URL以匹配规则开头即命中。例如:https://example.com/api" :
|
|
942
|
+
"URL重定向模式:输入完整的目标URL。匹配时要求完全匹配URL,必须与匹配规则完全一致才命中。例如:https://example.com/api/users"
|
|
885
943
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
944
|
+
>
|
|
945
|
+
<Input
|
|
946
|
+
placeholder={proxyType === 'redirect' ?
|
|
947
|
+
"例如:https://example.com/api" :
|
|
948
|
+
"例如:https://example.com/api/users"
|
|
949
|
+
}
|
|
950
|
+
addonBefore={
|
|
951
|
+
<Select
|
|
952
|
+
defaultValue="https://"
|
|
953
|
+
className="select-before"
|
|
954
|
+
style={{ width: 100 }}
|
|
955
|
+
onChange={(value) => {
|
|
956
|
+
const currentUrl = form.getFieldValue('targetUrl') || '';
|
|
957
|
+
const urlWithoutProtocol = currentUrl.replace(/^https?:\/\//, '');
|
|
958
|
+
form.setFieldsValue({ targetUrl: value + urlWithoutProtocol });
|
|
959
|
+
}}
|
|
960
|
+
>
|
|
961
|
+
<Option value="http://">http://</Option>
|
|
962
|
+
<Option value="https://">https://</Option>
|
|
963
|
+
</Select>
|
|
964
|
+
}
|
|
965
|
+
/>
|
|
966
|
+
</Form.Item>
|
|
967
|
+
|
|
968
|
+
<Form.Item
|
|
969
|
+
label="自定义请求头"
|
|
970
|
+
tooltip="为请求添加或替换HTTP请求头"
|
|
971
|
+
className="custom-headers-container"
|
|
972
|
+
>
|
|
973
|
+
<Form.List name="headerItems">
|
|
974
|
+
{(fields, { add, remove }) => (
|
|
975
|
+
<>
|
|
976
|
+
{fields.map(({ key, name, ...restField }) => (
|
|
977
|
+
<Row key={key} gutter={8} style={{ marginBottom: 8 }}>
|
|
978
|
+
<Col span={10}>
|
|
979
|
+
<Form.Item
|
|
980
|
+
{...restField}
|
|
981
|
+
name={[name, 'headerName']}
|
|
982
|
+
noStyle
|
|
983
|
+
rules={[
|
|
984
|
+
{
|
|
985
|
+
required: true,
|
|
986
|
+
message: '请输入请求头名称'
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
pattern: /^[^:]+$/,
|
|
990
|
+
message: '请求头名称不能包含冒号'
|
|
991
|
+
}
|
|
992
|
+
]}
|
|
993
|
+
>
|
|
994
|
+
<Input placeholder="Header-Name" />
|
|
995
|
+
</Form.Item>
|
|
996
|
+
</Col>
|
|
997
|
+
<Col span={12}>
|
|
998
|
+
<Form.Item
|
|
999
|
+
{...restField}
|
|
1000
|
+
name={[name, 'headerValue']}
|
|
1001
|
+
noStyle
|
|
1002
|
+
rules={[
|
|
1003
|
+
{
|
|
1004
|
+
validator: (_, value) => {
|
|
1005
|
+
if (!value) return Promise.resolve();
|
|
1006
|
+
|
|
1007
|
+
// 检查是否是随机数格式 @xxxx-xxx
|
|
1008
|
+
if (value.startsWith('@')) {
|
|
1009
|
+
const randomPattern = value.substring(1);
|
|
1010
|
+
// 验证格式: 只能包含x和-,且首尾不能是-
|
|
1011
|
+
if (!/^[x][-x]*[x]$/.test(randomPattern) ||
|
|
1012
|
+
randomPattern.startsWith('-') ||
|
|
1013
|
+
randomPattern.endsWith('-')) {
|
|
1014
|
+
return Promise.reject(
|
|
1015
|
+
new Error('随机数格式错误,应为@xxxx-xxx格式,只能包含x和-,且首尾不能是-')
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return Promise.resolve();
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
]}
|
|
1023
|
+
>
|
|
1024
|
+
<Input placeholder="Header-Value" />
|
|
1025
|
+
</Form.Item>
|
|
1026
|
+
</Col>
|
|
1027
|
+
<Col span={2}>
|
|
1028
|
+
<Button
|
|
1029
|
+
type="text"
|
|
1030
|
+
icon={<MinusCircleOutlined />}
|
|
1031
|
+
onClick={() => remove(name)}
|
|
1032
|
+
style={{ color: '#ff4d4f' }}
|
|
1033
|
+
/>
|
|
1034
|
+
</Col>
|
|
1035
|
+
</Row>
|
|
1036
|
+
))}
|
|
1037
|
+
|
|
1038
|
+
<Form.Item>
|
|
1039
|
+
<Button
|
|
1040
|
+
type="dashed"
|
|
1041
|
+
onClick={() => add()}
|
|
1042
|
+
block
|
|
1043
|
+
icon={<PlusOutlined />}
|
|
1044
|
+
>
|
|
1045
|
+
添加请求头
|
|
1046
|
+
</Button>
|
|
1047
|
+
</Form.Item>
|
|
1048
|
+
|
|
1049
|
+
<div style={{ marginTop: 8 }}>
|
|
1050
|
+
<Button
|
|
1051
|
+
type="link"
|
|
1052
|
+
onClick={() => {
|
|
1053
|
+
add({ headerName: 'Content-Type', headerValue: 'application/json' });
|
|
1054
|
+
}}
|
|
1055
|
+
>
|
|
1056
|
+
添加 Content-Type
|
|
1057
|
+
</Button>
|
|
1058
|
+
<Button
|
|
1059
|
+
type="link"
|
|
1060
|
+
onClick={() => {
|
|
1061
|
+
add({ headerName: 'Authorization', headerValue: 'Bearer ' });
|
|
1062
|
+
}}
|
|
1063
|
+
>
|
|
1064
|
+
添加 Authorization
|
|
1065
|
+
</Button>
|
|
1066
|
+
<Button
|
|
1067
|
+
type="link"
|
|
1068
|
+
onClick={() => {
|
|
1069
|
+
add({ headerName: 'User-Agent', headerValue: 'Mozilla/5.0' });
|
|
1070
|
+
}}
|
|
1071
|
+
>
|
|
1072
|
+
添加 User-Agent
|
|
1073
|
+
</Button>
|
|
1074
|
+
<Button
|
|
1075
|
+
type="link"
|
|
1076
|
+
onClick={() => {
|
|
1077
|
+
add({ headerName: 'X-Random-ID', headerValue: '@xxxx-xxxx' });
|
|
1078
|
+
}}
|
|
1079
|
+
>
|
|
1080
|
+
添加随机ID
|
|
1081
|
+
</Button>
|
|
1082
|
+
</div>
|
|
1083
|
+
|
|
1084
|
+
<div style={{ marginTop: 8, background: '#f5f5f5', padding: '8px', borderRadius: '4px' }}>
|
|
1085
|
+
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>随机数格式说明:</div>
|
|
1086
|
+
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
|
1087
|
+
<li>使用 <code>@</code> 开头表示这是一个随机数值</li>
|
|
1088
|
+
<li>格式示例:<code>@xxxx-xxxx</code> 将生成如 <code>a1b2-c3d4</code> 的随机值</li>
|
|
1089
|
+
<li>每个 <code>x</code> 将替换为随机字母或数字</li>
|
|
1090
|
+
<li><code>-</code> 将保留在输出中作为分隔符</li>
|
|
1091
|
+
</ul>
|
|
1092
|
+
</div>
|
|
1093
|
+
</>
|
|
1094
|
+
)}
|
|
1095
|
+
</Form.List>
|
|
1096
|
+
</Form.Item>
|
|
1097
|
+
</>
|
|
914
1098
|
);
|
|
915
1099
|
}
|
|
916
1100
|
|
|
@@ -1046,6 +1230,34 @@ const InterfaceManagement = () => {
|
|
|
1046
1230
|
</div>
|
|
1047
1231
|
)}
|
|
1048
1232
|
|
|
1233
|
+
{(editingInterface.proxyType === 'redirect' || editingInterface.proxyType === 'url_redirect') &&
|
|
1234
|
+
testResult.formattedHeaders && (
|
|
1235
|
+
<div className="result-item">
|
|
1236
|
+
<span className="label">自定义请求头:</span>
|
|
1237
|
+
<div className="value" style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>
|
|
1238
|
+
{
|
|
1239
|
+
editingInterface.customHeaders &&
|
|
1240
|
+
Object.entries(editingInterface.customHeaders)
|
|
1241
|
+
.map(([key, value]) => {
|
|
1242
|
+
const generatedValue = testResult.customHeaders[key];
|
|
1243
|
+
return (
|
|
1244
|
+
<div key={key}>
|
|
1245
|
+
{key}: {value.startsWith('@') ? (
|
|
1246
|
+
<span>
|
|
1247
|
+
<span style={{ color: '#d9d9d9' }}>{value}</span>
|
|
1248
|
+
{' '} → {' '}
|
|
1249
|
+
<span style={{ color: '#52c41a', fontWeight: 'bold' }}>{generatedValue}</span>
|
|
1250
|
+
<span style={{ color: '#8c8c8c', fontSize: '12px' }}> (随机生成)</span>
|
|
1251
|
+
</span>
|
|
1252
|
+
) : generatedValue}
|
|
1253
|
+
</div>
|
|
1254
|
+
);
|
|
1255
|
+
})
|
|
1256
|
+
}
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
)}
|
|
1260
|
+
|
|
1049
1261
|
{editingInterface.proxyType === 'response' && (
|
|
1050
1262
|
<>
|
|
1051
1263
|
<div className="result-item">
|