@sfdxy/mule-lint 1.5.2 → 1.7.0
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/README.md +22 -2
- package/dist/package.json +70 -0
- package/dist/src/core/YamlParser.d.ts +1 -0
- package/dist/src/core/YamlParser.d.ts.map +1 -1
- package/dist/src/core/YamlParser.js +38 -7
- package/dist/src/core/YamlParser.js.map +1 -1
- package/dist/src/formatters/HtmlFormatter.d.ts +1 -1
- package/dist/src/formatters/HtmlFormatter.d.ts.map +1 -1
- package/dist/src/formatters/HtmlFormatter.js +575 -330
- package/dist/src/formatters/HtmlFormatter.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,12 +49,22 @@ flowchart TB
|
|
|
49
49
|
I[Table]
|
|
50
50
|
J[JSON]
|
|
51
51
|
K[SARIF]
|
|
52
|
+
L[HTML Dashboard]
|
|
53
|
+
M[CSV]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph HTMLStack["HTML Report Stack"]
|
|
57
|
+
L --> N[Tailwind CSS]
|
|
58
|
+
L --> O[Chart.js]
|
|
59
|
+
L --> P[Tabulator]
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
A --> B
|
|
55
63
|
D --> I
|
|
56
64
|
D --> J
|
|
57
65
|
D --> K
|
|
66
|
+
D --> L
|
|
67
|
+
D --> M
|
|
58
68
|
```
|
|
59
69
|
|
|
60
70
|
### Data Flow
|
|
@@ -259,9 +269,19 @@ Machine-readable for scripting:
|
|
|
259
269
|
}
|
|
260
270
|
```
|
|
261
271
|
|
|
262
|
-
### HTML (
|
|
272
|
+
### HTML (Interactive Dashboard)
|
|
273
|
+
|
|
274
|
+
Generates a modern, interactive single-page report with:
|
|
275
|
+
|
|
276
|
+
- **Dashboard View**: Summary cards, severity donut chart, top violated rules bar chart, issues by category
|
|
277
|
+
- **Issues Browser**: Full-width searchable table with multiselect filters
|
|
278
|
+
- **Frozen Headers**: Table header stays visible when scrolling
|
|
279
|
+
- **Export**: Download filtered results as CSV
|
|
280
|
+
- **Responsive**: Works on desktop and mobile
|
|
281
|
+
|
|
282
|
+
Built with **Tailwind CSS**, **Chart.js**, and **Tabulator**.
|
|
263
283
|
|
|
264
|
-
|
|
284
|
+

|
|
265
285
|
|
|
266
286
|
```bash
|
|
267
287
|
mule-lint src/main/mule -f html -o report.html
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sfdxy/mule-lint",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Static analysis tool for MuleSoft applications - supports humans, AI agents, and CI/CD pipelines",
|
|
5
|
+
"author": "Avinava",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mulesoft",
|
|
9
|
+
"mule",
|
|
10
|
+
"lint",
|
|
11
|
+
"static-analysis",
|
|
12
|
+
"xml",
|
|
13
|
+
"sarif",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"mule-lint": "./dist/bin/mule-lint.js"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"build:watch": "tsc --watch",
|
|
29
|
+
"test": "jest",
|
|
30
|
+
"test:watch": "jest --watch",
|
|
31
|
+
"test:coverage": "jest --coverage",
|
|
32
|
+
"lint": "eslint src/ --ext .ts",
|
|
33
|
+
"lint:fix": "eslint src/ --ext .ts --fix",
|
|
34
|
+
"format": "prettier --write \"src/**/*.{ts,js,json,md}\"",
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"prepublishOnly": "npm run clean && npm run build && npm test",
|
|
37
|
+
"release": "git tag v$(node -p 'require(\"./package.json\").version') && git push origin v$(node -p 'require(\"./package.json\").version')"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@xmldom/xmldom": "^0.8.10",
|
|
41
|
+
"chalk": "^4.1.2",
|
|
42
|
+
"commander": "^11.1.0",
|
|
43
|
+
"fast-glob": "^3.3.2",
|
|
44
|
+
"js-yaml": "^4.1.1",
|
|
45
|
+
"xpath": "^0.0.34"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/jest": "^29.5.11",
|
|
49
|
+
"@types/js-yaml": "^4.0.9",
|
|
50
|
+
"@types/node": "^20.19.27",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
52
|
+
"@typescript-eslint/parser": "^6.15.0",
|
|
53
|
+
"eslint": "^8.56.0",
|
|
54
|
+
"jest": "^29.7.0",
|
|
55
|
+
"prettier": "^3.7.4",
|
|
56
|
+
"ts-jest": "^29.1.1",
|
|
57
|
+
"typescript": "^5.3.3"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.0.0"
|
|
61
|
+
},
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "https://github.com/Avinava/mule-lint.git"
|
|
65
|
+
},
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/Avinava/mule-lint/issues"
|
|
68
|
+
},
|
|
69
|
+
"homepage": "https://github.com/Avinava/mule-lint#readme"
|
|
70
|
+
}
|
|
@@ -24,6 +24,7 @@ export declare class YamlParser {
|
|
|
24
24
|
static isEncryptedValue(value: unknown): boolean;
|
|
25
25
|
/**
|
|
26
26
|
* Check if a key name suggests it contains sensitive data
|
|
27
|
+
* Uses word-boundary matching to avoid false positives
|
|
27
28
|
*/
|
|
28
29
|
static isSensitiveKey(key: string): boolean;
|
|
29
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YamlParser.d.ts","sourceRoot":"","sources":["../../../src/core/YamlParser.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,qBAAa,UAAU;IACnB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IASlE;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAQ7D;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK5C;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE;IAiBtE;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAMhD
|
|
1
|
+
{"version":3,"file":"YamlParser.d.ts","sourceRoot":"","sources":["../../../src/core/YamlParser.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,qBAAa,UAAU;IACnB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IASlE;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAQ7D;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK5C;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE;IAiBtE;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAMhD;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAgD9C"}
|
|
@@ -99,21 +99,52 @@ class YamlParser {
|
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Check if a key name suggests it contains sensitive data
|
|
102
|
+
* Uses word-boundary matching to avoid false positives
|
|
102
103
|
*/
|
|
103
104
|
static isSensitiveKey(key) {
|
|
104
|
-
const
|
|
105
|
+
const lowerKey = key.toLowerCase();
|
|
106
|
+
// Patterns that indicate a secret - must be at word boundary (end of key or before .)
|
|
107
|
+
const secretKeyEndings = [
|
|
105
108
|
'password',
|
|
109
|
+
'passwd',
|
|
106
110
|
'secret',
|
|
107
111
|
'apikey',
|
|
108
112
|
'api-key',
|
|
109
113
|
'api_key',
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
+
'accesstoken',
|
|
115
|
+
'access-token',
|
|
116
|
+
'access_token',
|
|
117
|
+
'refreshtoken',
|
|
118
|
+
'refresh-token',
|
|
119
|
+
'refresh_token',
|
|
120
|
+
'clientsecret',
|
|
121
|
+
'client-secret',
|
|
122
|
+
'client_secret',
|
|
123
|
+
'privatekey',
|
|
124
|
+
'private-key',
|
|
125
|
+
'private_key',
|
|
126
|
+
'credentials',
|
|
127
|
+
'authtoken',
|
|
128
|
+
'auth-token',
|
|
129
|
+
'auth_token',
|
|
130
|
+
'bearertoken',
|
|
131
|
+
'bearer-token',
|
|
132
|
+
'bearer_token',
|
|
133
|
+
'consumerkey',
|
|
134
|
+
'consumer-key',
|
|
135
|
+
'consumer_key',
|
|
136
|
+
'consumersecret',
|
|
137
|
+
'consumer-secret',
|
|
138
|
+
'consumer_secret',
|
|
139
|
+
'tokensecret',
|
|
140
|
+
'token-secret',
|
|
141
|
+
'token_secret',
|
|
114
142
|
];
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
// Get the last segment of the key (after the last .)
|
|
144
|
+
const segments = lowerKey.split('.');
|
|
145
|
+
const lastSegment = segments[segments.length - 1];
|
|
146
|
+
// Check if the last segment matches any secret pattern
|
|
147
|
+
return secretKeyEndings.some(pattern => lastSegment === pattern || lastSegment.endsWith(pattern));
|
|
117
148
|
}
|
|
118
149
|
}
|
|
119
150
|
exports.YamlParser = YamlParser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YamlParser.js","sourceRoot":"","sources":["../../../src/core/YamlParser.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8CAAgC;AAChC,uCAAyB;AACzB,2CAA6B;AAE7B;;GAEG;AACH,MAAa,UAAU;IACnB;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,QAAgB;QAC7B,IAAI,CAAC;YACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAClD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAA4B,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,OAAe;QACxB,IAAI,CAAC;YACD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAA4B,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,QAAgB;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,GAA4B,EAAE,MAAM,GAAG,EAAE;QACvD,MAAM,IAAI,GAAa,EAAE,CAAC;QAE1B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;YAClD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YAEvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvE,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,KAAgC,EAAE,OAAO,CAAC,CAAC,CAAC;YAC7E,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;QACL,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAc;QAClC,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC5C,8CAA8C;QAC9C,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"YamlParser.js","sourceRoot":"","sources":["../../../src/core/YamlParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LintReport } from '../types/Report';
|
|
2
2
|
/**
|
|
3
|
-
* Format lint report as a modern, interactive HTML
|
|
3
|
+
* Format lint report as a modern, interactive HTML Single Page Application
|
|
4
4
|
*/
|
|
5
5
|
export declare function formatHtml(report: LintReport): string;
|
|
6
6
|
//# sourceMappingURL=HtmlFormatter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HtmlFormatter.d.ts","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"HtmlFormatter.d.ts","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CA6mBrD"}
|
|
@@ -1,386 +1,631 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.formatHtml = formatHtml;
|
|
7
|
+
const rules_1 = require("../rules");
|
|
8
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
4
9
|
/**
|
|
5
|
-
* Format lint report as a modern, interactive HTML
|
|
10
|
+
* Format lint report as a modern, interactive HTML Single Page Application
|
|
6
11
|
*/
|
|
7
12
|
function formatHtml(report) {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
message: issue.message,
|
|
36
|
-
ruleId: issue.ruleId
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
13
|
+
// 1. Enrich Data
|
|
14
|
+
const enrichedFiles = report.files.map(file => ({
|
|
15
|
+
...file,
|
|
16
|
+
issues: file.issues.map(issue => {
|
|
17
|
+
const ruleDef = rules_1.ALL_RULES.find(r => r.id === issue.ruleId);
|
|
18
|
+
return {
|
|
19
|
+
...issue,
|
|
20
|
+
category: ruleDef?.category || 'General',
|
|
21
|
+
ruleDescription: ruleDef?.description || 'No description available',
|
|
22
|
+
ruleName: ruleDef?.name || issue.ruleId,
|
|
23
|
+
file: file.relativePath
|
|
24
|
+
};
|
|
25
|
+
})
|
|
26
|
+
}));
|
|
27
|
+
// Reconstruct report data for the client
|
|
28
|
+
const clientData = {
|
|
29
|
+
metadata: {
|
|
30
|
+
timestamp: report.timestamp,
|
|
31
|
+
version: package_json_1.default.version,
|
|
32
|
+
filesScanned: report.files.length,
|
|
33
|
+
duration: report.durationMs || 0
|
|
34
|
+
},
|
|
35
|
+
summary: report.summary,
|
|
36
|
+
files: enrichedFiles,
|
|
37
|
+
rules: rules_1.ALL_RULES.map(r => ({ id: r.id, name: r.name, category: r.category, severity: r.severity }))
|
|
38
|
+
};
|
|
39
|
+
const jsonPayload = JSON.stringify(clientData).replace(/</g, '\\u003c');
|
|
40
40
|
return `<!DOCTYPE html>
|
|
41
|
-
<html lang="en">
|
|
41
|
+
<html lang="en" class="bg-gray-50 text-slate-800">
|
|
42
42
|
<head>
|
|
43
43
|
<meta charset="UTF-8">
|
|
44
44
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
45
|
-
<title
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
<title>Mule-Lint Report</title>
|
|
46
|
+
|
|
47
|
+
<!-- Fonts -->
|
|
48
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
49
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
50
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
51
|
+
|
|
52
|
+
<!-- Tailwind CSS -->
|
|
53
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
54
|
+
<script>
|
|
55
|
+
tailwind.config = {
|
|
56
|
+
theme: {
|
|
57
|
+
extend: {
|
|
58
|
+
fontFamily: {
|
|
59
|
+
sans: ['Inter', 'sans-serif'],
|
|
60
|
+
mono: ['JetBrains Mono', 'monospace'],
|
|
61
|
+
},
|
|
62
|
+
colors: {
|
|
63
|
+
brand: {
|
|
64
|
+
50: '#f0f9ff',
|
|
65
|
+
100: '#e0f2fe',
|
|
66
|
+
500: '#0ea5e9',
|
|
67
|
+
600: '#0284c7',
|
|
68
|
+
900: '#0c4a6e',
|
|
69
|
+
},
|
|
70
|
+
severity: {
|
|
71
|
+
error: '#ef4444',
|
|
72
|
+
warning: '#f59e0b',
|
|
73
|
+
info: '#3b82f6'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
60
78
|
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<!-- Icons -->
|
|
82
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
61
83
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
line-height: 1.6;
|
|
69
|
-
}
|
|
84
|
+
<!-- Chart.js -->
|
|
85
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
86
|
+
|
|
87
|
+
<!-- Tabulator -->
|
|
88
|
+
<link href="https://unpkg.com/tabulator-tables@6.2.1/dist/css/tabulator.min.css" rel="stylesheet">
|
|
89
|
+
<script type="text/javascript" src="https://unpkg.com/tabulator-tables@6.2.1/dist/js/tabulator.min.js"></script>
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
91
|
+
<style>
|
|
92
|
+
/* Custom Scrollbar */
|
|
93
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
94
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
95
|
+
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
|
96
|
+
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
|
77
97
|
|
|
78
|
-
/*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
margin-bottom: 2rem;
|
|
84
|
-
position: sticky;
|
|
85
|
-
top: 0;
|
|
86
|
-
z-index: 100;
|
|
87
|
-
}
|
|
88
|
-
.header-content {
|
|
89
|
-
display: flex;
|
|
90
|
-
justify-content: space-between;
|
|
91
|
-
align-items: center;
|
|
98
|
+
/* Tabulator Overrides */
|
|
99
|
+
.tabulator {
|
|
100
|
+
border: none !important;
|
|
101
|
+
background-color: transparent !important;
|
|
102
|
+
font-family: 'Inter', sans-serif !important;
|
|
92
103
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
margin-bottom: 30px;
|
|
104
|
+
.tabulator-header {
|
|
105
|
+
background-color: #f8fafc !important;
|
|
106
|
+
border-bottom: 2px solid #e2e8f0 !important;
|
|
107
|
+
color: #64748b !important;
|
|
108
|
+
font-weight: 600 !important;
|
|
109
|
+
font-size: 0.75rem !important;
|
|
110
|
+
text-transform: uppercase !important;
|
|
111
|
+
letter-spacing: 0.05em !important;
|
|
102
112
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
background: var(--surface);
|
|
107
|
-
padding: 24px;
|
|
108
|
-
border-radius: 8px;
|
|
109
|
-
box-shadow: var(--shadow);
|
|
110
|
-
display: flex;
|
|
111
|
-
flex-direction: column;
|
|
112
|
-
align-items: center;
|
|
113
|
-
justify-content: center;
|
|
114
|
-
transition: transform 0.2s;
|
|
113
|
+
.tabulator-header .tabulator-col {
|
|
114
|
+
background-color: #f8fafc !important;
|
|
115
|
+
border-right: none !important;
|
|
115
116
|
}
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
/* Filters */
|
|
122
|
-
.controls {
|
|
123
|
-
display: flex;
|
|
124
|
-
gap: 15px;
|
|
125
|
-
margin-bottom: 20px;
|
|
126
|
-
align-items: center;
|
|
127
|
-
background: var(--surface);
|
|
128
|
-
padding: 15px;
|
|
129
|
-
border-radius: 8px;
|
|
130
|
-
box-shadow: var(--shadow);
|
|
117
|
+
.tabulator-row {
|
|
118
|
+
background-color: white !important;
|
|
119
|
+
border-bottom: 1px solid #f1f5f9 !important;
|
|
120
|
+
color: #334155 !important;
|
|
131
121
|
}
|
|
132
|
-
.
|
|
133
|
-
|
|
134
|
-
padding: 10px 15px;
|
|
135
|
-
border: 1px solid var(--border);
|
|
136
|
-
border-radius: 6px;
|
|
137
|
-
font-size: 1rem;
|
|
122
|
+
.tabulator-row:hover {
|
|
123
|
+
background-color: #f8fafc !important;
|
|
138
124
|
}
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
gap: 10px;
|
|
125
|
+
.tabulator-row.tabulator-selected {
|
|
126
|
+
background-color: #e0f2fe !important;
|
|
142
127
|
}
|
|
143
|
-
.
|
|
144
|
-
padding:
|
|
145
|
-
border:
|
|
146
|
-
|
|
147
|
-
border-radius: 6px;
|
|
148
|
-
cursor: pointer;
|
|
149
|
-
font-weight: 500;
|
|
150
|
-
color: var(--text-secondary);
|
|
151
|
-
display: flex;
|
|
152
|
-
align-items: center;
|
|
153
|
-
gap: 6px;
|
|
128
|
+
.tabulator-cell {
|
|
129
|
+
padding: 14px 20px !important;
|
|
130
|
+
border-right: none !important;
|
|
131
|
+
font-size: 0.9rem !important;
|
|
154
132
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
133
|
+
|
|
134
|
+
/* Header Filter Inputs */
|
|
135
|
+
.tabulator-header-filter input,
|
|
136
|
+
.tabulator-header-filter select {
|
|
137
|
+
width: 100% !important;
|
|
138
|
+
padding: 6px 10px !important;
|
|
139
|
+
border: 2px solid #cbd5e1 !important;
|
|
140
|
+
border-radius: 6px !important;
|
|
141
|
+
font-size: 0.8rem !important;
|
|
142
|
+
background-color: #ffffff !important;
|
|
143
|
+
color: #334155 !important;
|
|
144
|
+
outline: none !important;
|
|
159
145
|
}
|
|
160
|
-
.filter
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
background: var(--surface);
|
|
165
|
-
border-radius: 8px;
|
|
166
|
-
box-shadow: var(--shadow);
|
|
167
|
-
overflow-x: auto;
|
|
146
|
+
.tabulator-header-filter input:focus,
|
|
147
|
+
.tabulator-header-filter select:focus {
|
|
148
|
+
border-color: #0ea5e9 !important;
|
|
149
|
+
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.15) !important;
|
|
168
150
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
border-collapse: collapse;
|
|
151
|
+
.tabulator-header-filter input::placeholder {
|
|
152
|
+
color: #94a3b8 !important;
|
|
172
153
|
}
|
|
173
154
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
border
|
|
155
|
+
/* Tabulator List Filter Styling */
|
|
156
|
+
.tabulator-edit-list {
|
|
157
|
+
background: white !important;
|
|
158
|
+
border: 2px solid #e2e8f0 !important;
|
|
159
|
+
border-radius: 8px !important;
|
|
160
|
+
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1) !important;
|
|
161
|
+
max-height: 200px !important;
|
|
162
|
+
overflow-y: auto !important;
|
|
178
163
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
font-size: 0.85rem;
|
|
184
|
-
text-transform: uppercase;
|
|
185
|
-
position: sticky;
|
|
186
|
-
top: 0;
|
|
164
|
+
.tabulator-edit-list-item {
|
|
165
|
+
padding: 8px 12px !important;
|
|
166
|
+
font-size: 0.85rem !important;
|
|
167
|
+
color: #334155 !important;
|
|
187
168
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
.severity-badge {
|
|
192
|
-
font-weight: 700;
|
|
193
|
-
text-transform: uppercase;
|
|
194
|
-
font-size: 0.75rem;
|
|
195
|
-
padding: 4px 8px;
|
|
196
|
-
border-radius: 4px;
|
|
197
|
-
display: inline-block;
|
|
198
|
-
min-width: 60px;
|
|
199
|
-
text-align: center;
|
|
169
|
+
.tabulator-edit-list-item:hover {
|
|
170
|
+
background-color: #f1f5f9 !important;
|
|
200
171
|
}
|
|
201
|
-
.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
.file-link { font-family: monospace; font-weight: 600; color: var(--text-primary); }
|
|
206
|
-
.location { font-family: monospace; color: var(--text-secondary); }
|
|
207
|
-
|
|
208
|
-
.rule-pill {
|
|
209
|
-
display: inline-block;
|
|
210
|
-
background: #eef2f5;
|
|
211
|
-
padding: 2px 8px;
|
|
212
|
-
border-radius: 4px;
|
|
213
|
-
font-size: 0.8rem;
|
|
214
|
-
color: var(--text-secondary);
|
|
215
|
-
font-family: monospace;
|
|
216
|
-
border: 1px solid #dce1e6;
|
|
172
|
+
.tabulator-edit-list-item.active {
|
|
173
|
+
background-color: #0ea5e9 !important;
|
|
174
|
+
color: white !important;
|
|
217
175
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
176
|
+
|
|
177
|
+
/* Chart Container */
|
|
178
|
+
.chart-container {
|
|
179
|
+
position: relative;
|
|
180
|
+
height: 250px;
|
|
181
|
+
width: 100%;
|
|
223
182
|
}
|
|
224
|
-
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
|
|
225
183
|
|
|
226
|
-
|
|
227
|
-
@media (max-width: 768px) {
|
|
228
|
-
.controls { flex-direction: column; align-items: stretch; }
|
|
229
|
-
.filter-group { justify-content: space-between; }
|
|
230
|
-
}
|
|
184
|
+
[x-cloak] { display: none !important; }
|
|
231
185
|
</style>
|
|
232
186
|
</head>
|
|
233
187
|
<body>
|
|
234
|
-
<header>
|
|
235
|
-
<div class="container header-content">
|
|
236
|
-
<div>
|
|
237
|
-
<h1>
|
|
238
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
|
239
|
-
${title}
|
|
240
|
-
</h1>
|
|
241
|
-
<div class="meta">Generated on ${date}</div>
|
|
242
|
-
</div>
|
|
243
|
-
<div>
|
|
244
|
-
<a href="#" onclick="window.print()" style="color: var(--primary); text-decoration: none; font-weight: 500;">Download / Print</a>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
</header>
|
|
248
188
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
189
|
+
<!-- Data Injection -->
|
|
190
|
+
<script id="report-data" type="application/json">
|
|
191
|
+
${jsonPayload}
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
<div id="app" class="min-h-screen flex flex-col">
|
|
195
|
+
|
|
196
|
+
<!-- Navbar -->
|
|
197
|
+
<header class="bg-white border-b border-gray-200 sticky top-0 z-50">
|
|
198
|
+
<div class="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8">
|
|
199
|
+
<div class="flex justify-between h-16">
|
|
200
|
+
<div class="flex items-center gap-4">
|
|
201
|
+
<div class="flex items-center gap-2">
|
|
202
|
+
<div class="bg-brand-600 text-white p-1.5 rounded-lg shadow-sm">
|
|
203
|
+
<i data-lucide="shield-check" class="w-6 h-6"></i>
|
|
204
|
+
</div>
|
|
205
|
+
<div>
|
|
206
|
+
<h1 class="text-xl font-bold text-gray-900 tracking-tight">Mule-Lint</h1>
|
|
207
|
+
<p class="text-xs text-gray-500 font-medium">Enterprise Static Analysis</p>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Tabs -->
|
|
212
|
+
<nav class="ml-10 flex space-x-1 bg-gray-100 p-1 rounded-lg">
|
|
213
|
+
<button onclick="router.navigate('dashboard')"
|
|
214
|
+
id="nav-dashboard"
|
|
215
|
+
data-active="true"
|
|
216
|
+
class="px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 data-[active=true]:bg-white data-[active=true]:text-brand-600 data-[active=true]:shadow-sm">
|
|
217
|
+
Dashboard
|
|
218
|
+
</button>
|
|
219
|
+
<button onclick="router.navigate('issues')"
|
|
220
|
+
id="nav-issues"
|
|
221
|
+
class="px-4 py-1.5 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 data-[active=true]:bg-white data-[active=true]:text-brand-600 data-[active=true]:shadow-sm">
|
|
222
|
+
Issues
|
|
223
|
+
<span id="nav-badge" class="ml-2 bg-gray-200 text-gray-600 px-1.5 py-0.5 rounded-full text-xs">0</span>
|
|
224
|
+
</button>
|
|
225
|
+
</nav>
|
|
226
|
+
</div>
|
|
269
227
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
228
|
+
<div class="flex items-center gap-4">
|
|
229
|
+
<span class="text-sm text-gray-500 hidden md:block">
|
|
230
|
+
Generated <span id="timestamp-display" class="font-mono text-gray-700"></span>
|
|
231
|
+
</span>
|
|
232
|
+
<div class="h-6 w-px bg-gray-200"></div>
|
|
233
|
+
<a href="https://github.com/mulesoft-labs/mule-lint" target="_blank" class="text-gray-500 hover:text-gray-900 transition-colors">
|
|
234
|
+
<i data-lucide="github" class="w-5 h-5"></i>
|
|
235
|
+
</a>
|
|
236
|
+
<button onclick="window.print()" class="text-gray-500 hover:text-brand-600 transition-colors" title="Print Report">
|
|
237
|
+
<i data-lucide="printer" class="w-5 h-5"></i>
|
|
238
|
+
</button>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
278
241
|
</div>
|
|
279
|
-
</
|
|
242
|
+
</header>
|
|
280
243
|
|
|
281
|
-
<!-- Main
|
|
282
|
-
<
|
|
283
|
-
<table id="issuesTable">
|
|
284
|
-
<thead>
|
|
285
|
-
<tr>
|
|
286
|
-
<th width="100">Severity</th>
|
|
287
|
-
<th width="150">Rule</th>
|
|
288
|
-
<th width="300">File</th>
|
|
289
|
-
<th width="100">Location</th>
|
|
290
|
-
<th>Message</th>
|
|
291
|
-
</tr>
|
|
292
|
-
</thead>
|
|
293
|
-
<tbody>
|
|
294
|
-
${allIssues.map(issue => renderIssueRow(issue)).join('')}
|
|
295
|
-
</tbody>
|
|
296
|
-
</table>
|
|
244
|
+
<!-- Main Content -->
|
|
245
|
+
<main class="flex-1 w-full">
|
|
297
246
|
|
|
298
|
-
|
|
299
|
-
<div class="
|
|
300
|
-
|
|
301
|
-
<
|
|
302
|
-
|
|
247
|
+
<!-- Dashboard View -->
|
|
248
|
+
<div id="view-dashboard" class="space-y-6 max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
249
|
+
<!-- Summary Cards -->
|
|
250
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
251
|
+
<!-- Error Card -->
|
|
252
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-start justify-between cursor-pointer hover:shadow-md transition-shadow group" onclick="router.filterBySeverity('error')">
|
|
253
|
+
<div>
|
|
254
|
+
<p class="text-sm font-medium text-gray-500">Errors</p>
|
|
255
|
+
<h3 class="text-3xl font-bold text-severity-error mt-2" id="stat-error">0</h3>
|
|
256
|
+
<p class="text-xs text-gray-400 mt-1 group-hover:text-red-400 transition-colors">Critical violations</p>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="p-3 bg-red-50 rounded-lg group-hover:bg-red-100 transition-colors">
|
|
259
|
+
<i data-lucide="x-circle" class="w-6 h-6 text-severity-error"></i>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<!-- Warning Card -->
|
|
264
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-start justify-between cursor-pointer hover:shadow-md transition-shadow group" onclick="router.filterBySeverity('warning')">
|
|
265
|
+
<div>
|
|
266
|
+
<p class="text-sm font-medium text-gray-500">Warnings</p>
|
|
267
|
+
<h3 class="text-3xl font-bold text-severity-warning mt-2" id="stat-warning">0</h3>
|
|
268
|
+
<p class="text-xs text-gray-400 mt-1 group-hover:text-orange-400 transition-colors">Potential issues</p>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="p-3 bg-orange-50 rounded-lg group-hover:bg-orange-100 transition-colors">
|
|
271
|
+
<i data-lucide="alert-triangle" class="w-6 h-6 text-severity-warning"></i>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<!-- Info Card -->
|
|
276
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-start justify-between cursor-pointer hover:shadow-md transition-shadow group" onclick="router.filterBySeverity('info')">
|
|
277
|
+
<div>
|
|
278
|
+
<p class="text-sm font-medium text-gray-500">Infos</p>
|
|
279
|
+
<h3 class="text-3xl font-bold text-severity-info mt-2" id="stat-info">0</h3>
|
|
280
|
+
<p class="text-xs text-gray-400 mt-1 group-hover:text-blue-400 transition-colors">Suggestions</p>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="p-3 bg-blue-50 rounded-lg group-hover:bg-blue-100 transition-colors">
|
|
283
|
+
<i data-lucide="info" class="w-6 h-6 text-severity-info"></i>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<!-- Files Card -->
|
|
288
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex items-start justify-between">
|
|
289
|
+
<div>
|
|
290
|
+
<p class="text-sm font-medium text-gray-500">Files Scanned</p>
|
|
291
|
+
<h3 class="text-3xl font-bold text-gray-700 mt-2" id="stat-files">0</h3>
|
|
292
|
+
<p class="text-xs text-gray-400 mt-1">XML Resources</p>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="p-3 bg-gray-50 rounded-lg">
|
|
295
|
+
<i data-lucide="file-code" class="w-6 h-6 text-gray-500"></i>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<!-- Charts Section -->
|
|
301
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
302
|
+
<!-- Top Rules -->
|
|
303
|
+
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
304
|
+
<div class="flex items-center justify-between mb-6">
|
|
305
|
+
<div>
|
|
306
|
+
<h3 class="text-lg font-semibold text-gray-800">Top 5 Violated Rules</h3>
|
|
307
|
+
<p class="text-sm text-gray-500 mt-1">Most frequently triggered lint rules</p>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="chart-container">
|
|
311
|
+
<canvas id="chart-top-rules"></canvas>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<!-- Severity Distribution -->
|
|
316
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
317
|
+
<div class="flex items-center justify-between mb-6">
|
|
318
|
+
<div>
|
|
319
|
+
<h3 class="text-lg font-semibold text-gray-800">Severity Breakdown</h3>
|
|
320
|
+
<p class="text-sm text-gray-500 mt-1">Distribution by issue type</p>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="chart-container flex items-center justify-center">
|
|
324
|
+
<canvas id="chart-severity"></canvas>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
<!-- Categories Section -->
|
|
330
|
+
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
331
|
+
<div class="flex items-center justify-between mb-6">
|
|
332
|
+
<div>
|
|
333
|
+
<h3 class="text-lg font-semibold text-gray-800">Issues by Category</h3>
|
|
334
|
+
<p class="text-sm text-gray-500 mt-1">Grouped by rule category</p>
|
|
335
|
+
</div>
|
|
336
|
+
<button onclick="router.navigate('issues')" class="text-sm text-brand-600 hover:text-brand-900 font-medium">View All Issues →</button>
|
|
337
|
+
</div>
|
|
338
|
+
<div class="h-[300px] w-full relative">
|
|
339
|
+
<canvas id="chart-categories"></canvas>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
303
342
|
</div>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<div id="
|
|
307
|
-
|
|
308
|
-
<p
|
|
343
|
+
|
|
344
|
+
<!-- Issues View -->
|
|
345
|
+
<div id="view-issues" class="hidden h-[calc(100vh-80px)] flex flex-col px-6 py-4 overflow-hidden">
|
|
346
|
+
<!-- Filters Toolbar -->
|
|
347
|
+
<div class="bg-white p-4 rounded-t-xl border border-gray-200 border-b-0 shadow-sm flex flex-wrap gap-4 items-center justify-between flex-shrink-0">
|
|
348
|
+
<div class="flex items-center gap-4 flex-1">
|
|
349
|
+
<div class="relative flex-1 max-w-md">
|
|
350
|
+
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400"></i>
|
|
351
|
+
<input type="text" id="global-search" placeholder="Quick search..."
|
|
352
|
+
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent transition-shadow">
|
|
353
|
+
</div>
|
|
354
|
+
<div class="text-sm text-gray-500 italic">
|
|
355
|
+
<i data-lucide="filter" class="w-4 h-4 inline mr-1"></i>
|
|
356
|
+
Use column headers to filter (multiselect supported)
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div class="flex items-center gap-2">
|
|
361
|
+
<button id="download-csv" class="flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-600 bg-gray-50 border border-gray-300 rounded-lg hover:bg-gray-100 transition-colors">
|
|
362
|
+
<i data-lucide="download" class="w-4 h-4"></i>
|
|
363
|
+
Export CSV
|
|
364
|
+
</button>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<!-- Tabulator Container - uses remaining height -->
|
|
369
|
+
<div id="issues-table-container" class="flex-1 bg-white border border-gray-200 rounded-b-xl overflow-hidden shadow-sm"></div>
|
|
309
370
|
</div>
|
|
310
|
-
</
|
|
371
|
+
</main>
|
|
311
372
|
</div>
|
|
312
373
|
|
|
313
374
|
<script>
|
|
314
|
-
|
|
375
|
+
// --- State ---
|
|
376
|
+
const reportRaw = document.getElementById('report-data').textContent;
|
|
377
|
+
const report = JSON.parse(reportRaw);
|
|
378
|
+
|
|
379
|
+
// Flatten issues for the table
|
|
380
|
+
const allIssues = report.files.flatMap(f => f.issues.map(i => ({
|
|
381
|
+
...i,
|
|
382
|
+
fileName: f.relativePath
|
|
383
|
+
})));
|
|
315
384
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
385
|
+
// --- Router ---
|
|
386
|
+
const router = {
|
|
387
|
+
navigate(view) {
|
|
388
|
+
['dashboard', 'issues'].forEach(v => {
|
|
389
|
+
document.getElementById(\`view-\${v}\`).classList.toggle('hidden', v !== view);
|
|
390
|
+
const btn = document.getElementById(\`nav-\${v}\`);
|
|
391
|
+
if (v === view) btn.setAttribute('data-active', 'true');
|
|
392
|
+
else btn.removeAttribute('data-active');
|
|
393
|
+
});
|
|
394
|
+
if (view === 'issues' && window.tableInstance) window.tableInstance.redraw();
|
|
395
|
+
},
|
|
322
396
|
|
|
323
|
-
|
|
324
|
-
|
|
397
|
+
filterBySeverity(severity) {
|
|
398
|
+
this.navigate('issues');
|
|
399
|
+
window.tableInstance.setHeaderFilterValue("severity", [severity]);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// --- Renderer ---
|
|
404
|
+
const renderer = {
|
|
405
|
+
init() {
|
|
406
|
+
document.getElementById('timestamp-display').textContent = new Date(report.metadata.timestamp).toLocaleString();
|
|
407
|
+
|
|
408
|
+
document.getElementById('stat-error').textContent = report.summary.bySeverity.error;
|
|
409
|
+
document.getElementById('stat-warning').textContent = report.summary.bySeverity.warning;
|
|
410
|
+
document.getElementById('stat-info').textContent = report.summary.bySeverity.info;
|
|
411
|
+
document.getElementById('stat-files').textContent = report.metadata.filesScanned;
|
|
412
|
+
document.getElementById('nav-badge').textContent = allIssues.length;
|
|
413
|
+
|
|
414
|
+
this.renderCharts();
|
|
415
|
+
this.initTable();
|
|
416
|
+
lucide.createIcons();
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
renderCharts() {
|
|
420
|
+
const colors = { error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' };
|
|
421
|
+
|
|
422
|
+
// Top Rules (Bar) - Using Names
|
|
423
|
+
const ruleCounts = {};
|
|
424
|
+
const ruleNames = {};
|
|
425
|
+
allIssues.forEach(i => {
|
|
426
|
+
ruleCounts[i.ruleId] = (ruleCounts[i.ruleId] || 0) + 1;
|
|
427
|
+
ruleNames[i.ruleId] = i.ruleName;
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const sortedRules = Object.entries(ruleCounts)
|
|
431
|
+
.sort((a, b) => b[1] - a[1])
|
|
432
|
+
.slice(0, 5);
|
|
433
|
+
|
|
434
|
+
new Chart(document.getElementById('chart-top-rules'), {
|
|
435
|
+
type: 'bar',
|
|
436
|
+
data: {
|
|
437
|
+
labels: sortedRules.map(x => {
|
|
438
|
+
const id = x[0];
|
|
439
|
+
const name = ruleNames[id];
|
|
440
|
+
return name.length > 30 ? name.substring(0, 27) + '...' : name;
|
|
441
|
+
}),
|
|
442
|
+
datasets: [{
|
|
443
|
+
label: 'Violations',
|
|
444
|
+
data: sortedRules.map(x => x[1]),
|
|
445
|
+
backgroundColor: '#0ea5e9',
|
|
446
|
+
borderRadius: 6,
|
|
447
|
+
}]
|
|
448
|
+
},
|
|
449
|
+
options: {
|
|
450
|
+
maintainAspectRatio: false,
|
|
451
|
+
plugins: {
|
|
452
|
+
legend: { display: false },
|
|
453
|
+
tooltip: {
|
|
454
|
+
callbacks: {
|
|
455
|
+
title: (ctx) => {
|
|
456
|
+
const idx = ctx[0].dataIndex;
|
|
457
|
+
const id = sortedRules[idx][0];
|
|
458
|
+
return \`\${ruleNames[id]} (\${id})\`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
scales: { y: { beginAtZero: true, grid: { borderDash: [2, 4], color: '#f1f5f9' } }, x: { grid: { display: false } } }
|
|
464
|
+
}
|
|
465
|
+
});
|
|
325
466
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
467
|
+
// Severity (Doughnut)
|
|
468
|
+
new Chart(document.getElementById('chart-severity'), {
|
|
469
|
+
type: 'doughnut',
|
|
470
|
+
data: {
|
|
471
|
+
labels: ['Error', 'Warning', 'Info'],
|
|
472
|
+
datasets: [{
|
|
473
|
+
data: [report.summary.bySeverity.error, report.summary.bySeverity.warning, report.summary.bySeverity.info],
|
|
474
|
+
backgroundColor: [colors.error, colors.warning, colors.info],
|
|
475
|
+
borderWidth: 0,
|
|
476
|
+
hoverOffset: 4
|
|
477
|
+
}]
|
|
478
|
+
},
|
|
479
|
+
options: {
|
|
480
|
+
cutout: '75%',
|
|
481
|
+
plugins: { legend: { position: 'bottom', labels: { usePointStyle: true, padding: 20 } } }
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Categories (Bar) - Colorful
|
|
486
|
+
const catCounts = {};
|
|
487
|
+
allIssues.forEach(i => catCounts[i.category] = (catCounts[i.category] || 0) + 1);
|
|
488
|
+
const sortedCats = Object.entries(catCounts).sort((a,b) => b[1] - a[1]);
|
|
489
|
+
|
|
490
|
+
// Color palette for categories
|
|
491
|
+
const categoryColors = [
|
|
492
|
+
'#0ea5e9', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444',
|
|
493
|
+
'#ec4899', '#06b6d4', '#84cc16', '#f97316', '#6366f1'
|
|
494
|
+
];
|
|
495
|
+
|
|
496
|
+
new Chart(document.getElementById('chart-categories'), {
|
|
497
|
+
type: 'bar',
|
|
498
|
+
data: {
|
|
499
|
+
labels: sortedCats.map(x => x[0].charAt(0).toUpperCase() + x[0].slice(1)),
|
|
500
|
+
datasets: [{
|
|
501
|
+
label: 'Issues',
|
|
502
|
+
data: sortedCats.map(x => x[1]),
|
|
503
|
+
backgroundColor: sortedCats.map((_, i) => categoryColors[i % categoryColors.length]),
|
|
504
|
+
borderRadius: 4,
|
|
505
|
+
barThickness: 20
|
|
506
|
+
}]
|
|
507
|
+
},
|
|
508
|
+
options: {
|
|
509
|
+
indexAxis: 'y',
|
|
510
|
+
maintainAspectRatio: false,
|
|
511
|
+
plugins: { legend: { display: false } },
|
|
512
|
+
scales: { x: { beginAtZero: true, grid: { borderDash: [2, 4], color: '#f1f5f9' } }, y: { grid: { display: false } } }
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
},
|
|
332
516
|
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
517
|
+
initTable() {
|
|
518
|
+
const table = new Tabulator("#issues-table-container", {
|
|
519
|
+
data: allIssues,
|
|
520
|
+
layout: "fitColumns",
|
|
521
|
+
height: "100%",
|
|
522
|
+
placeholder: "No issues found matching filters",
|
|
523
|
+
frozenRows: 0, // Header is always frozen in Tabulator by default
|
|
524
|
+
|
|
525
|
+
columns: [
|
|
526
|
+
{
|
|
527
|
+
title: "Severity",
|
|
528
|
+
field: "severity",
|
|
529
|
+
width: 140,
|
|
530
|
+
headerFilter: "list",
|
|
531
|
+
headerFilterParams: {
|
|
532
|
+
valuesLookup: true,
|
|
533
|
+
multiselect: true,
|
|
534
|
+
clearable: true
|
|
535
|
+
},
|
|
536
|
+
headerFilterFunc: "in",
|
|
537
|
+
formatter: (cell) => {
|
|
538
|
+
const val = cell.getValue();
|
|
539
|
+
const colorMap = {
|
|
540
|
+
error: "bg-red-100 text-red-700 border-red-200",
|
|
541
|
+
warning: "bg-orange-100 text-orange-700 border-orange-200",
|
|
542
|
+
info: "bg-blue-100 text-blue-700 border-blue-200"
|
|
543
|
+
};
|
|
544
|
+
return '<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ' + (colorMap[val] || 'bg-gray-100') + ' uppercase tracking-wide">' + val + '</span>';
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
title: "Rule",
|
|
549
|
+
field: "ruleName",
|
|
550
|
+
width: 250,
|
|
551
|
+
headerFilter: "list",
|
|
552
|
+
headerFilterParams: {
|
|
553
|
+
valuesLookup: true,
|
|
554
|
+
multiselect: true,
|
|
555
|
+
clearable: true
|
|
556
|
+
},
|
|
557
|
+
headerFilterFunc: "in",
|
|
558
|
+
formatter: (cell) => {
|
|
559
|
+
const row = cell.getRow().getData();
|
|
560
|
+
return '<div class="flex flex-col"><span class="font-medium text-sm text-gray-900">' + cell.getValue() + '</span><span class="text-xs text-gray-500 font-mono">' + row.ruleId + '</span></div>';
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
title: "Category",
|
|
565
|
+
field: "category",
|
|
566
|
+
width: 140,
|
|
567
|
+
headerFilter: "list",
|
|
568
|
+
headerFilterParams: {
|
|
569
|
+
valuesLookup: true,
|
|
570
|
+
multiselect: true,
|
|
571
|
+
clearable: true
|
|
572
|
+
},
|
|
573
|
+
headerFilterFunc: "in",
|
|
574
|
+
formatter: (cell) => '<span class="text-sm text-gray-600 capitalize">' + cell.getValue() + '</span>'
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
title: "File Path",
|
|
578
|
+
field: "fileName",
|
|
579
|
+
headerFilter: "input",
|
|
580
|
+
headerFilterPlaceholder: "Filter...",
|
|
581
|
+
formatter: (cell) => {
|
|
582
|
+
const row = cell.getRow().getData();
|
|
583
|
+
return '<div class="flex flex-col"><span class="font-mono text-sm text-brand-600 font-medium truncate" title="' + row.fileName + '">' + row.fileName + '</span><span class="text-xs text-gray-400 font-mono">Line: ' + row.line + '</span></div>';
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
title: "Message",
|
|
588
|
+
field: "message",
|
|
589
|
+
widthGrow: 2,
|
|
590
|
+
headerFilter: "input",
|
|
591
|
+
headerFilterPlaceholder: "Filter...",
|
|
592
|
+
formatter: (cell) => {
|
|
593
|
+
const row = cell.getRow().getData();
|
|
594
|
+
return '<div class="flex flex-col gap-1"><span class="text-sm text-gray-700">' + cell.getValue() + '</span><span class="text-xs text-gray-400">' + row.ruleDescription + '</span></div>';
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
initialSort: [{ column: "severity", dir: "asc" }]
|
|
599
|
+
});
|
|
337
600
|
|
|
338
|
-
|
|
339
|
-
const textMatch = text.includes(filter);
|
|
601
|
+
window.tableInstance = table;
|
|
340
602
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
table.style.display = 'none';
|
|
358
|
-
} else {
|
|
359
|
-
noResults.style.display = 'none';
|
|
360
|
-
table.style.display = '';
|
|
603
|
+
// Quick Search
|
|
604
|
+
document.getElementById('global-search').addEventListener('keyup', (e) => {
|
|
605
|
+
const term = e.target.value.toLowerCase();
|
|
606
|
+
if (!term) { table.clearFilter(); return; }
|
|
607
|
+
table.setFilter((data) => (
|
|
608
|
+
data.message.toLowerCase().includes(term) ||
|
|
609
|
+
data.fileName.toLowerCase().includes(term) ||
|
|
610
|
+
data.ruleName.toLowerCase().includes(term) ||
|
|
611
|
+
data.ruleId.toLowerCase().includes(term)
|
|
612
|
+
));
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Export
|
|
616
|
+
document.getElementById('download-csv').addEventListener('click', () => {
|
|
617
|
+
table.download("csv", "mule-lint-report.csv");
|
|
618
|
+
});
|
|
361
619
|
}
|
|
362
|
-
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// --- Init ---
|
|
623
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
624
|
+
renderer.init();
|
|
625
|
+
});
|
|
626
|
+
|
|
363
627
|
</script>
|
|
364
628
|
</body>
|
|
365
629
|
</html>`;
|
|
366
630
|
}
|
|
367
|
-
function renderIssueRow(issue) {
|
|
368
|
-
const badgeClass = issue.severity === 'error' ? 'bg-error' :
|
|
369
|
-
issue.severity === 'warning' ? 'bg-warning' : 'bg-info';
|
|
370
|
-
return `<tr data-severity="${issue.severity}">
|
|
371
|
-
<td><span class="severity-badge ${badgeClass}">${issue.severity}</span></td>
|
|
372
|
-
<td><span class="rule-pill">${issue.ruleId}</span></td>
|
|
373
|
-
<td class="file-link" title="${issue.file}">${issue.file}</td>
|
|
374
|
-
<td class="location">${issue.line}:${issue.column}</td>
|
|
375
|
-
<td>${escapeHtml(issue.message)}</td>
|
|
376
|
-
</tr>`;
|
|
377
|
-
}
|
|
378
|
-
function escapeHtml(unsafe) {
|
|
379
|
-
return unsafe
|
|
380
|
-
.replace(/&/g, "&")
|
|
381
|
-
.replace(/</g, "<")
|
|
382
|
-
.replace(/>/g, ">")
|
|
383
|
-
.replace(/"/g, """)
|
|
384
|
-
.replace(/'/g, "'");
|
|
385
|
-
}
|
|
386
631
|
//# sourceMappingURL=HtmlFormatter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HtmlFormatter.js","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HtmlFormatter.js","sourceRoot":"","sources":["../../../src/formatters/HtmlFormatter.ts"],"names":[],"mappings":";;;;;AAOA,gCA6mBC;AAnnBD,oCAAqC;AACrC,sEAA6C;AAE7C;;GAEG;AACH,SAAgB,UAAU,CAAC,MAAkB;IACzC,iBAAiB;IACjB,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5C,GAAG,IAAI;QACP,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YAC5B,MAAM,OAAO,GAAG,iBAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;YAC3D,OAAO;gBACH,GAAG,KAAK;gBACR,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,SAAS;gBACxC,eAAe,EAAE,OAAO,EAAE,WAAW,IAAI,0BAA0B;gBACnE,QAAQ,EAAE,OAAO,EAAE,IAAI,IAAI,KAAK,CAAC,MAAM;gBACvC,IAAI,EAAE,IAAI,CAAC,YAAY;aAC1B,CAAC;QACN,CAAC,CAAC;KACL,CAAC,CAAC,CAAC;IAEJ,yCAAyC;IACzC,MAAM,UAAU,GAAG;QACf,QAAQ,EAAE;YACN,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,OAAO,EAAE,sBAAW,CAAC,OAAO;YAC5B,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;YACjC,QAAQ,EAAE,MAAM,CAAC,UAAU,IAAI,CAAC;SACnC;QACD,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,iBAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;KACtG,CAAC;IAEF,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAExE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuJD,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsbb,CAAC;AACT,CAAC"}
|