mirror of
https://github.com/Zhuym07/Tsumugiboshi.git
synced 2026-02-04 07:37:47 -05:00
Compare commits
11 Commits
6e57643f6e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8a7da1959 | ||
|
|
7a28bdb453 | ||
|
|
efc0c7cb50 | ||
|
|
f923ed0695 | ||
|
|
9ca6cde4b0 | ||
|
|
fc7c93de91 | ||
|
|
c7cb992208 | ||
|
|
bfbab6be5d | ||
|
|
7a596c7850 | ||
|
|
8738f1992e | ||
|
|
339383f852 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
# 忽略UserDataDir文件夹
|
# 忽略UserDataDir文件夹
|
||||||
UserDataDir
|
UserDataDir
|
||||||
.vscode/settings.json
|
.vscode
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"mapstock": {
|
"mapstock": {
|
||||||
"tab": {
|
"tab": {
|
||||||
"index": 2,
|
"index": 2,
|
||||||
"label": "🗺️存入Stock",
|
"label": "⏲️存入Stock",
|
||||||
"description": "保存 99km Stocks 到账户"
|
"description": "保存 99km Stocks 到账户"
|
||||||
},
|
},
|
||||||
"endpoint": "/mapstock",
|
"endpoint": "/mapstock",
|
||||||
@@ -55,11 +55,48 @@
|
|||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"confirmBeforeSubmit": true
|
"confirmBeforeSubmit": true,
|
||||||
|
"confirmCount": 2,
|
||||||
|
"confirmMessages": [
|
||||||
|
"警告(1/2)\n\n这将修改ID={music.musicId}账号内的数据",
|
||||||
|
"警告(2/2)\n\n您是否确认修改这些数据?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"tab": {
|
||||||
|
"index": 3,
|
||||||
|
"label": "🏃➡️一键跑图",
|
||||||
|
"description": "## 一键完成区域\n\n可以领取的地图列表:\n-\n\n输入地图ID和用户ID即可一键领取该区域所有收藏品。"
|
||||||
|
},
|
||||||
|
"endpoint": "/map",
|
||||||
|
"method": "GET",
|
||||||
|
"queryParams": ["userid", "mapid"],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"id": "userid",
|
||||||
|
"label": "🪪UserID",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mapid",
|
||||||
|
"label": "🗺️地图ID",
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"placeholder": "10001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"confirmBeforeSubmit": true,
|
||||||
|
"confirmCount": 3,
|
||||||
|
"confirmMessages": [
|
||||||
|
"警告(1/3)\n\n这将永久修改ID={music.musicId}账号内的数据",
|
||||||
|
"警告(2/3)\n\n即将修改的数据:\n地图ID: {mapid}\n\n原始请求:\n{rawData}",
|
||||||
|
"警告(3/3)\n\n您是否确认修改这些数据?"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"unlock": {
|
"unlock": {
|
||||||
"tab": {
|
"tab": {
|
||||||
"index": 3,
|
"index": 4,
|
||||||
"label": "🔓解锁紫铺",
|
"label": "🔓解锁紫铺",
|
||||||
"description": "解锁所有 DX Master 谱面"
|
"description": "解锁所有 DX Master 谱面"
|
||||||
},
|
},
|
||||||
@@ -74,11 +111,35 @@
|
|||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"confirmBeforeSubmit": true,
|
||||||
|
"confirmCount": 2,
|
||||||
|
"confirmMessages": [
|
||||||
|
"警告(1/2)\n\n这将永久修改ID={music.musicId}账号内的数据",
|
||||||
|
"警告(2/2)\n\n您是否确认修改这些数据?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bonus": {
|
||||||
|
"tab": {
|
||||||
|
"index": 5,
|
||||||
|
"label": "☑️一键印章",
|
||||||
|
"description": "为所有现有收藏品的集章卡各盖至9/10个章"
|
||||||
|
},
|
||||||
|
"endpoint": "/bonus",
|
||||||
|
"method": "GET",
|
||||||
|
"queryParams": ["userid"],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"id": "userid",
|
||||||
|
"label": "🪪UserID",
|
||||||
|
"type": "text",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"confirmBeforeSubmit": true
|
"confirmBeforeSubmit": true
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"tab": {
|
"tab": {
|
||||||
"index": 4,
|
"index": 6,
|
||||||
"label": "✏️修改成绩",
|
"label": "✏️修改成绩",
|
||||||
"description": "⚠警告\n这将[覆写]账户中的成绩数据"
|
"description": "⚠警告\n这将[覆写]账户中的成绩数据"
|
||||||
},
|
},
|
||||||
@@ -160,7 +221,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"confirmBeforeSubmit": true,
|
"confirmBeforeSubmit": true,
|
||||||
"confirmTemplate": "即将发送的数据:\nUserID: {userId}\n歌曲ID: {music.musicId}\nachievement: {music.achievement}\n难度: {music.level}\nFC状态: {music.comboStatus}\nsyncStatus: {music.syncStatus}\nFS状态:{music.syncStatus}\n\n原始请求:\n{rawData}"
|
"confirmCount": 3,
|
||||||
|
"confirmMessages": [
|
||||||
|
"警告(1/3)\n\n这将永久修改ID={music.musicId}账号内的数据",
|
||||||
|
"警告(2/3)\n\n即将修改的数据:\nUserID: {userId}\n歌曲ID: {music.musicId}\n分数: {music.achievement}\n难度: {music.level}\nFC状态: {music.comboStatus}\nFS状态:{music.syncStatus}\n\n原始请求:\n{rawData}",
|
||||||
|
"警告(3/3)\n\n您是否确认修改这些数据?"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
image/og.png
Normal file
BIN
image/og.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
242
index.html
242
index.html
@@ -13,7 +13,9 @@
|
|||||||
<!-- OpenGraph 标签 -->
|
<!-- OpenGraph 标签 -->
|
||||||
<meta property="og:title" content="TsumugiBoshi|纺星">
|
<meta property="og:title" content="TsumugiBoshi|纺星">
|
||||||
<meta property="og:description" content="神秘API调试工具">
|
<meta property="og:description" content="神秘API调试工具">
|
||||||
<meta property="og:image" content="image\og-image.png">
|
<meta property="og:image" content="https://s2.loli.net/2025/01/29/1KPvmysVrAdLW3C.png" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:image" content="https://s2.loli.net/2025/01/29/1KPvmysVrAdLW3C.png" />
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
|
|
||||||
<title>TsumugiBoshi|纺星</title>
|
<title>TsumugiBoshi|纺星</title>
|
||||||
@@ -334,14 +336,22 @@
|
|||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (formConfig.confirmBeforeSubmit) {
|
if (formConfig.confirmBeforeSubmit) {
|
||||||
let confirmMessage = formConfig.confirmTemplate || "确认提交数据?";
|
const confirmCount = formConfig.confirmCount || 1;
|
||||||
if (formConfig.confirmTemplate) {
|
const messages = formConfig.confirmMessages || Array(confirmCount).fill("确认提交数据?");
|
||||||
|
|
||||||
|
for (let i = 0; i < confirmCount; i++) {
|
||||||
|
let confirmMessage = messages[i] || "确认提交数据?";
|
||||||
|
if (confirmMessage) {
|
||||||
confirmMessage = confirmMessage.replace(/\{([^}]+)\}/g, (_, key) => {
|
confirmMessage = confirmMessage.replace(/\{([^}]+)\}/g, (_, key) => {
|
||||||
return key === 'rawData' ? JSON.stringify(formData, null, 2) :
|
return key === 'rawData' ? JSON.stringify(formData, null, 2) :
|
||||||
key.split('.').reduce((obj, k) => obj && obj[k], formData) || '';
|
key.split('.').reduce((obj, k) => obj && obj[k], formData) || '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!confirm(confirmMessage)) return;
|
|
||||||
|
if (!confirm(confirmMessage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onSubmit(formData);
|
onSubmit(formData);
|
||||||
};
|
};
|
||||||
@@ -395,6 +405,27 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 在 App 组件外部添加地址格式化函数
|
||||||
|
const formatApiUrl = (url) => {
|
||||||
|
if (!url) return '';
|
||||||
|
|
||||||
|
// 移除末尾的斜杠
|
||||||
|
url = url.replace(/\/+$/, '');
|
||||||
|
|
||||||
|
// 如果不包含协议,添加 http://
|
||||||
|
if (!/^https?:\/\//i.test(url)) {
|
||||||
|
url = 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用 URL API 解析和规范化地址
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
return urlObj.toString().replace(/\/+$/, '');
|
||||||
|
} catch (e) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [tabValue, setTabValue] = React.useState(0);
|
const [tabValue, setTabValue] = React.useState(0);
|
||||||
const [qrInput, setQrInput] = React.useState('');
|
const [qrInput, setQrInput] = React.useState('');
|
||||||
@@ -421,7 +452,7 @@
|
|||||||
...JSON.parse(localStorage.getItem('customBackends') || '[]')
|
...JSON.parse(localStorage.getItem('customBackends') || '[]')
|
||||||
]);
|
]);
|
||||||
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
||||||
const [newBackend, setNewBackend] = React.useState({ label: '', value: '' });
|
const [newBackend, setNewBackend] = React.useState({ label: '', value: '', formattedUrl: '' });
|
||||||
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
|
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
|
||||||
const [snackbarMessage, setSnackbarMessage] = React.useState('');
|
const [snackbarMessage, setSnackbarMessage] = React.useState('');
|
||||||
const [menuAnchorEl, setMenuAnchorEl] = React.useState(null);
|
const [menuAnchorEl, setMenuAnchorEl] = React.useState(null);
|
||||||
@@ -431,6 +462,98 @@
|
|||||||
const [isLogExpanded, setIsLogExpanded] = React.useState(false);
|
const [isLogExpanded, setIsLogExpanded] = React.useState(false);
|
||||||
const formRefs = React.useRef({});
|
const formRefs = React.useRef({});
|
||||||
|
|
||||||
|
const handleApiCall = async (endpoint, method = "GET", formData = null, config) => {
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
credentials: "include"
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = `${apiBase}${endpoint}`;
|
||||||
|
|
||||||
|
if (method === "GET" && config?.queryParams) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
config.queryParams.forEach(param => {
|
||||||
|
if (formData?.[param]) params.append(param, formData[param]);
|
||||||
|
});
|
||||||
|
url += `?${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === "POST" && config?.requestFormat === "json") {
|
||||||
|
options.body = JSON.stringify(formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(url, options);
|
||||||
|
const rawText = await res.text();
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
try {
|
||||||
|
responseData = JSON.parse(rawText);
|
||||||
|
} catch {
|
||||||
|
responseData = {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
rawResponse: rawText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将状态码添加到显示结果中
|
||||||
|
const displayResponse = {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
...responseData
|
||||||
|
};
|
||||||
|
|
||||||
|
setResponse(JSON.stringify(displayResponse, null, 2));
|
||||||
|
|
||||||
|
// 保存日志
|
||||||
|
saveLog(endpoint, {
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
...formData,
|
||||||
|
status: res.status
|
||||||
|
}, displayResponse);
|
||||||
|
|
||||||
|
// 特殊处理登录成功的情况
|
||||||
|
if (config?.endpoint === "/qr" && responseData.userId) {
|
||||||
|
setUserId(responseData.userId.toString());
|
||||||
|
setCookie("userId", responseData.userId.toString(), 7);
|
||||||
|
|
||||||
|
Object.entries(formConfigs).forEach(([key, formConfig]) => {
|
||||||
|
const userIdField = formConfig.fields.find(
|
||||||
|
field => field.id.toLowerCase().includes("userid")
|
||||||
|
);
|
||||||
|
if (userIdField && formRefs.current[key]) {
|
||||||
|
formRefs.current[key].updateField(userIdField.id, responseData.userId.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消息提示
|
||||||
|
setSnackbarMessage(responseData.info || `请求完成 (${res.status} ${res.statusText})`);
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
const errorResponse = {
|
||||||
|
error: true,
|
||||||
|
message: error.message,
|
||||||
|
type: error.name
|
||||||
|
};
|
||||||
|
|
||||||
|
setResponse(JSON.stringify(errorResponse, null, 2));
|
||||||
|
setSnackbarMessage(`请求发生错误: ${error.message}`);
|
||||||
|
setSnackbarOpen(true);
|
||||||
|
|
||||||
|
// 保存错误日志
|
||||||
|
saveLog(endpoint, {
|
||||||
|
method,
|
||||||
|
url: `${apiBase}${endpoint}`,
|
||||||
|
...formData
|
||||||
|
}, errorResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Cookie操作函数
|
// Cookie操作函数
|
||||||
function setCookie(name, value, days) {
|
function setCookie(name, value, days) {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
@@ -462,65 +585,6 @@
|
|||||||
localStorage.setItem('apiLogs', JSON.stringify(updatedLogs));
|
localStorage.setItem('apiLogs', JSON.stringify(updatedLogs));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApiCall = async (endpoint, method = 'GET', formData = null, config) => {
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
method,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
};
|
|
||||||
|
|
||||||
let url = `${apiBase}${endpoint}`;
|
|
||||||
|
|
||||||
// 处理GET请求参数
|
|
||||||
if (method === 'GET' && config.queryParams) {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
config.queryParams.forEach(param => {
|
|
||||||
if (formData[param]) params.append(param, formData[param]);
|
|
||||||
});
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理POST请求体
|
|
||||||
if (method === 'POST') {
|
|
||||||
if (config.requestFormat === 'json') {
|
|
||||||
options.body = JSON.stringify(formData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(url, options);
|
|
||||||
const data = await res.json();
|
|
||||||
setResponse(JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
// 保存日志
|
|
||||||
saveLog(endpoint, formData, data);
|
|
||||||
|
|
||||||
// 处理登录响应
|
|
||||||
if (config.endpoint === '/qr' && data.userId) {
|
|
||||||
setUserId(data.userId.toString());
|
|
||||||
setCookie('userId', data.userId.toString(), 7);
|
|
||||||
|
|
||||||
// 遍历所有表单配置,更新包含userid/userId字段的表单
|
|
||||||
Object.entries(formConfigs).forEach(([key, formConfig]) => {
|
|
||||||
const userIdField = formConfig.fields.find(
|
|
||||||
field => field.id.toLowerCase().includes('userid')
|
|
||||||
);
|
|
||||||
if (userIdField && formRefs.current[key]) {
|
|
||||||
formRefs.current[key].updateField(userIdField.id, data.userId.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据状态设置提示消息
|
|
||||||
setSnackbarMessage(data.info || '操作成功');
|
|
||||||
setSnackbarOpen(true);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
setResponse(`Error: ${error.message}`);
|
|
||||||
setSnackbarMessage(`错误: ${error.message}`);
|
|
||||||
setSnackbarOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMusicChange = (e) => {
|
const handleMusicChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setMusicData(prev => ({
|
setMusicData(prev => ({
|
||||||
@@ -544,7 +608,7 @@
|
|||||||
|
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
setNewBackend({ label: '', value: '' });
|
setNewBackend({ label: '', value: '', formattedUrl: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveBackend = () => {
|
const handleSaveBackend = () => {
|
||||||
@@ -600,6 +664,12 @@
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNewBackendChange = (e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const formattedUrl = formatApiUrl(value);
|
||||||
|
setNewBackend(prev => ({...prev, value: value, formattedUrl: formattedUrl}));
|
||||||
|
};
|
||||||
|
|
||||||
// 加载表单配置
|
// 加载表单配置
|
||||||
const [formConfigs, setFormConfigs] = React.useState(null);
|
const [formConfigs, setFormConfigs] = React.useState(null);
|
||||||
|
|
||||||
@@ -705,7 +775,7 @@
|
|||||||
{/* 移动后端选择器到底部 */}
|
{/* 移动后端选择器到底部 */}
|
||||||
<Paper elevation={3} className="backend-selector">
|
<Paper elevation={3} className="backend-selector">
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
后端设置
|
⚙️ 后端设置
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>选择后端地址</InputLabel>
|
<InputLabel>选择后端地址</InputLabel>
|
||||||
@@ -749,6 +819,7 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -757,6 +828,20 @@
|
|||||||
>
|
>
|
||||||
添加自定义后端
|
添加自定义后端
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{/* 新增认证提示区域 */}
|
||||||
|
<Box display="flex" justifyContent="space-between" alignItems="center" mt={2}>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
⚠️部分后端地址受认证保护 使用前可能需要身份认证
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => window.open(apiBase, '_blank')}
|
||||||
|
>
|
||||||
|
认证
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -812,12 +897,33 @@
|
|||||||
label="后端地址"
|
label="后端地址"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={newBackend.value}
|
value={newBackend.value}
|
||||||
onChange={(e) => setNewBackend(prev => ({...prev, value: e.target.value}))}
|
onChange={handleNewBackendChange}
|
||||||
|
placeholder="example.com:2333"
|
||||||
|
helperText={
|
||||||
|
<div style={{fontSize: '0.75rem', color: '#666'}}>
|
||||||
|
支持的格式:<br/>
|
||||||
|
- example.com<br/>
|
||||||
|
- example.com:2333<br/>
|
||||||
|
- http(s)://example.com<br/>
|
||||||
|
{newBackend.value && (
|
||||||
|
<>
|
||||||
|
<br/>
|
||||||
|
将被格式化为: <span style={{color: '#1976d2'}}>{newBackend.formattedUrl}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleDialogClose}>取消</Button>
|
<Button onClick={handleDialogClose}>取消</Button>
|
||||||
<Button onClick={handleSaveBackend} color="primary">保存</Button>
|
<Button
|
||||||
|
onClick={() => handleSaveBackend()}
|
||||||
|
color="primary"
|
||||||
|
disabled={!newBackend.label || !newBackend.value}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user