feat: Add `marked` for improved Markdown to HTML report generation and refined download handling.

This commit is contained in:
lintsinghua 2025-12-13 21:38:11 +08:00
parent a9a22b91c7
commit 3d4f90c547
4 changed files with 267 additions and 149 deletions

View File

@ -2298,103 +2298,110 @@ async def generate_audit_report(
if task.completed_at and task.started_at:
duration = (task.completed_at - task.started_at).total_seconds()
if duration >= 3600:
duration_str = f"{duration / 3600:.1f} hours"
duration_str = f"{duration / 3600:.1f} 小时"
elif duration >= 60:
duration_str = f"{duration / 60:.1f} minutes"
duration_str = f"{duration / 60:.1f} 分钟"
else:
duration_str = f"{int(duration)} seconds"
duration_str = f"{int(duration)} "
md_lines = []
# Header
md_lines.append("# DeepAudit Security Audit Report")
md_lines.append("# DeepAudit 安全审计报告")
md_lines.append("")
md_lines.append("---")
md_lines.append("")
# Report Info
md_lines.append("## Report Information")
md_lines.append("## 报告信息")
md_lines.append("")
md_lines.append(f"| Property | Value |")
md_lines.append(f"| 属性 | 内容 |")
md_lines.append(f"|----------|-------|")
md_lines.append(f"| **Project** | {project.name} |")
md_lines.append(f"| **Task ID** | `{task.id[:8]}...` |")
md_lines.append(f"| **Generated** | {timestamp} |")
md_lines.append(f"| **Status** | {task.status.upper()} |")
md_lines.append(f"| **Duration** | {duration_str} |")
md_lines.append(f"| **项目名称** | {project.name} |")
md_lines.append(f"| **任务 ID** | `{task.id[:8]}...` |")
md_lines.append(f"| **生成时间** | {timestamp} |")
md_lines.append(f"| **任务状态** | {task.status.upper()} |")
md_lines.append(f"| **耗时** | {duration_str} |")
md_lines.append("")
# Executive Summary
md_lines.append("## Executive Summary")
md_lines.append("## 执行摘要")
md_lines.append("")
score = task.security_score
if score is not None:
if score >= 80:
score_assessment = "Good - Minor improvements recommended"
score_icon = "PASS"
score_assessment = "良好 - 建议进行少量优化"
score_icon = "通过"
elif score >= 60:
score_assessment = "Moderate - Several issues require attention"
score_icon = "WARN"
score_assessment = "中等 - 存在若干问题需要关注"
score_icon = "警告"
else:
score_assessment = "Critical - Immediate remediation required"
score_icon = "FAIL"
md_lines.append(f"**Security Score: {int(score)}/100** [{score_icon}]")
score_assessment = "严重 - 需要立即进行修复"
score_icon = "未通过"
md_lines.append(f"**安全评分: {int(score)}/100** [{score_icon}]")
md_lines.append(f"*{score_assessment}*")
else:
md_lines.append("**Security Score:** Not calculated")
md_lines.append("**安全评分:** 未计算")
md_lines.append("")
# Findings Summary
md_lines.append("### Findings Overview")
md_lines.append("### 漏洞发现概览")
md_lines.append("")
md_lines.append(f"| Severity | Count | Verified |")
md_lines.append(f"| 严重程度 | 数量 | 已验证 |")
md_lines.append(f"|----------|-------|----------|")
if critical > 0:
md_lines.append(f"| **CRITICAL** | {critical} | {sum(1 for f in findings if normalize_severity(f.severity) == 'critical' and f.is_verified)} |")
md_lines.append(f"| **严重 (CRITICAL)** | {critical} | {sum(1 for f in findings if normalize_severity(f.severity) == 'critical' and f.is_verified)} |")
if high > 0:
md_lines.append(f"| **HIGH** | {high} | {sum(1 for f in findings if normalize_severity(f.severity) == 'high' and f.is_verified)} |")
md_lines.append(f"| **高危 (HIGH)** | {high} | {sum(1 for f in findings if normalize_severity(f.severity) == 'high' and f.is_verified)} |")
if medium > 0:
md_lines.append(f"| **MEDIUM** | {medium} | {sum(1 for f in findings if normalize_severity(f.severity) == 'medium' and f.is_verified)} |")
md_lines.append(f"| **中危 (MEDIUM)** | {medium} | {sum(1 for f in findings if normalize_severity(f.severity) == 'medium' and f.is_verified)} |")
if low > 0:
md_lines.append(f"| **LOW** | {low} | {sum(1 for f in findings if normalize_severity(f.severity) == 'low' and f.is_verified)} |")
md_lines.append(f"| **Total** | {total} | {verified} |")
md_lines.append(f"| **低危 (LOW)** | {low} | {sum(1 for f in findings if normalize_severity(f.severity) == 'low' and f.is_verified)} |")
md_lines.append(f"| **总计** | {total} | {verified} |")
md_lines.append("")
# Audit Metrics
md_lines.append("### Audit Metrics")
md_lines.append("### 审计指标")
md_lines.append("")
md_lines.append(f"- **Files Analyzed:** {task.analyzed_files} / {task.total_files}")
md_lines.append(f"- **Agent Iterations:** {task.total_iterations}")
md_lines.append(f"- **Tool Invocations:** {task.tool_calls_count}")
md_lines.append(f"- **Tokens Used:** {task.tokens_used:,}")
md_lines.append(f"- **分析文件数:** {task.analyzed_files} / {task.total_files}")
md_lines.append(f"- **Agent 迭代次数:** {task.total_iterations}")
md_lines.append(f"- **工具调用次数:** {task.tool_calls_count}")
md_lines.append(f"- **Token 消耗:** {task.tokens_used:,}")
if with_poc > 0:
md_lines.append(f"- **PoC Generated:** {with_poc}")
md_lines.append(f"- **生成的 PoC:** {with_poc}")
md_lines.append("")
# Detailed Findings
if not findings:
md_lines.append("## Findings")
md_lines.append("## 漏洞详情")
md_lines.append("")
md_lines.append("*No security vulnerabilities were detected during this audit.*")
md_lines.append("*本次审计未发现安全漏洞。*")
md_lines.append("")
else:
# Group findings by severity
for severity_level, severity_name in [('critical', 'Critical'), ('high', 'High'), ('medium', 'Medium'), ('low', 'Low')]:
severity_map = {
'critical': '严重 (Critical)',
'high': '高危 (High)',
'medium': '中危 (Medium)',
'low': '低危 (Low)'
}
for severity_level, severity_name in severity_map.items():
severity_findings = [f for f in findings if normalize_severity(f.severity) == severity_level]
if not severity_findings:
continue
md_lines.append(f"## {severity_name} Severity Findings")
md_lines.append(f"## {severity_name} 漏洞")
md_lines.append("")
for i, f in enumerate(severity_findings, 1):
verified_badge = "[Verified]" if f.is_verified else "[Unverified]"
poc_badge = " [PoC]" if f.has_poc else ""
verified_badge = "[已验证]" if f.is_verified else "[未验证]"
poc_badge = " [PoC]" if f.has_poc else ""
md_lines.append(f"### {severity_level.upper()}-{i}: {f.title}")
md_lines.append("")
md_lines.append(f"**{verified_badge}**{poc_badge} | Type: `{f.vulnerability_type}`")
md_lines.append(f"**{verified_badge}**{poc_badge} | 类型: `{f.vulnerability_type}`")
md_lines.append("")
if f.file_path:
@ -2404,15 +2411,15 @@ async def generate_audit_report(
if f.line_end and f.line_end != f.line_start:
location += f"-{f.line_end}"
location += "`"
md_lines.append(f"**Location:** {location}")
md_lines.append(f"**位置:** {location}")
md_lines.append("")
if f.ai_confidence:
md_lines.append(f"**AI Confidence:** {int(f.ai_confidence * 100)}%")
md_lines.append(f"**AI 置信度:** {int(f.ai_confidence * 100)}%")
md_lines.append("")
if f.description:
md_lines.append("**Description:**")
md_lines.append("**漏洞描述:**")
md_lines.append("")
md_lines.append(f.description)
md_lines.append("")
@ -2429,7 +2436,7 @@ async def generate_audit_report(
'cpp': 'cpp', 'cs': 'csharp', 'sol': 'solidity'
}
lang = lang_map.get(ext, 'text')
md_lines.append("**Vulnerable Code:**")
md_lines.append("**漏洞代码:**")
md_lines.append("")
md_lines.append(f"```{lang}")
md_lines.append(f.code_snippet.strip())
@ -2437,13 +2444,13 @@ async def generate_audit_report(
md_lines.append("")
if f.suggestion:
md_lines.append("**Recommendation:**")
md_lines.append("**修复建议:**")
md_lines.append("")
md_lines.append(f.suggestion)
md_lines.append("")
if f.fix_code:
md_lines.append("**Suggested Fix:**")
md_lines.append("**参考修复代码:**")
md_lines.append("")
md_lines.append(f"```{lang if f.file_path else 'text'}")
md_lines.append(f.fix_code.strip())
@ -2452,7 +2459,7 @@ async def generate_audit_report(
# 🔥 添加 PoC 详情
if f.has_poc:
md_lines.append("**Proof of Concept (PoC):**")
md_lines.append("**概念验证 (PoC):**")
md_lines.append("")
if f.poc_description:
@ -2460,14 +2467,14 @@ async def generate_audit_report(
md_lines.append("")
if f.poc_steps:
md_lines.append("**Reproduction Steps:**")
md_lines.append("**复现步骤:**")
md_lines.append("")
for step_idx, step in enumerate(f.poc_steps, 1):
md_lines.append(f"{step_idx}. {step}")
md_lines.append("")
if f.poc_code:
md_lines.append("**PoC Payload:**")
md_lines.append("**PoC 代码:**")
md_lines.append("")
md_lines.append("```")
md_lines.append(f.poc_code.strip())
@ -2479,24 +2486,29 @@ async def generate_audit_report(
# Remediation Priority
if critical > 0 or high > 0:
md_lines.append("## Remediation Priority")
md_lines.append("## 修复优先级建议")
md_lines.append("")
md_lines.append("Based on the findings, we recommend the following remediation priority:")
md_lines.append("基于已发现的漏洞,我们建议按以下优先级进行修复:")
md_lines.append("")
priority_idx = 1
if critical > 0:
md_lines.append(f"1. **IMMEDIATE:** Address {critical} critical finding(s) - potential for severe impact")
md_lines.append(f"{priority_idx}. **立即修复:** 处理 {critical} 个严重漏洞 - 可能造成严重影响")
priority_idx += 1
if high > 0:
md_lines.append(f"2. **HIGH PRIORITY:** Resolve {high} high severity finding(s) within 1 week")
md_lines.append(f"{priority_idx}. **高优先级:** 在 1 周内修复 {high} 个高危漏洞")
priority_idx += 1
if medium > 0:
md_lines.append(f"3. **MEDIUM PRIORITY:** Fix {medium} medium severity finding(s) within 2-4 weeks")
md_lines.append(f"{priority_idx}. **中优先级:** 在 2-4 周内修复 {medium} 个中危漏洞")
priority_idx += 1
if low > 0:
md_lines.append(f"4. **LOW PRIORITY:** Address {low} low severity finding(s) in regular maintenance")
md_lines.append(f"{priority_idx}. **低优先级:** 在日常维护中处理 {low} 个低危漏洞")
priority_idx += 1
md_lines.append("")
# Footer
md_lines.append("---")
md_lines.append("")
md_lines.append("*This report was generated by DeepAudit - AI-Powered Security Analysis*")
md_lines.append("*本报告由 DeepAudit - AI 驱动的安全分析系统生成*")
md_lines.append("")
content = "\n".join(md_lines)

View File

@ -1,12 +1,12 @@
{
"name": "deep-audit",
"version": "1.3.4",
"version": "2.0.0-beta.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deep-audit",
"version": "1.3.4",
"version": "2.0.0-beta.7",
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@radix-ui/react-accordion": "^1.2.8",
@ -50,6 +50,7 @@
"input-otp": "^1.4.2",
"ky": "^1.9.1",
"lucide-react": "^0.525.0",
"marked": "^17.0.1",
"miaoda-auth-react": "^2.0.0",
"miaoda-sc-plugin": "^1.0.11",
"next-themes": "^0.4.6",
@ -6912,9 +6913,9 @@
}
},
"node_modules/marked": {
"version": "16.4.2",
"resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz",
"integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
"version": "17.0.1",
"resolved": "https://registry.npmmirror.com/marked/-/marked-17.0.1.tgz",
"integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
@ -7258,6 +7259,18 @@
"uuid": "^11.1.0"
}
},
"node_modules/mermaid/node_modules/marked": {
"version": "16.4.2",
"resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz",
"integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/miaoda-auth-react": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/miaoda-auth-react/-/miaoda-auth-react-2.0.6.tgz",
@ -9429,6 +9442,18 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/streamdown/node_modules/marked": {
"version": "16.4.2",
"resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz",
"integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",

View File

@ -57,6 +57,7 @@
"input-otp": "^1.4.2",
"ky": "^1.9.1",
"lucide-react": "^0.525.0",
"marked": "^17.0.1",
"miaoda-auth-react": "^2.0.0",
"miaoda-sc-plugin": "^1.0.11",
"next-themes": "^0.4.6",

View File

@ -5,6 +5,7 @@
*/
import { useState, useEffect, useCallback, memo } from "react";
import { marked } from "marked";
import {
Dialog,
DialogContent,
@ -372,7 +373,7 @@ export const ReportExportDialog = memo(function ReportExportDialog({
responseType: "text",
});
const htmlContent = generateHtmlReport(mdResponse.data, task);
const htmlContent = await generateHtmlReport(mdResponse.data, task);
setPreview({
content: htmlContent,
format: "html",
@ -405,34 +406,29 @@ export const ReportExportDialog = memo(function ReportExportDialog({
}, [task, findings]);
// Generate HTML report from markdown
const generateHtmlReport = (markdown: string, task: AgentTask): string => {
// Convert markdown to HTML with styling
let html = markdown
.replace(/^# (.+)$/gm, '<h1 class="report-h1">$1</h1>')
.replace(/^## (.+)$/gm, '<h2 class="report-h2">$1</h2>')
.replace(/^### (.+)$/gm, '<h3 class="report-h3">$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`([^`]+)`/g, '<code class="report-code">$1</code>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/^---$/gm, '<hr class="report-hr">')
.replace(/\n\n/g, '</p><p class="report-p">');
const generateHtmlReport = async (markdown: string, task: AgentTask): Promise<string> => {
// Parse markdown to HTML using marked
const contentHtml = await marked.parse(markdown);
return `<!DOCTYPE html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Audit Report - ${task.name || task.id}</title>
<title> - ${task.name || task.id}</title>
<style>
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #0d0d12;
--bg-tertiary: #1a1a24;
--text-primary: #ffffff;
--text-secondary: #94a3b8;
--accent: #FF6B2C;
--border: #2d2d3d;
--success: #34d399;
--warning: #fbbf24;
--error: #fb7185;
--code-bg: #1e1e2e;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
@ -444,78 +440,124 @@ export const ReportExportDialog = memo(function ReportExportDialog({
max-width: 900px;
margin: 0 auto;
}
.report-h1 {
/* Typography */
h1, h2, h3, h4, h5, h6 {
color: var(--text-primary);
font-size: 1.75rem;
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
}
.report-h2 {
color: var(--text-primary);
font-size: 1.25rem;
margin: 1.5rem 0 0.75rem;
}
.report-h3 {
color: var(--accent);
font-size: 1rem;
margin: 1rem 0 0.5rem;
}
.report-p { margin: 0.75rem 0; }
.report-code {
background: var(--bg-secondary);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-family: 'SF Mono', Monaco, monospace;
font-size: 0.875em;
}
.report-hr {
border: none;
border-top: 1px solid rgba(255,255,255,0.1);
h1 { font-size: 2rem; border-bottom: 2px solid var(--accent); padding-bottom: 0.5rem; }
h2 { font-size: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
h3 { font-size: 1.25rem; color: var(--text-primary); margin-top: 1.5rem; }
p { margin-bottom: 1rem; }
/* Tables */
table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
}
li {
margin: 0.25rem 0;
margin-left: 1.5rem;
}
pre {
background: var(--bg-secondary);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
font-size: 0.875rem;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border);
}
.header {
text-align: center;
padding: 2rem;
background: var(--bg-secondary);
border-radius: 0.75rem;
margin-bottom: 2rem;
border: 1px solid rgba(255,255,255,0.05);
th, td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
.header h1 {
th {
background: var(--bg-tertiary);
color: var(--text-primary);
font-size: 1.5rem;
margin-bottom: 0.5rem;
font-weight: 600;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 0.05em;
}
.header .subtitle {
color: var(--accent);
font-size: 0.875rem;
tr:last-child td { border-bottom: none; }
tr:hover td { background: rgba(255, 255, 255, 0.02); }
/* Code Blocks */
pre {
background: var(--code-bg);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
margin: 1rem 0;
border: 1px solid var(--border);
}
code {
font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
color: #e2e8f0;
}
p code, li code, td code {
background: rgba(255, 255, 255, 0.1);
padding: 0.2em 0.4em;
border-radius: 4px;
font-size: 0.85em;
color: #e2e8f0;
}
/* Lists */
ul, ol { margin-left: 1.5rem; margin-bottom: 1rem; }
li { margin-bottom: 0.5rem; }
/* Blockquotes */
blockquote {
border-left: 4px solid var(--accent);
padding-left: 1rem;
margin: 1rem 0;
color: var(--text-secondary);
font-style: italic;
background: rgba(255, 107, 44, 0.05);
padding: 0.5rem 0.5rem 0.5rem 1rem;
border-radius: 0 4px 4px 0;
}
/* Links */
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* Horizontal Rule */
hr {
border: 0;
height: 1px;
background: var(--border);
margin: 2rem 0;
}
/* Utilities */
strong { color: var(--text-primary); font-weight: 600; }
em { color: var(--text-secondary); }
/* Print Styles */
@media print {
body {
background: white;
color: black;
max-width: 100%;
padding: 0;
}
h1, h2, h3, strong { color: black; }
pre, code { background: #f1f5f9; color: black; border-color: #e2e8f0; }
table { border-color: #e2e8f0; }
th { background: #f8fafc; color: black; border-bottom-color: #cbd5e1; }
td { border-bottom-color: #e2e8f0; }
p code, li code, td code { background: #f1f5f9; color: #0f172a; }
a { color: #2563eb; text-decoration: underline; }
}
.severity-critical { color: #fb7185; }
.severity-high { color: #fb923c; }
.severity-medium { color: #fbbf24; }
.severity-low { color: #38bdf8; }
</style>
</head>
<body>
<div class="header">
<h1>DEEPAUDIT Security Report</h1>
<p class="subtitle">Generated ${new Date().toLocaleString()}</p>
</div>
<main>
<p class="report-p">${html}</p>
</main>
${contentHtml}
<footer style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--border); text-align: center; font-size: 0.875rem; color: var(--text-secondary);">
<p> ${new Date().toLocaleString('zh-CN')} &bull; DeepAudit </p>
</footer>
</body>
</html>`;
};
@ -540,26 +582,64 @@ export const ReportExportDialog = memo(function ReportExportDialog({
// Handle download
const handleDownload = async () => {
if (!task) return;
if (!task || !findings && activeFormat !== "json") return; // Allow json even if findings empty? Actually backend handles it.
setDownloading(true);
try {
if (activeFormat === "markdown" || activeFormat === "json") {
await downloadAgentReport(task.id, activeFormat);
} else {
// HTML download
const blob = new Blob([preview.content], { type: "text/html" });
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `audit-report-${task.id.slice(0, 8)}.html`;
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
let content = "";
let filename = `audit_report_${task.id.substring(0, 8)}`;
let mimeType = "text/plain";
if (activeFormat === "markdown") {
content = preview.content; // Use cached preview
// Fallback if preview failed? Re-fetch.
if (!content) {
const response = await apiClient.get(`/agent-tasks/${task.id}/report`, {
params: { format: "markdown" },
responseType: "text",
});
content = response.data;
}
filename += ".md";
} else if (activeFormat === "json") {
// Fetch JSON directly from backend
const response = await apiClient.get(`/agent-tasks/${task.id}/report`, {
params: { format: "json" },
responseType: "json", // Important: axios parses JSON
});
// Pretty print JSON
content = JSON.stringify(response.data, null, 2);
filename += ".json";
mimeType = "application/json";
} else if (activeFormat === "html") { // HTML
if (preview.content && preview.format === "html") {
content = preview.content;
} else {
const response = await apiClient.get(`/agent-tasks/${task.id}/report`, {
params: { format: "markdown" },
responseType: "text",
});
content = await generateHtmlReport(response.data, task);
}
filename += ".html";
mimeType = "text/html";
}
// Create download trigger
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
onOpenChange(false);
} catch (err) {
console.error("Download failed:", err);
// Ideally show toast error here
} finally {
setDownloading(false);
}