@vint.tri/report_gen_mcp 1.0.30 โ 1.0.32
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.
- package/dist/index.js +31 -13
- package/final-verification-test.js +116 -141
- package/package.json +2 -1
- package/src/index.ts +28 -13
- package/test-reports/test-file-url-report.html +15 -0
package/dist/index.js
CHANGED
|
@@ -9,9 +9,11 @@ const commander_1 = require("commander");
|
|
|
9
9
|
const reportGenerator_1 = require("./utils/reportGenerator");
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
12
13
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
13
14
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
14
15
|
const zod_1 = require("zod");
|
|
16
|
+
const url_1 = require("url");
|
|
15
17
|
// Check if we're running in stdio mode (no command-line arguments)
|
|
16
18
|
const isStdioMode = process.argv.length === 2;
|
|
17
19
|
// For CLI and HTTP API modes, check for mandatory REPORTS_DIR environment variable
|
|
@@ -88,7 +90,7 @@ if (process.argv.length === 2) {
|
|
|
88
90
|
// No command specified, run in stdio mode using MCP SDK
|
|
89
91
|
const mcpServer = new mcp_js_1.McpServer({
|
|
90
92
|
name: "report_gen_mcp",
|
|
91
|
-
version: "1.0.
|
|
93
|
+
version: "1.0.31",
|
|
92
94
|
}, {
|
|
93
95
|
// Disable health check to prevent automatic calls
|
|
94
96
|
capabilities: {
|
|
@@ -117,7 +119,7 @@ if (process.argv.length === 2) {
|
|
|
117
119
|
}),
|
|
118
120
|
})).describe("Chart configurations mapped by ID"),
|
|
119
121
|
outputFile: zod_1.z.string().optional().describe("Output HTML file path"),
|
|
120
|
-
tempDirectory: zod_1.z.string().describe("Temporary directory for file storage (
|
|
122
|
+
tempDirectory: zod_1.z.string().optional().describe("Temporary directory for file storage (optional, will use REPORTS_DIR environment variable if set)"),
|
|
121
123
|
},
|
|
122
124
|
}, async (params) => {
|
|
123
125
|
// Handle case where arguments might be sent as a JSON string by Claude desktop
|
|
@@ -133,16 +135,33 @@ if (process.argv.length === 2) {
|
|
|
133
135
|
else if (params.arguments && typeof params.arguments === 'object') {
|
|
134
136
|
processedParams = params.arguments;
|
|
135
137
|
}
|
|
136
|
-
// Check if tempDirectory parameter is provided
|
|
137
|
-
if (!processedParams.tempDirectory) {
|
|
138
|
-
throw new Error('tempDirectory parameter is required. Please provide the directory where reports should be generated.');
|
|
139
|
-
}
|
|
140
138
|
const { document, charts, outputFile = 'report.html', tempDirectory } = processedParams;
|
|
141
|
-
|
|
139
|
+
// Determine the output directory:
|
|
140
|
+
// 1. Use REPORTS_DIR environment variable if set
|
|
141
|
+
// 2. Fall back to tempDirectory parameter if provided
|
|
142
|
+
// 3. Default to system temp directory if neither is available
|
|
143
|
+
let outputDir;
|
|
144
|
+
if (process.env.REPORTS_DIR) {
|
|
145
|
+
outputDir = process.env.REPORTS_DIR;
|
|
146
|
+
// Ensure the reports directory exists
|
|
147
|
+
try {
|
|
148
|
+
fs_extra_1.default.ensureDirSync(outputDir);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
throw new Error(`Cannot create or access the reports directory: ${outputDir}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (tempDirectory) {
|
|
155
|
+
outputDir = tempDirectory;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
outputDir = os_1.default.tmpdir();
|
|
159
|
+
}
|
|
160
|
+
const outputPath = path_1.default.resolve(outputDir, outputFile);
|
|
142
161
|
try {
|
|
143
162
|
const result = await (0, reportGenerator_1.generateReport)(document, charts, outputPath);
|
|
144
|
-
//
|
|
145
|
-
const
|
|
163
|
+
// Generate proper file URL
|
|
164
|
+
const fileUrl = (0, url_1.pathToFileURL)(outputPath).href;
|
|
146
165
|
return {
|
|
147
166
|
content: [
|
|
148
167
|
{
|
|
@@ -151,7 +170,7 @@ if (process.argv.length === 2) {
|
|
|
151
170
|
...result,
|
|
152
171
|
message: "Report generated successfully",
|
|
153
172
|
filePath: outputPath,
|
|
154
|
-
fileUrl:
|
|
173
|
+
fileUrl: fileUrl
|
|
155
174
|
})
|
|
156
175
|
}
|
|
157
176
|
]
|
|
@@ -172,9 +191,8 @@ if (process.argv.length === 2) {
|
|
|
172
191
|
try {
|
|
173
192
|
// Check if file exists
|
|
174
193
|
await fs_extra_1.default.access(filePath);
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
const fileUrl = `file://${encodedPath}`;
|
|
194
|
+
// Generate proper file URL
|
|
195
|
+
const fileUrl = (0, url_1.pathToFileURL)(filePath).href;
|
|
178
196
|
return {
|
|
179
197
|
content: [
|
|
180
198
|
{
|
|
@@ -1,164 +1,139 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Final verification test for report_gen_mcp v1.0.23
|
|
4
|
-
// This test verifies that the tempDirectory parameter is properly handled
|
|
5
|
-
|
|
6
3
|
const { spawn } = require('child_process');
|
|
7
4
|
const fs = require('fs');
|
|
8
5
|
const path = require('path');
|
|
9
6
|
|
|
10
|
-
console.log('
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
console.log('=== Final Verification Test ===\n');
|
|
8
|
+
|
|
9
|
+
// Test 1: CLI mode with REPORTS_DIR
|
|
10
|
+
console.log('Test 1: CLI mode with REPORTS_DIR environment variable...');
|
|
11
|
+
const testReportsDir = path.join(__dirname, 'test-reports');
|
|
12
|
+
if (!fs.existsSync(testReportsDir)) {
|
|
13
|
+
fs.mkdirSync(testReportsDir);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const cliTest = spawn('node', [
|
|
17
|
+
'dist/index.js',
|
|
18
|
+
'generate',
|
|
19
|
+
'--document', '# CLI Test Report\n\nThis is a test report with a [[chart:cli]] chart.',
|
|
20
|
+
'--charts', JSON.stringify({
|
|
21
|
+
cli: {
|
|
22
|
+
type: "bar",
|
|
23
|
+
config: {
|
|
24
|
+
labels: ["X", "Y", "Z"],
|
|
25
|
+
datasets: [{
|
|
26
|
+
label: "CLI Data",
|
|
27
|
+
data: [10, 20, 30],
|
|
28
|
+
backgroundColor: ["red", "green", "blue"]
|
|
29
|
+
}]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
'--output', 'cli-test-report.html'
|
|
34
|
+
], {
|
|
35
|
+
env: {
|
|
36
|
+
...process.env,
|
|
37
|
+
REPORTS_DIR: testReportsDir
|
|
38
|
+
}
|
|
17
39
|
});
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
testWithTempDir.stdout.on('data', (data) => {
|
|
22
|
-
test1Output += data.toString();
|
|
41
|
+
cliTest.stdout.on('data', (data) => {
|
|
42
|
+
console.log('CLI stdout:', data.toString());
|
|
23
43
|
});
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
cliTest.stderr.on('data', (data) => {
|
|
46
|
+
console.log('CLI stderr:', data.toString());
|
|
27
47
|
});
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
console.log(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
outputFile: "test-report.html",
|
|
58
|
-
tempDirectory: "/tmp"
|
|
49
|
+
cliTest.on('close', (code) => {
|
|
50
|
+
console.log('CLI test exited with code:', code);
|
|
51
|
+
|
|
52
|
+
const expectedReportPath = path.join(testReportsDir, 'cli-test-report.html');
|
|
53
|
+
if (fs.existsSync(expectedReportPath)) {
|
|
54
|
+
console.log('โ
CLI Test PASSED: Report generated in correct directory\n');
|
|
55
|
+
} else {
|
|
56
|
+
console.log('โ CLI Test FAILED: Report not found in expected directory\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Clean up
|
|
60
|
+
if (fs.existsSync(testReportsDir)) {
|
|
61
|
+
fs.rmSync(testReportsDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Test 2: CLI mode without REPORTS_DIR (should fail)
|
|
65
|
+
console.log('Test 2: CLI mode without REPORTS_DIR environment variable (should fail)...');
|
|
66
|
+
const cliErrorTest = spawn('node', [
|
|
67
|
+
'dist/index.js',
|
|
68
|
+
'generate',
|
|
69
|
+
'--document', '# Error Test Report',
|
|
70
|
+
'--charts', JSON.stringify({}),
|
|
71
|
+
'--output', 'error-test-report.html'
|
|
72
|
+
], {
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
REPORTS_DIR: ''
|
|
59
76
|
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
console.log('\n๐ Sending test request with tempDirectory...');
|
|
63
|
-
console.log(JSON.stringify(testRequest, null, 2));
|
|
64
|
-
|
|
65
|
-
const testProcess = spawn('npx', ['@vint.tri/report_gen_mcp@1.0.23'], {
|
|
66
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
77
|
});
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
testProcess.stdout.on('data', (data) => {
|
|
72
|
-
testOutput += data.toString();
|
|
79
|
+
cliErrorTest.stdout.on('data', (data) => {
|
|
80
|
+
console.log('CLI Error stdout:', data.toString());
|
|
73
81
|
});
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
cliErrorTest.stderr.on('data', (data) => {
|
|
84
|
+
console.log('CLI Error stderr:', data.toString());
|
|
77
85
|
});
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
urlProcess.stdin.write(JSON.stringify(urlRequest));
|
|
121
|
-
urlProcess.stdin.end();
|
|
122
|
-
|
|
123
|
-
urlProcess.on('close', (code) => {
|
|
124
|
-
console.log(`\nโ
URL test completed with code ${code}`);
|
|
125
|
-
console.log('Full output:');
|
|
126
|
-
console.log(urlOutput);
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const urlLines = urlOutput.split('\n').filter(line => line.trim());
|
|
130
|
-
const urlLastLine = urlLines[urlLines.length - 1];
|
|
131
|
-
const urlResponse = JSON.parse(urlLastLine);
|
|
132
|
-
|
|
133
|
-
if (urlResponse.success) {
|
|
134
|
-
console.log('๐ SUCCESS: get-report-url works correctly!');
|
|
135
|
-
console.log(`๐ File URL: ${urlResponse.fileUrl}`);
|
|
136
|
-
console.log('\n๐ ALL TESTS PASSED! The fix is working correctly.');
|
|
137
|
-
console.log('๐ Summary:');
|
|
138
|
-
console.log(' - tempDirectory parameter is properly handled');
|
|
139
|
-
console.log(' - Reports are generated successfully');
|
|
140
|
-
console.log(' - File URLs are returned correctly');
|
|
141
|
-
console.log(' - Version 1.0.23 is working as expected');
|
|
142
|
-
} else {
|
|
143
|
-
console.log('โ FAILED: get-report-url did not succeed');
|
|
144
|
-
}
|
|
145
|
-
} catch (e) {
|
|
146
|
-
console.log('โ ERROR: Could not parse URL response');
|
|
147
|
-
console.error(e);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
} else {
|
|
151
|
-
console.log('โ FAILED: Report generation was not successful');
|
|
152
|
-
console.log(response.message || 'Unknown error');
|
|
153
|
-
}
|
|
154
|
-
} catch (e) {
|
|
155
|
-
console.log('โ ๏ธ WARNING: Could not parse response, but checking for success indicators...');
|
|
156
|
-
if (testOutput.includes('success') && testOutput.includes('filePath')) {
|
|
157
|
-
console.log('๐ SUCCESS: Found success indicators in output!');
|
|
87
|
+
cliErrorTest.on('close', (code) => {
|
|
88
|
+
console.log('CLI error test exited with code:', code);
|
|
89
|
+
|
|
90
|
+
if (code !== 0) {
|
|
91
|
+
console.log('โ
CLI Error Test PASSED: Correctly failed without REPORTS_DIR\n');
|
|
92
|
+
} else {
|
|
93
|
+
console.log('โ CLI Error Test FAILED: Should have failed without REPORTS_DIR\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Test 3: Stdio mode (should work without REPORTS_DIR)
|
|
97
|
+
console.log('Test 3: Stdio mode (should work without REPORTS_DIR)...');
|
|
98
|
+
const stdioTest = spawn('node', ['dist/index.js'], {
|
|
99
|
+
env: process.env
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Send a simple MCP request
|
|
103
|
+
const mcpRequest = {
|
|
104
|
+
"jsonrpc": "2.0",
|
|
105
|
+
"id": 1,
|
|
106
|
+
"method": "tools/list",
|
|
107
|
+
"params": {}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
stdioTest.stdin.write(JSON.stringify(mcpRequest) + '\n');
|
|
111
|
+
|
|
112
|
+
let stdioOutput = '';
|
|
113
|
+
stdioTest.stdout.on('data', (data) => {
|
|
114
|
+
stdioOutput += data.toString();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
stdioTest.stderr.on('data', (data) => {
|
|
118
|
+
console.log('Stdio stderr:', data.toString());
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
stdioTest.on('close', (code) => {
|
|
122
|
+
console.log('Stdio test exited with code:', code);
|
|
123
|
+
console.log('Stdio output:', stdioOutput);
|
|
124
|
+
|
|
125
|
+
if (stdioOutput.includes('"tools"') || stdioOutput.includes('MCP server is running')) {
|
|
126
|
+
console.log('โ
Stdio Test PASSED: Stdio mode is working\n');
|
|
158
127
|
} else {
|
|
159
|
-
console.log('โ FAILED:
|
|
160
|
-
console.error(e);
|
|
128
|
+
console.log('โ Stdio Test FAILED: Stdio mode not working correctly\n');
|
|
161
129
|
}
|
|
162
|
-
|
|
130
|
+
|
|
131
|
+
console.log('=== All Tests Completed ===');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Give it a moment then close stdin
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
stdioTest.stdin.end();
|
|
137
|
+
}, 1000);
|
|
163
138
|
});
|
|
164
139
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vint.tri/report_gen_mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "CLI tool for generating HTML reports with embedded charts",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
16
|
+
"@vint.tri/report_gen_mcp": "^1.0.31",
|
|
16
17
|
"chart.js": "^3.9.1",
|
|
17
18
|
"chartjs-node-canvas": "^4.1.6",
|
|
18
19
|
"commander": "^12.1.0",
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import os from 'os';
|
|
|
9
9
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
import { pathToFileURL } from 'url';
|
|
12
13
|
|
|
13
14
|
// Check if we're running in stdio mode (no command-line arguments)
|
|
14
15
|
const isStdioMode = process.argv.length === 2;
|
|
@@ -94,7 +95,7 @@ if (process.argv.length === 2) {
|
|
|
94
95
|
// No command specified, run in stdio mode using MCP SDK
|
|
95
96
|
const mcpServer = new McpServer({
|
|
96
97
|
name: "report_gen_mcp",
|
|
97
|
-
version: "1.0.
|
|
98
|
+
version: "1.0.31",
|
|
98
99
|
}, {
|
|
99
100
|
// Disable health check to prevent automatic calls
|
|
100
101
|
capabilities: {
|
|
@@ -124,7 +125,7 @@ if (process.argv.length === 2) {
|
|
|
124
125
|
}),
|
|
125
126
|
})).describe("Chart configurations mapped by ID"),
|
|
126
127
|
outputFile: z.string().optional().describe("Output HTML file path"),
|
|
127
|
-
tempDirectory: z.string().describe("Temporary directory for file storage (
|
|
128
|
+
tempDirectory: z.string().optional().describe("Temporary directory for file storage (optional, will use REPORTS_DIR environment variable if set)"),
|
|
128
129
|
},
|
|
129
130
|
}, async (params: any) => {
|
|
130
131
|
// Handle case where arguments might be sent as a JSON string by Claude desktop
|
|
@@ -139,18 +140,33 @@ if (process.argv.length === 2) {
|
|
|
139
140
|
processedParams = params.arguments;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const { document, charts, outputFile = 'report.html', tempDirectory } = processedParams;
|
|
144
|
+
|
|
145
|
+
// Determine the output directory:
|
|
146
|
+
// 1. Use REPORTS_DIR environment variable if set
|
|
147
|
+
// 2. Fall back to tempDirectory parameter if provided
|
|
148
|
+
// 3. Default to system temp directory if neither is available
|
|
149
|
+
let outputDir: string;
|
|
150
|
+
if (process.env.REPORTS_DIR) {
|
|
151
|
+
outputDir = process.env.REPORTS_DIR;
|
|
152
|
+
// Ensure the reports directory exists
|
|
153
|
+
try {
|
|
154
|
+
fs.ensureDirSync(outputDir);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw new Error(`Cannot create or access the reports directory: ${outputDir}`);
|
|
157
|
+
}
|
|
158
|
+
} else if (tempDirectory) {
|
|
159
|
+
outputDir = tempDirectory;
|
|
160
|
+
} else {
|
|
161
|
+
outputDir = os.tmpdir();
|
|
145
162
|
}
|
|
146
163
|
|
|
147
|
-
const
|
|
148
|
-
const outputPath = path.resolve(tempDirectory, outputFile);
|
|
164
|
+
const outputPath = path.resolve(outputDir, outputFile);
|
|
149
165
|
|
|
150
166
|
try {
|
|
151
167
|
const result = await generateReport(document, charts, outputPath);
|
|
152
|
-
//
|
|
153
|
-
const
|
|
168
|
+
// Generate proper file URL
|
|
169
|
+
const fileUrl = pathToFileURL(outputPath).href;
|
|
154
170
|
return {
|
|
155
171
|
content: [
|
|
156
172
|
{
|
|
@@ -159,7 +175,7 @@ if (process.argv.length === 2) {
|
|
|
159
175
|
...result,
|
|
160
176
|
message: "Report generated successfully",
|
|
161
177
|
filePath: outputPath,
|
|
162
|
-
fileUrl:
|
|
178
|
+
fileUrl: fileUrl
|
|
163
179
|
})
|
|
164
180
|
}
|
|
165
181
|
]
|
|
@@ -182,9 +198,8 @@ if (process.argv.length === 2) {
|
|
|
182
198
|
// Check if file exists
|
|
183
199
|
await fs.access(filePath);
|
|
184
200
|
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
const fileUrl = `file://${encodedPath}`;
|
|
201
|
+
// Generate proper file URL
|
|
202
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
188
203
|
return {
|
|
189
204
|
content: [
|
|
190
205
|
{
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<title>Report</title>
|
|
7
|
+
<style> body { font-family: Arial, sans-serif; } img { max-width: 100%; } </style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>Test Report</h1>
|
|
11
|
+
<p>This is a test report with a <img src="data:image/png;base64,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993PL6W8ZuFBTdM8vtb6jNnZ2Q9ExNqJiYkvllJeMBgM7px3t9URseYwz7/+gQwPHNPWjXoAYGSW2Pq/7cSItaMeAhbR5UtqzR3wQvOJiYl/e88997y11vqhwWDwroW311o/U0p5+fT09PsnJyd7N9988zsj4jci4jfn3W1XROw8xHOvO8ztwPLnNQCOX0to/a+9a9QTwOK69JaIy5bMmvuuAGnb9pKIuLDruv98sIvKB4PBe/b9Pjk5Ode27Z9FxIWLNyYAALAc7HcNSL/fPzciXhARTz7UJ1pNTEy8p9/vnzlv16Mj4pbFGREAAFgu9jsDUmv9tVrrR5umuaDf7+/bff309PR1/X7/j2qtKweDwaZSyvtqrdf0+/3/EhFraq0X11qfmz49AABwTJl/BqRExFubpvmnA92x67q/rbVeGxExGAymu647NyIeXmvdMxwOf3xmZubGhHkBAIBjWBnhc58REaIFjl/rY0ldhAokWmLrv14REa8a9RSwiDZElCWz5hZ+DwgAAMCiESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkESAAAEAaAQIAAKQRIAAAQBoBAgAApBEgAABAGgECAACkOWCAtG170sUXX/yQwx188cUXP+Scc845+eiPBQAALEdl/saFF164puu6t9VaHx0RJ5RSPr579+5f3LZt267599u4cePKNWvWvCMi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" alt="Chart" /> chart.</p>
|
|
12
|
+
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
15
|
+
|